基于UDP的网络聊天室
项目需求:
- 如果有用户登录,其他用户可以收到这个人的登录信息
- 如果有人发送信息,其他用户可以收到这个人的群聊信息
- 如果有人下线,其他用户可以收到这个人的下线信息
- 服务器可以发送系统信息
zy.h
#ifndef UDP_H
#define UDP_H#include<myhead.h>#define N 128
#define M 32typedef struct Node{struct sockaddr_in sin; struct Node *next;
}Node,*Nodeptr;typedef struct Msg{char code;char user[M];char text[N];
}msg_t;#endif
服务端:ser.c
#include "zy.h"
void create(Nodeptr *q)
{// 分配内存以创建新的链表节点*q = (Nodeptr )malloc(sizeof(Node));// 如果内存分配失败,则打印错误信息并退出函数if(*q ==NULL){printf("error\n");return ;}// 将新节点的next指针设置为NULL,表示该节点目前是链表的最后一个节点(*q)->next = NULL;
}
int insert_tail(Nodeptr q, struct sockaddr_in sin)
{// 检查链表头指针是否为空,如果为空则无法插入节点if(q == NULL){printf("插入失败\n");return -1;}// 定义并初始化Nodeptr p = NULL;create(&p);p->sin = sin;// 定义一个临时指针L,用于遍历链表Nodeptr L = q;// 遍历链表,直到找到最后一个节点while (L->next != NULL){L = L->next;}// 将新节点p链接到链表的尾部L->next = p;return 0;
}int main(int argc, char const *argv[])
{// 检查命令行参数数量,确保提供了IP和端口号if(argc != 3){printf("请输入IP和端口号\n");return -1;}// 创建套接字int sfd = socket(AF_INET,SOCK_DGRAM,0);if(sfd == -1){perror("error");return -1;}// 初始化服务器地址结构struct sockaddr_in sin;sin.sin_family = AF_INET;sin.sin_port = htons(atoi(argv[2]));sin.sin_addr.s_addr = inet_addr(argv[1]);// 绑定套接字if(bind(sfd,(struct sockaddr *)&sin,sizeof(sin)) == -1){perror("error bind");return -1;}// 初始化客户端地址结构struct sockaddr_in cin;socklen_t len = sizeof(cin);char name[32] = {0};pid_t pid= 0;// 客户端消息结构体msg_t msg;// 服务器消息结构体msg_t msg_ser;// 客户端节点指针,用于管理客户端列表Nodeptr q = NULL;// 初始化客户端列表create(&q);// 设置客户端列表的服务器地址q->sin =cin;// 创建子进程pid = fork();if(pid < 0){perror("fork error");return -1;}else if (pid ==0){// 子进程:接收并处理客户端消息while (1){bzero(&msg,sizeof(msg));recvfrom(sfd,(void*)&msg,sizeof(msg),0,(struct sockaddr *)&cin,&len);switch (msg.code){case 'L':{printf("[%s]:玩家已上线\n",msg.user);// 将新上线的客户端插入到列表中insert_tail(q,cin);// 遍历客户端列表,通知所有客户端有人上线Nodeptr p1 =q->next;while (p1 !=NULL){msg.code = 'd';if(sendto(sfd,&msg,sizeof(msg),0,(struct sockaddr *)&p1->sin,sizeof(p1->sin)) == -1){perror("error send");return -1;}p1=p1->next;}}break;case 'C':{// 遍历客户端列表,向所有客户端发送消息Nodeptr p2 = q->next; while(p2 != NULL){msg.code='q';if(sendto(sfd,(void *)&msg,sizeof(msg),0,(struct sockaddr *)&p2->sin,sizeof(p2->sin)) == -1){perror("send error");}p2=p2->next;} } break; case 'Q':// 接收客户端退出消息,并从列表中移除该客户端printf("[%s]:退出了...\n", msg.user);Nodeptr p3 = q; Nodeptr h = NULL; while(p3->next != NULL){msg.code='t';if(memcmp(&(p3->next->sin), &cin,sizeof(cin)) == 0){h = p3->next;p3->next = h->next;free(h);}else{p3 = p3->next;if(-1 == sendto(sfd, &msg,sizeof(msg),0,(struct sockaddr *)&p3->sin,sizeof(p3->sin)))){perror("send error");}} } break;}}}else if (pid > 0){// 父进程:发送管理员消息给所有客户端while (1){strcpy(msg_ser.user,"管理员");memset(msg_ser.text, 0, N);fgets(msg_ser.text,N,stdin);msg_ser.text[strlen(msg_ser.text)-1] = '\0';msg_ser.code ='q';if(sendto(sfd,&msg_ser,sizeof(msg_ser),0,(struct sockaddr *)&sin,sizeof(sin)) == -1){perror("send error");return -1;}}}// 结束子进程kill(pid,SIGKILL);// 等待子进程结束wait(NULL);close(sfd);return 0;
}
客户端:cli.c
#include "zy.h"int main(int argc, char const *argv[])
{// 检查命令行参数数量是否正确if(argc != 3){printf("请在后面输入IP和端口号\n");return -1;}// 创建套接字int sfd = socket(AF_INET,SOCK_DGRAM,0);if(sfd == -1){perror("socket error");return -1;}// 设置服务器地址结构struct sockaddr_in sin;sin.sin_family = AF_INET;sin.sin_port = htons(atoi(argv[2]));sin.sin_addr.s_addr = inet_addr(argv[1]);socklen_t lent =sizeof(sin);// 初始化变量int res = 0;char name[32]={0};msg_t msg;pid_t pid;// 获取用户名称printf("请输入你的名字:");msg.code = 'L';fgets(name,M,stdin);strcpy(msg.user,name);msg.user[strlen(msg.user) - 1] = '\0';// 向服务器发送登录请求if(sendto(sfd,&msg,sizeof(msg),0,(struct sockaddr *)&sin,lent) == -1){perror("send error");return -1;}// 创建进程,子进程接收数据,父进程发送数据pid = fork();if(pid == -1){perror("fork error");}else if(pid == 0){ // 子进程:接收服务器消息while (1){bzero(&msg,sizeof(msg));// 接收服务器的应答if ( (res=recvfrom(sfd, &msg, sizeof(msg), 0,(struct sockaddr *)&sin,&lent)) == -1){perror("recv error");} // 处理接收到的消息if(strcmp(msg.user,name) == -10){ continue;}else{// 根据消息类型打印信息switch(msg.code){case 'L':printf("[%s]登录上线了\n", msg.user); break; case 'C':printf("[%s]:%s\n",msg.user,msg.text);break;case 'Q': printf("[%s]退出了\n", msg.user); break;} } } }else if(pid>0){// 父进程:发送消息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 = 'Q'; if (sendto(sfd, &msg, sizeof(msg), 0,(struct sockaddr *)&sin,lent) == -1){perror("send error"); } break;}else{msg.code = 'C'; }// 发送消息给服务器if (sendto(sfd, &msg, sizeof(msg), 0,(struct sockaddr *)&sin,lent) == -1){perror("send error");}} } close(sfd);return 0;
}
结果图:

