开源项目:TCP模拟实现QQ群聊(云服务器版)

一时失言乱红尘 2022-05-21 03:49 332阅读 0赞

基于多线程TCP协议模拟实现群聊(云服务器版)

1.思路

①使用链表保存每一个accept返回的socket文件描述符,在服务端给客户端send数据的时候,循环遍历链表,给每个socket文件描述符都发送,从而即可实现简单的群聊
②再将自己的云服务器的外网和内网IP找到,在服务器代码中绑定内网IP,在客户端中绑定外网IP,然后再固定一个开放的端口号(自己设置,一般设置1024之后,如果不自己设置开放的端口号,所有的端口号都可以使用,但是这样是不安全的)

2.云服务器如何开放端口号(以腾讯云为例)

①登录并打开云主机页面,红线标记处就是外网IP和内网IP
这里写图片描述
②选择“安全组”,点击新建
这里写图片描述
③有三种选项,模板选择图示中的即可,名称随便改
这里写图片描述
④点击新建好的安全组,进入图示界面,点击添加规则
这里写图片描述
⑤按照图示填写即可,端口号最好大于1024
这里写图片描述

3.碰到的问题

有可能在这里有你写群聊的过程中遇到的困惑的小问题,看看能不能帮助你解决

①为什么一个客户端发送消息后,另一个客户端不能立刻收到,而是得自己发送一次后才能看到上一个人发送的消息?
答:看看是不是客户端中的send和recv设置成阻塞式的了。
②为什么将send和recv都设置成非阻塞的后,还是出现上一个问题?
答:如果是用read从标准输入读取键入内容,如read(0,buf,sizeof(buf));,那么就使用fcntl函数将0号文件,即标准输入,更改成非阻塞的。
③为什么会出现发送多条消息后服务器就崩了的情况,只有一个客户端连接的时候还好,有两个或以上的客户端就会这样?
答:根据我某个叫小刚的朋友的经历,出现这个问题就是链表没写好,请仔细检查链表的插入和删除,以及空间的释放。

3.代码

代码分成三部分:
protocol.h包含了各种自定义协议(即一些结构体和宏)
client.c是客户端代码
pthread_server.c是服务器代码
注:所有代码都是在通过xshell链接云服务器编写,即代码存储在云服务器中,读者可以自行将服务器写成守护进程,这样只要别人运行你的客户端程序,即可实现群聊

头文件

  1. #ifndef __PROTOCOL_H__
  2. #define __PROTOCOL_H__
  3. #include<stdio.h>
  4. #include<stdlib.h>
  5. #include<fcntl.h>
  6. #include<string.h>
  7. #include<unistd.h>
  8. #include<sys/socket.h>
  9. #include<netinet/in.h>
  10. #include<arpa/inet.h>
  11. #include<pthread.h>
  12. #define NAME 10 //用户名长度
  13. #define MSG 1024 //发送信息大小
  14. #define IP "172.21.0.4" //内网IP
  15. #define PIP "192.144.188.26" //外网IP
  16. #define PORT 2020 //固定的开放的端口号
  17. typedef struct SocketListNode
  18. {
  19. int _sockfd;
  20. struct SocketListNode* _prev;
  21. struct SocketListNode* _next;
  22. }SLN;//带头双向链表,存储所有客户端套接文件描述符
  23. typedef struct User
  24. {
  25. char _name[NAME];//用户名
  26. char _msg[MSG];//用户发送的信息
  27. }user;
  28. #endif //__PROTOCOL_H___

客户端

  1. #include "protocol.h"
  2. int ClientDo(char* argv[])//客户端工作前的准备工作
  3. {
  4. int fd=socket(AF_INET,SOCK_STREAM,0);//创建套接字
  5. if(fd<0)
  6. {
  7. perror("socket");
  8. exit(2);
  9. }
  10. //填充服务器的套接字信息
  11. struct sockaddr_in server;
  12. server.sin_family=AF_INET;
  13. server.sin_port=htons(PORT);
  14. server.sin_addr.s_addr=inet_addr(PIP);
  15. if(connect(fd,(struct sockaddr*)&server,sizeof(server))<0)//建立连接
  16. {
  17. perror("connect");
  18. exit(3);
  19. }
  20. return fd;
  21. }
  22. void working(int sockfd,char* name)//客户端工作ing
  23. {
  24. if(fcntl(0,F_SETFL,FNDELAY)<0)//将0号文件设置成非阻塞的
  25. {
  26. perror("fcntl");
  27. exit(4);
  28. }
  29. user u;
  30. char buf[NAME+MSG]={
  31. 0};//存储序列化后的数据
  32. while(1)
  33. {
  34. strncpy(buf,name,NAME);
  35. ssize_t r=read(0,u._msg,MSG);//从标准输入读取键入内容
  36. if(r>0)
  37. {
  38. u._msg[r]=0;
  39. fflush(stdout);
  40. strcpy(buf+NAME,u._msg);
  41. send(sockfd,buf,NAME+MSG,MSG_DONTWAIT);//非阻塞式发送
  42. }
  43. ssize_t rec=recv(sockfd,buf,NAME+MSG,MSG_DONTWAIT);//非阻塞式接收
  44. if(rec>0)
  45. {
  46. strncpy(u._name,buf,NAME);
  47. strncpy(u._msg,buf+NAME,MSG);
  48. printf("%s :>%s",u._name,u._msg);
  49. }
  50. }
  51. close(sockfd);
  52. }
  53. int main(int argc,char* argv[])
  54. {
  55. if(argc!=2)
  56. {
  57. printf("parameter is too less\n");
  58. return 1;
  59. }
  60. int sockfd=ClientDo(argv);
  61. working(sockfd,argv[1]);
  62. return 0;
  63. }

服务端

  1. #include "protocol.h"
  2. SLN* BuyNode(int sockfd)//创建节点
  3. {
  4. SLN* node=(SLN*)malloc(sizeof(SLN));
  5. if(node==NULL)
  6. {
  7. perror("malloc");
  8. exit(1);
  9. }
  10. node->_sockfd=sockfd;
  11. node->_prev=NULL;
  12. node->_next=NULL;
  13. return node;
  14. }
  15. SLN* phead=NULL;//全局的头节点,方便操作
  16. int ServerDo()//服务器工作前的准备工作
  17. {
  18. int fd=socket(AF_INET,SOCK_STREAM,0);//创建套接字
  19. if(fd<0)
  20. {
  21. perror("socket");
  22. exit(2);
  23. }
  24. //填充自己的套接字信息
  25. struct sockaddr_in server;
  26. server.sin_family=AF_INET;
  27. server.sin_port=htons(PORT);
  28. server.sin_addr.s_addr=inet_addr(IP);
  29. if(bind(fd,(struct sockaddr*)&server,sizeof(server))<0)//绑定端口号
  30. {
  31. perror("bind");
  32. exit(3);
  33. }
  34. if(listen(fd,5)<0)//将套接字设置为监听状态
  35. {
  36. perror("listen");
  37. exit(4);
  38. }
  39. return fd;
  40. }
  41. void func(void* arg)//线程函数
  42. {
  43. SLN* node=(SLN*)arg;
  44. SLN* cur=NULL;
  45. char buf[NAME+MSG]={
  46. 0};
  47. while(1)
  48. {
  49. ssize_t rec=recv(node->_sockfd,buf,NAME+MSG,0);//阻塞式接收
  50. if(rec<=0)
  51. {
  52. printf("sockfd closed\n");
  53. break;
  54. }
  55. cur=phead->_next;
  56. while(cur!=NULL)//循环给每个套接字都发送
  57. {
  58. send(cur->_sockfd,buf,NAME+MSG,MSG_DONTWAIT);
  59. cur=cur->_next;
  60. }
  61. }
  62. //删除节点,释放空间
  63. node->_prev->_next=node->_next;
  64. if(node->_next!=NULL)
  65. {
  66. node->_next->_prev=node->_prev;
  67. }
  68. node->_prev=NULL;
  69. node->_next=NULL;
  70. free(node);
  71. close(node->_sockfd);
  72. }
  73. void working(int sockfd)//服务器工作ing
  74. {
  75. int new_sockfd=0;
  76. while(1)
  77. {
  78. new_sockfd=accept(sockfd,NULL,NULL);//接收链接
  79. if(new_sockfd<0)
  80. {
  81. perror("accept");
  82. continue;
  83. }
  84. //头插节点,保存客户端套接字
  85. SLN* node=BuyNode(new_sockfd);
  86. node->_next=phead->_next;
  87. node->_prev=phead;
  88. if(phead->_next!=NULL)
  89. {
  90. phead->_next->_prev=node;
  91. }
  92. phead->_next=node;
  93. pthread_t thread=0;
  94. pthread_create(&thread,NULL,(void*)func,(void*)node);
  95. pthread_detach(thread);//将线程设置成分离态,让其“自生自灭”
  96. }
  97. close(sockfd);
  98. }
  99. int main()
  100. {
  101. phead=BuyNode(0);//初始化链表头节点
  102. int sockfd=ServerDo();
  103. working(sockfd);
  104. return 0;
  105. }

运行结果图

这里写图片描述

发表评论

表情:
评论列表 (有 0 条评论,332人围观)

还没有评论,来说两句吧...

相关阅读