当前位置:   article > 正文

【网络编程】 基于UDP的网络聊天室

【网络编程】 基于UDP的网络聊天室

前言

        将前面的数据结构,多线程,网络的内容加在一起的一个项目,比较综合,在代码部分采用了分文件编译并且写了比较详细的注释(个人觉得)。

ps:希望对大家有用

项目需求:

  1. 如果有用户登录,其他用户可以收到这个人的登录信息
  2. 如果有人发送信息,其他用户可以收到这个人的群聊信息
  3. 如果有人下线,其他用户可以收到这个人的下线信息
  4. 服务器可以发送系统信息

项目原理分析图

服务端

chatser.h

  1. #ifndef CHATSER_H
  2. #define CHATSER_H
  3. #include<myhead.h>
  4. //从客户端接收消息结构体
  5. typedef struct msgTyp
  6. {
  7. char type;//消息类型
  8. char userName[20];//用户名
  9. char msgText[1024];//消息数据
  10. }msgTyp,*msgTypPtr;
  11. //用户信息
  12. typedef struct users
  13. {
  14. char userName[20];//用户名
  15. struct sockaddr_in cin;//客户端的信息
  16. }Users,*UsersPtr;
  17. //链表的结构体
  18. typedef struct Node
  19. {
  20. union
  21. {
  22. int len;//链表长度
  23. UsersPtr data;//用户信息
  24. };
  25. struct Node *next;//指针域
  26. }Node, *NodePtr;
  27. //创建传递给线程函数的结构体
  28. typedef struct argTyp
  29. {
  30. NodePtr L;//链表
  31. struct sockaddr_in cin;//客户端的信息
  32. struct sockaddr_in sin;//服务器的信息
  33. int sfd;//套接字
  34. msgTyp msg;//客户端接收消息结构体
  35. }argTyp,*argTypPtr;
  36. //创建链表
  37. NodePtr carete_link();
  38. //创建节点
  39. NodePtr create_node(char *username,struct sockaddr_in cin);
  40. //服务器发送消息
  41. void *send_msg(void *arg);
  42. //服务器接收并转发消息
  43. void *recv_msg(void *arg);
  44. //删除节点
  45. void delete_node(NodePtr L, char *username);
  46. #endif

chatser.c

  1. #include"chatser.h"
  2. //创建链表
  3. NodePtr carete_link()
  4. {
  5. //创建头节点
  6. NodePtr L = (NodePtr)malloc(sizeof(Node));
  7. if (NULL == L)
  8. {
  9. printf("创建头节点失败\n");
  10. return NULL;
  11. }
  12. //初始化头节点
  13. L->len = 0;
  14. L->next = NULL;
  15. return L;
  16. }
  17. //创建节点
  18. NodePtr create_node(char *username,struct sockaddr_in cin)
  19. {
  20. //创建节点
  21. NodePtr p =(NodePtr)malloc(sizeof(Node));
  22. if (NULL == p)
  23. {
  24. printf("创建节点失败\n");
  25. return NULL;
  26. }
  27. //初始化节点信息
  28. p->data = (UsersPtr)malloc(sizeof(Users));//data用户信息分配空间
  29. strcpy(p->data->userName,username);//将用户名复制到data的username
  30. p->data->cin = cin;//将客户端信息复制到data的cin
  31. p->next = NULL;
  32. return p;
  33. }
  34. //服务器发送消息
  35. void *send_msg(void *arg)//传入线程函数的结构体
  36. {
  37. //获取结构体
  38. argTypPtr argTyp = (argTypPtr)arg;//结构体
  39. NodePtr L = argTyp->L;//链表
  40. struct sockaddr_in sin = argTyp->sin;//服务器信息
  41. int sfd = argTyp->sfd;//socket
  42. while (1)
  43. {
  44. //从终端输入消息
  45. char buf[1024] = "";//未处理消息
  46. char wbuf[1024] = "";//处理后的消息
  47. fgets(buf,sizeof(buf),stdin);
  48. buf[strlen(buf)-1] = 0;
  49. //处理消息
  50. snprintf(wbuf,sizeof(wbuf),"**system**:%s",buf);
  51. //遍历链表发送消息
  52. NodePtr p = L->next;
  53. while(p!=NULL)
  54. {
  55. if(sendto(sfd,wbuf,strlen(wbuf),0,(struct sockaddr*)&p->data->cin,sizeof(p->data->cin)) < 0)
  56. {
  57. printf("发送消息失败\n");
  58. return NULL;
  59. }
  60. p = p->next;
  61. }
  62. printf("**system** [%s:%d]:chat成功\n", inet_ntoa(sin.sin_addr), ntohs(sin.sin_port));
  63. }
  64. pthread_exit(NULL);//退出线程
  65. return NULL;
  66. }
  67. //服务器接收并转发消息
  68. void *recv_msg(void *arg)//传入线程函数的结构体
  69. {
  70. //获取结构体
  71. argTypPtr argTyp = (argTypPtr)arg;
  72. NodePtr L = argTyp->L;//链表
  73. struct sockaddr_in cin = argTyp->cin;//客户端信息
  74. int sfd = argTyp->sfd;//socket
  75. char username[20] = "";//用户名
  76. char buf[1024] = "";//未处理的消息
  77. char wbuf[1024] = "";//处理后的消息
  78. //获取用户名
  79. strcpy(username,argTyp->msg.userName);
  80. //获取是什么类型的消息
  81. if(argTyp->msg.type == 'L')//登录
  82. {
  83. //判断是否有该用户
  84. NodePtr q = L->next;
  85. // while(q!=NULL)
  86. // {
  87. // if(strcmp(q->data->userName,username) == 0)
  88. // {
  89. // printf("该用户已存在\n");
  90. // break;
  91. // return NULL;
  92. // }
  93. // }
  94. //创建节点
  95. NodePtr p = create_node(username,cin);
  96. //将节点添加到链表(我采用头插)
  97. p->next = L->next;
  98. L->next = p;
  99. L->len++;//链表长度加1
  100. //处理消息
  101. snprintf(wbuf,sizeof(wbuf),"**%s已登录**",username);
  102. //遍历链表发送消息
  103. p = L->next;
  104. while(p!=NULL)
  105. {
  106. if(sendto(sfd,wbuf,strlen(wbuf),0,(struct sockaddr*)&p->data->cin,sizeof(p->data->cin)) < 0)
  107. {
  108. printf("发送消息失败\n");
  109. return NULL;
  110. }
  111. p = p->next;
  112. }
  113. printf("%s [%s:%d]:登录成功\n", username, inet_ntoa(cin.sin_addr), ntohs(cin.sin_port));
  114. }else if(argTyp->msg.type == 'C')//聊天
  115. {
  116. //处理消息
  117. snprintf(wbuf,sizeof(wbuf),"%s:%s",username,argTyp->msg.msgText);
  118. //遍历链表转发消息
  119. NodePtr p = L->next;
  120. while(p!=NULL)
  121. {
  122. if(sendto(sfd,wbuf,strlen(wbuf),0,(struct sockaddr*)&p->data->cin,sizeof(p->data->cin)) < 0)
  123. {
  124. printf("发送消息失败\n");
  125. return NULL;
  126. }
  127. p = p->next;
  128. }
  129. printf("%s [%s:%d]:chat成功\n", username, inet_ntoa(cin.sin_addr), ntohs(cin.sin_port));
  130. }else if(argTyp->msg.type == 'Q')//退出
  131. {
  132. //删除节点
  133. delete_node(L,username);
  134. //处理消息
  135. snprintf(wbuf,sizeof(wbuf),"**%s已退出**",username);
  136. //循环遍历链表发送消息
  137. NodePtr p = L->next;
  138. while(p!=NULL)
  139. {
  140. if(sendto(sfd,wbuf,strlen(wbuf),0,(struct sockaddr*)&p->data->cin,sizeof(p->data->cin)) < 0)
  141. {
  142. printf("发送消息失败\n");
  143. return NULL;
  144. }
  145. p = p->next;
  146. }
  147. printf("%s [%s:%d]:退出成功\n", username, inet_ntoa(cin.sin_addr), ntohs(cin.sin_port));
  148. L->len--;//链表长度减1
  149. }
  150. pthread_exit(NULL);//退出线程
  151. return NULL;
  152. }
  153. //删除节点
  154. void delete_node(NodePtr L, char *username)
  155. {
  156. //删除节点
  157. void delete_node(NodePtr L, char *username)
  158. {
  159. //找到要删除节点的前驱节点
  160. NodePtr p = L;
  161. while (p->next != NULL && strcmp(p->next->data->userName, username) != 0)
  162. {
  163. p = p->next;
  164. }
  165. //删除节点
  166. if (p->next != NULL)
  167. {
  168. NodePtr temp = p->next; // 保存要删除的节点
  169. p->next = p->next->next; // 删除节点
  170. free(temp->data); // 释放节点数据
  171. free(temp); // 释放节点
  172. }
  173. }
  174. }

sermain.c

  1. #include"chatser.h"
  2. int main(int argc, const char *argv[])
  3. {
  4. //创建链表
  5. NodePtr L = carete_link();
  6. //创建管道
  7. int sfd = socket(AF_INET,SOCK_DGRAM,0);
  8. if (sfd == -1)
  9. {
  10. perror("socket");
  11. return 1;
  12. }
  13. if(argc != 3)
  14. {
  15. printf("请输入ip地址和端口号\n");//argv[1]是 ip地址 argv[2]是端口号
  16. return 1;
  17. }
  18. //填充服务端地址结构体
  19. struct sockaddr_in sin;
  20. sin.sin_family = AF_INET;
  21. sin.sin_port = htons(atoi(argv[2]));
  22. sin.sin_addr.s_addr = inet_addr(argv[1]);
  23. //绑定
  24. if (bind(sfd,(struct sockaddr*)&sin,sizeof(sin)) == -1)
  25. {
  26. perror("bind error");
  27. return 1;
  28. }
  29. //获取客户端地址结构体
  30. struct sockaddr_in cin;
  31. socklen_t len = sizeof(cin);
  32. //线程实现收发数据
  33. pthread_t pid1,pid2;
  34. //填充结构体信息
  35. struct argTyp arg = {L,cin,sin,sfd};
  36. //创建发送线程
  37. if(pthread_create(&pid1,NULL,send_msg,&arg)==-1)
  38. {
  39. perror("pthread_create error");
  40. return 1;
  41. }
  42. //将线程设置为分离状态
  43. pthread_detach(pid1);
  44. while(1)
  45. {
  46. //接收客户端发送的数据
  47. if(recvfrom(sfd,&arg.msg,sizeof(arg.msg),0,(struct sockaddr*)&cin,&len)==-1)
  48. {
  49. perror("recvfrom error");
  50. return 1;
  51. }
  52. //填充结构体
  53. struct argTyp arg1 = {L,cin,sin,sfd};
  54. arg1.msg = arg.msg;
  55. //创建接收线程
  56. if(pthread_create(&pid2,NULL,recv_msg,&arg1)==-1)
  57. {
  58. perror("pthread_create error");
  59. return 1;
  60. }
  61. //将线程设置为分离状态
  62. //pthread_detach(pid1);
  63. pthread_detach(pid2);
  64. }
  65. close(sfd);
  66. return 0;
  67. }

客户端

chatcli.h

  1. #ifndef CHATCLI_H
  2. #define CHATCLI_H
  3. #include<myhead.h>
  4. //创建发送消息结构体
  5. typedef struct msgTyp
  6. {
  7. char type;//消息类型
  8. char userName[20];//用户名
  9. char msgText[1024];//消息数据
  10. }msgTyp,*msgTypPtr;
  11. //创建线程函数传输的结构体
  12. typedef struct argTyp
  13. {
  14. struct sockaddr_in sin;//服务器的信息
  15. int cfd;//套接字
  16. msgTyp msg;//客户端接收消息结构体
  17. }argTyp,*argTypPtr;
  18. //客户端发送消息
  19. void *send_msg(void *arg);
  20. //客户端接收并转发消息
  21. void *recv_msg(void *arg);
  22. // 将全局变量改为外部声明
  23. extern int flag; // 标志位判断用户是否创建
  24. extern char userName[20]; // 存放姓名
  25. extern int num; // 记录用户是否退出
  26. #endif

chatcli.c

  1. #include"chatcli.h"
  2. // 在这里定义全局变量
  3. int flag = 0;
  4. char userName[20] = "";
  5. int num = 1;
  6. //客户端发送消息
  7. void *send_msg(void *arg)
  8. {
  9. //printf("?\n");
  10. argTypPtr argTyp = (argTypPtr)arg;//获取传来的结构体
  11. struct sockaddr_in sin = argTyp->sin;
  12. int cfd = argTyp->cfd;
  13. char msgText[1024]="";//存放消息内容
  14. char buf[1024]="";//用来中转的
  15. while (num == 1)
  16. {
  17. fgets(buf,sizeof(buf),stdin);//获取终端传来的数据
  18. buf[strlen(buf)-1]=0;
  19. //表示该用户未注册
  20. if(flag ==0)
  21. {
  22. //printf("%d\n",flag);
  23. argTyp->msg.type = 'L';//发送的消息类型为登录
  24. strcpy(userName, buf);//将用户名记录下来
  25. //printf("%d\n",flag);
  26. strcpy(argTyp->msg.userName, buf);//给用户名赋值
  27. //printf("%s\n",argTyp->msg.userName);
  28. strcpy(argTyp->msg.msgText, msgText);//给消息数据赋值
  29. //printf("%d\n",flag);
  30. if (sendto(cfd, &argTyp->msg, sizeof(argTyp->msg), 0, (struct sockaddr*)&sin, sizeof(sin)) == -1)
  31. {
  32. perror("sendto error");
  33. return NULL;
  34. }
  35. flag++;//表示用户已经注册
  36. //printf("%d\n",flag);
  37. }else if (flag > 0)
  38. {
  39. if(strcmp(buf,"quit")==0)
  40. {
  41. argTyp->msg.type = 'Q';//发送的消息类型为登出
  42. strcpy(argTyp->msg.userName,userName);//给用户名赋值
  43. strcpy(argTyp->msg.msgText,msgText);//给消息数据赋值
  44. if(sendto(cfd,&argTyp->msg,sizeof(argTyp->msg),0,(struct sockaddr*)&sin,sizeof(sin))==-1)
  45. {
  46. perror("sendto error");
  47. return NULL;
  48. }
  49. num = 0;
  50. //表示退出
  51. break;
  52. }
  53. argTyp->msg.type = 'C';//发送的消息类型为聊天
  54. strcpy(argTyp->msg.userName,userName);//给用户名赋值
  55. strcpy(argTyp->msg.msgText,buf);//给消息数据赋值
  56. if(sendto(cfd,&argTyp->msg,sizeof(argTyp->msg),0,(struct sockaddr*)&sin,sizeof(sin))==-1)
  57. {
  58. perror("sendto error");
  59. return NULL;
  60. }
  61. }
  62. }
  63. pthread_exit(NULL);
  64. }
  65. //客户端接收消息
  66. void *recv_msg(void *arg)
  67. {
  68. argTypPtr argTyp = (argTypPtr)arg;//接收传来的结构体
  69. int cfd = argTyp->cfd;//获取套接字
  70. char buf[1024]="";//存放消息内容
  71. while (num == 1)
  72. {
  73. bzero(buf,sizeof(buf));//清空容器
  74. if(recvfrom(cfd,buf,sizeof(buf),0,NULL,NULL)==-1)
  75. {
  76. perror("recvfrom error");
  77. return NULL;
  78. }
  79. printf("%s\n",buf);
  80. }
  81. pthread_exit(NULL);
  82. }

climain.c

  1. #include"chatcli.h"
  2. // 声明 num 为外部变量
  3. extern int num;
  4. int main(int argc, const char *argv[])
  5. {
  6. //创建管道
  7. int cfd = socket(AF_INET,SOCK_DGRAM,0);
  8. if (cfd == -1)
  9. {
  10. perror("socket");
  11. return 1;
  12. }
  13. //判断输入的参数是否正确
  14. if(argc != 3)
  15. {
  16. printf("请输入ip地址和端口号\n");//argv[1]是 ip地址 argv[2]是端口号
  17. return 1;
  18. }
  19. //填充服务端地址结构体
  20. struct sockaddr_in sin;
  21. sin.sin_family = AF_INET;
  22. sin.sin_port = htons(atoi(argv[2]));
  23. sin.sin_addr.s_addr = inet_addr(argv[1]);
  24. //创建线程
  25. pthread_t pid1,pid2;
  26. //结构体赋值
  27. struct argTyp arg = {sin,cfd};
  28. //创建发送线程
  29. if(pthread_create(&pid1,NULL,send_msg,&arg) != 0)
  30. {
  31. printf("创建线程失败\n");
  32. return -1;
  33. }
  34. //创建接收线程
  35. if(pthread_create(&pid2,NULL,recv_msg,&arg) != 0)
  36. {
  37. printf("创建线程失败\n");
  38. return -1;
  39. }
  40. //设置线程分离
  41. pthread_detach(pid1);
  42. pthread_detach(pid2);
  43. while (num);
  44. close(cfd);
  45. return 0;
  46. }

 项目效果图

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/喵喵爱编程/article/detail/1003893
推荐阅读
相关标签
  

闽ICP备14008679号