当前位置: 首页 > web >正文

项目基于udp通信的聊天室

udp.h

#ifndef _UDP_H_
#define _UDP_H_#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>#define M 32       // 用户名最大长度
#define N 1024     // 消息内容最大长度
#define ERRLOG(msg) do { perror(msg); exit(-1); } while(0)// 消息结构体(包含操作码、用户名、消息内容)
typedef struct {char code;          // 操作码:'d'登录 'q'群聊 't'退出char user[M];       // 用户名char text[N];       // 消息内容(群聊内容/系统消息)
} msg_t;// 链表节点结构体(存储客户端地址信息)
typedef struct node {struct sockaddr_in addr;  // 客户端socket地址struct node *next;        // 指向下一个节点
} node_t;#endif

server.c

#include "udp.h"// 创建链表头节点
int create_node(node_t **phead) {*phead = (node_t *)malloc(sizeof(node_t));if (*phead == NULL) {ERRLOG("内存分配失败");}(*phead)->next = NULL;return 0;
}// 尾插法插入客户端节点
int insert_node(node_t *phead, struct sockaddr_in addr) {node_t *pnew = NULL;create_node(&pnew);pnew->addr = addr;node_t *ptemp = phead;while (ptemp->next != NULL) {ptemp = ptemp->next;}ptemp->next = pnew;return 0;
}// 删除指定客户端节点
int delete_node(node_t *phead, struct sockaddr_in addr) {node_t *prev = phead;node_t *curr = phead->next;while (curr != NULL) {if (memcmp(&curr->addr, &addr, sizeof(addr)) == 0) {prev->next = curr->next;free(curr);return 0;}prev = curr;curr = curr->next;}return -1;
}int main(int argc, const char *argv[]) {if (argc != 3) {printf("Usage: %s <IP> <port>\n", argv[0]);return -1;}// 创建UDP套接字int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd == -1) {ERRLOG("socket创建失败");}// 绑定服务器地址struct sockaddr_in serveraddr;memset(&serveraddr, 0, sizeof(serveraddr));serveraddr.sin_family = AF_INET;serveraddr.sin_port = htons(atoi(argv[2]));serveraddr.sin_addr.s_addr = inet_addr(argv[1]);if (bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) == -1) {ERRLOG("bind失败");}printf("服务器启动,监听 %s:%d\n", argv[1], atoi(argv[2]));// 初始化客户端链表node_t *phead;create_node(&phead);pid_t pid = fork();if (pid < 0) {ERRLOG("fork失败");} else if (pid == 0) {  // 子进程:处理客户端消息接收与广播struct sockaddr_in clientaddr;socklen_t client_len = sizeof(clientaddr);msg_t msg;while (1) {memset(&msg, 0, sizeof(msg));if (recvfrom(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&clientaddr, &client_len) == -1) {ERRLOG("recvfrom失败");}switch (msg.code) {case 'd':  // 登录处理printf("[%s] 上线\n", msg.user);insert_node(phead, clientaddr);  // 添加到在线列表// 广播登录通知(排除自己)node_t *p = phead->next;while (p != NULL) {if (memcmp(&p->addr, &clientaddr, sizeof(clientaddr)) != 0) {sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&p->addr, sizeof(p->addr));}p = p->next;}break;case 'q':  // 群聊处理if (strcmp(msg.user, "管理员") != 0) {  // 非管理员消息打印printf("[%s]: %s\n", msg.user, msg.text);}// 广播群聊消息(包括自己,UDP无连接需全播)node_t *q = phead->next;while (q != NULL) {sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&q->addr, sizeof(q->addr));q = q->next;}break;case 't':  // 退出处理printf("[%s] 下线\n", msg.user);delete_node(phead, clientaddr);  // 从列表删除// 广播退出通知node_t *r = phead->next;while (r != NULL) {sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&r->addr, sizeof(r->addr));r = r->next;}break;}}} else {  // 父进程:发送系统消息(以"管理员"身份)msg_t sys_msg;while (1) {memset(&sys_msg, 0, sizeof(sys_msg));strcpy(sys_msg.user, "管理员");fgets(sys_msg.text, N, stdin);sys_msg.text[strlen(sys_msg.text) - 1] = '\0';  // 去除换行符sys_msg.code = 'q';  // 系统消息以群聊码发送// 广播系统消息给所有在线客户端node_t *s = phead->next;while (s != NULL) {sendto(sockfd, &sys_msg, sizeof(sys_msg), 0, (struct sockaddr *)&s->addr, sizeof(s->addr));s = s->next;}}}close(sockfd);return 0;
}

client.c

#include "udp.h"int main(int argc, const char *argv[]) {if (argc != 3) {printf("Usage: %s <服务器IP> <端口>\n", argv[0]);return -1;}// 创建UDP套接字int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd == -1) {ERRLOG("socket创建失败");}// 服务器地址struct sockaddr_in serveraddr;memset(&serveraddr, 0, sizeof(serveraddr));serveraddr.sin_family = AF_INET;serveraddr.sin_port = htons(atoi(argv[2]));serveraddr.sin_addr.s_addr = inet_addr(argv[1]);socklen_t server_len = sizeof(serveraddr);// 登录:发送用户名msg_t msg;char username[M] = {0};printf("请输入用户名(退出输入quit): ");fgets(username, M, stdin);username[strlen(username) - 1] = '\0';  // 去除换行符if (strcmp(username, "quit") == 0) {printf("未登录,退出程序\n");close(sockfd);return 0;}msg.code = 'd';strcpy(msg.user, username);if (sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&serveraddr, server_len) == -1) {ERRLOG("登录消息发送失败");}pid_t pid = fork();if (pid < 0) {ERRLOG("fork失败");} else if (pid == 0) {  // 子进程:接收服务器广播消息msg_t recv_msg;while (1) {memset(&recv_msg, 0, sizeof(recv_msg));if (recvfrom(sockfd, &recv_msg, sizeof(recv_msg), 0, (struct sockaddr *)&serveraddr, &server_len) == -1) {ERRLOG("recvfrom失败");}// 过滤自己的登录/退出通知(UDP广播会收到自己的消息)if (strcmp(recv_msg.user, username) == 0 && (recv_msg.code == 'd' || recv_msg.code == 't')) {continue;}switch (recv_msg.code) {case 'd': printf("[%s] 登录上线\n", recv_msg.user); break;case 'q': printf("[%s]: %s\n", recv_msg.user, recv_msg.text); break;case 't': printf("[%s] 退出聊天\n", recv_msg.user); break;}}} else {  // 父进程:发送群聊消息或退出while (1) {memset(msg.text, 0, N);fgets(msg.text, N, stdin);msg.text[strlen(msg.text) - 1] = '\0';  // 去除换行符if (strcmp(msg.text, "quit") == 0) {  // 退出操作msg.code = 't';sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&serveraddr, server_len);break;} else {  // 群聊消息msg.code = 'q';sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&serveraddr, server_len);}}}close(sockfd);return 0;
}

http://www.xdnf.cn/news/6161.html

相关文章:

  • CPU的用户态(用户模式)和核心态(内核态)
  • 若依框架页面
  • 填涂颜色(bfs)
  • 如何恢复被勒索软件加密的服务器文件(解密与备份策略)
  • (C语言)超市管理系统(测试2版)(指针)(数据结构)(清屏操作)
  • 内存安全设计方案
  • FFmpeg 与 C++ 构建音视频处理全链路实战(五)—— 音视频编码与封装
  • vue 去掉右边table的下拉条与下面的白色边框并补充满
  • Android Activity之间跳转的原理
  • 试除法判断素数优化【C语言】
  • C语言:51单片机实现数码管依次循环显示【1~F】课堂练习
  • Spring 中的 @Configuration @Bean注解
  • PyTorch 中神经网络相关要点(损失函数,学习率)及优化方法总结
  • 建筑IT数字化突围:建筑设计企业的生存法则重塑
  • java连数据库
  • FFmpeg视频编码的完整操作指南
  • 如何设置FFmpeg实现对高分辨率视频进行转码
  • Tailwind CSS 实战教程:从入门到精通
  • 基于开源AI大模型与S2B2C生态的个人品牌优势挖掘与标签重构研究
  • 数据库系统概论|第七章:数据库设计—课程笔记
  • 使用大语言模型从零构建知识图谱(上)
  • Kubernetes控制平面组件:Kubelet详解(三):CRI 容器运行时接口层
  • 国产 ETL 数据集成厂商推荐—谷云科技 RestCloud
  • 【C++设计模式之Decorator装饰模式】
  • 砷化镓太阳能电池:开启多元领域能源新篇
  • 什么是SparkONYarn模式?
  • 【解析:新能源汽车芯片主要玩家及技术发展】
  • 聊聊JetCache的缓存构建
  • 基于自校准分数的扩散模型在并行磁共振成像中联合进行线圈灵敏度校正和运动校正|文献速递-深度学习医疗AI最新文献
  • SVM在医疗设备故障维修服务决策中的应用:策略、技术与实践