聊天室的流程图如上所示
实现
服务器端:
#include <stdio.h>#include <sys/types.h>#include <sys/socket.h>#include <unistd.h>#include <sys/socket.h>#include <netinet/in.h>#include <netinet/ip.h>#include <string.h>#include <arpa/inet.h>#include <stdlib.h>#define ERRLOG(errmsg) do{\perror(errmsg);\printf("%s--%s--(%d)\n",__FILE__,__func__,__LINE__);\exit(-1);\}while(0)typedef struct MSG{char code;// 'L'登录 'C'群聊 'Q'退出(类似于选项标号)char name[32];//名字char text[128]; //文本输入的内容}msg_t;//链表使用来保存客户端 网络信息结构体的//所以数据域应该是 struct sockaddr_in 类型typedef struct NODE{struct sockaddr_in c_addr;struct NODE *next;}node_t;//定义有头链表结构体node_t *create_node();//声明三个函数void do_login(int , msg_t , node_t *, struct sockaddr_in);void do_chat(int , msg_t , node_t *, struct sockaddr_in);void do_quit(int , msg_t , node_t *, struct sockaddr_in);int main(int argc, char *argv[]){if(3 != argc){printf("Usage: %s <IP> <port>\n", argv[0]);exit(-1);}//创建 UDP 套接字int sockfd = -1;if(-1 == (sockfd = socket(AF_INET, SOCK_DGRAM, 0))){ERRLOG("socket error");}//填充服务器网络信息结构体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 serveraddr_len = sizeof(serveraddr);struct sockaddr_in clientaddr;memset(&clientaddr, 0, sizeof(clientaddr));socklen_t clientaddr_len = sizeof(clientaddr);//绑定 套接字和服务器网络信息结构体if(-1 == bind(sockfd, (struct sockaddr *)&serveraddr, serveraddr_len)){ERRLOG("bind error");}//创建父子进程分别处理读写pid_t pid = 0;if(-1 == (pid = fork())){ERRLOG("fork error");}else if(pid>0)//父进程 负责发送系统消息{//可以把父进程当做一个客户端 以群聊的方式发送系统消息msg_t msg;msg.code = 'C';strcpy(msg.name,"server");//把服务器添加进去while(1){fgets(msg.text, 128, stdin);msg.text[strlen(msg.text)-1] = '\0';if(-1 == sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&serveraddr, serveraddr_len)){ERRLOG("sendto error");}}}else if(0 == pid)//子进程{//创建保存客户端网络信息结构体的链表node_t *phead = create_node();msg_t msg;memset(&msg, 0, sizeof(msg));while(1){//子进程 负责接收客户端的消息if(-1 == recvfrom(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&clientaddr, &clientaddr_len)){ERRLOG("recvfrom error");}//printf("%c %s %s\n", msg.code, msg.name, msg.text);switch(msg.code){case 'L':do_login(sockfd, msg, phead, clientaddr);//执行相应的函数break;case 'C':do_chat(sockfd, msg, phead, clientaddr);//执行相应的函数break;case 'Q':do_quit(sockfd, msg, phead, clientaddr);//执行相应的函数break;}}}return 0;}//登录的函数void do_login(int sockfd, msg_t msg, node_t *phead, struct sockaddr_in clientaddr){node_t *ptemp = phead;//保存一下当前节点信息,操作完毕可以查找到上一节点sprintf(msg.text, "%s %s", msg.name, "上线了");//遍历链表 发送 xx上线while(ptemp->next != NULL){ptemp = ptemp->next;if(-1 == sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&(ptemp->c_addr), sizeof(ptemp->c_addr))){ERRLOG("sendto error");}}//将新用户的信息 头插到链表中(有头链表的操作)node_t *pnew = create_node();pnew->c_addr = clientaddr;pnew->next = phead->next;phead->next = pnew;}//群聊的函数void do_chat(int sockfd, msg_t msg, node_t *phead, struct sockaddr_in clientaddr){node_t *ptemp = phead;//将消息发给除了自己的所有人while(ptemp->next != NULL){ptemp = ptemp->next;if(memcmp(&(ptemp->c_addr), &clientaddr, sizeof(clientaddr))){if(-1 == sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&(ptemp->c_addr), sizeof(ptemp->c_addr))){ERRLOG("sendto error");}}}}//退出的函数void do_quit(int sockfd, msg_t msg, node_t *phead, struct sockaddr_in clientaddr){node_t *ptemp = phead;//将 XX退出的消息 发给除了他自己的所有人sprintf(msg.text, "%s %s", msg.name, "下线了");while(ptemp->next != NULL){//ptemp = ptemp->next;if(memcmp(&(ptemp->next->c_addr), &clientaddr, sizeof(clientaddr))){if(-1 == sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&(ptemp->next->c_addr), sizeof(ptemp->next->c_addr))){ERRLOG("sendto error");}ptemp = ptemp->next;}else{//将退出的客户端的网络信息结构体在链表中删除node_t *pdel = ptemp->next;ptemp->next = pdel->next;free(pdel);pdel = NULL;}}}//创建链表节点的函数node_t *create_node(){node_t *p = (node_t *)malloc(sizeof(node_t));p->next = NULL;return p;}
客户端:
#include <stdio.h>#include <sys/types.h>#include <sys/socket.h>#include <unistd.h>#include <sys/socket.h>#include <netinet/in.h>#include <netinet/ip.h>#include <string.h>#include <arpa/inet.h>#include <stdlib.h>#include <signal.h>#define ERRLOG(errmsg) do{\perror(errmsg);\printf("%s--%s--(%d)\n",__FILE__,__func__,__LINE__);\exit(-1);\}while(0)typedef struct MSG{char code;// 'L'登录 'C'群聊 'Q'退出char name[32];char text[128];}msg_t;int main(int argc, char *argv[]){if(3 != argc){printf("Usage: %s <IP> <port>\n", argv[0]);exit(-1);}//创建 UDP 套接字int sockfd = -1;if(-1 == (sockfd = socket(AF_INET, SOCK_DGRAM, 0))){ERRLOG("socket error");}//填充服务器网络信息结构体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 serveraddr_len = sizeof(serveraddr);//先执行登录操作msg_t msg;msg.code = 'L';printf("请输入用户名:");fgets(msg.name, 32, stdin);msg.name[strlen(msg.name)-1] = '\0';//发送登录消息if(-1 == sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&serveraddr, serveraddr_len)){ERRLOG("sendto error");}//创建父子进程分别处理读写pid_t pid = 0;if(-1 == (pid = fork())){ERRLOG("fork error");}//父进程else if(pid>0){//父进程 负责发送消息while(1){fgets(msg.text, 128, stdin);msg.text[strlen(msg.text)-1] = '\0';if(!strcmp(msg.text, "quit")){msg.code = 'Q';}else{msg.code = 'C';}//发送消息if(-1 == sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&serveraddr, serveraddr_len)){ERRLOG("sendto error");}//判断是否是quitif(!strcmp(msg.text, "quit")){kill(pid, SIGINT);close(sockfd);exit(0);}}}//子进程else if(0 == pid){//接收服务器发来的消息while(1){if(-1 == recvfrom(sockfd, &msg, sizeof(msg), 0, NULL, NULL)){ERRLOG("recvfrom error");}//输出接到的消息printf("%s : %s\n", msg.name, msg.text);}}return 0;}
华清远见学习笔记整理(4)
如果觉得《TCP网络编程(基于UDP的网络聊天室)》对你有帮助,请点赞、收藏,并留下你的观点哦!