Linux网络编程——tcp并发服务器(多线程)

https://blog.csdn.net/lianghe_work/article/details/46504243

tcp多线程并发服务器

多线程服务器是对多进程服务器的改进,由于多进程服务器在创建进程时要消耗较大的系统资源,所以用线程来取代进程,这样服务处理程序可以较快的创建。据统计,创建线程与创建进程要快 10100 倍,所以又把线程称为“轻量级”进程。线程与进程不同的是:一个进程内的所有线程共享相同的全局内存、全局变量等信息,这种机制又带来了同步问题。

tcp多线程并发服务器框架:



我们在使用多线程并发服务器时,直接使用以上框架,我们仅仅修改client_fun()里面的内容。
代码示例:
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <unistd.h>
  5. #include <sys/socket.h>
  6. #include <netinet/in.h>
  7. #include <arpa/inet.h>
  8. #include <pthread.h>
  9. /************************************************************************
  10. 函数名称: void *client_fun(void *arg)
  11. 函数功能: 线程函数,处理客户信息
  12. 函数参数: 已连接套接字
  13. 函数返回: 无
  14. ************************************************************************/
  15. void *client_fun( void *arg)
  16. {
  17. int recv_len = 0;
  18. char recv_buf[ 1024] = ""; // 接收缓冲区
  19. int connfd = ( int)arg; // 传过来的已连接套接字
  20. // 接收数据
  21. while((recv_len = recv(connfd, recv_buf, sizeof(recv_buf), 0)) > 0)
  22. {
  23. printf( "recv_buf: %s\n", recv_buf); // 打印数据
  24. send(connfd, recv_buf, recv_len, 0); // 给客户端回数据
  25. }
  26. printf( "client closed!\n");
  27. close(connfd); //关闭已连接套接字
  28. return NULL;
  29. }
  30. //===============================================================
  31. // 语法格式: void main(void)
  32. // 实现功能: 主函数,建立一个TCP并发服务器
  33. // 入口参数: 无
  34. // 出口参数: 无
  35. //===============================================================
  36. int main(int argc, char *argv[])
  37. {
  38. int sockfd = 0; // 套接字
  39. int connfd = 0;
  40. int err_log = 0;
  41. struct sockaddr_in my_addr; // 服务器地址结构体
  42. unsigned short port = 8080; // 监听端口
  43. pthread_t thread_id;
  44. printf( "TCP Server Started at port %d!\n", port);
  45. sockfd = socket(AF_INET, SOCK_STREAM, 0); // 创建TCP套接字
  46. if(sockfd < 0)
  47. {
  48. perror( "socket error");
  49. exit( -1);
  50. }
  51. bzero(&my_addr, sizeof(my_addr)); // 初始化服务器地址
  52. my_addr.sin_family = AF_INET;
  53. my_addr.sin_port = htons(port);
  54. my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
  55. printf( "Binding server to port %d\n", port);
  56. // 绑定
  57. err_log = bind(sockfd, ( struct sockaddr*)&my_addr, sizeof(my_addr));
  58. if(err_log != 0)
  59. {
  60. perror( "bind");
  61. close(sockfd);
  62. exit( -1);
  63. }
  64. // 监听,套接字变被动
  65. err_log = listen(sockfd, 10);
  66. if( err_log != 0)
  67. {
  68. perror( "listen");
  69. close(sockfd);
  70. exit( -1);
  71. }
  72. printf( "Waiting client...\n");
  73. while( 1)
  74. {
  75. char cli_ip[INET_ADDRSTRLEN] = ""; // 用于保存客户端IP地址
  76. struct sockaddr_in client_addr; // 用于保存客户端地址
  77. socklen_t cliaddr_len = sizeof(client_addr); // 必须初始化!!!
  78. //获得一个已经建立的连接
  79. connfd = accept(sockfd, ( struct sockaddr*)&client_addr, &cliaddr_len);
  80. if(connfd < 0)
  81. {
  82. perror( "accept this time");
  83. continue;
  84. }
  85. // 打印客户端的 ip 和端口
  86. inet_ntop(AF_INET, &client_addr.sin_addr, cli_ip, INET_ADDRSTRLEN);
  87. printf( "----------------------------------------------\n");
  88. printf( "client ip=%s,port=%d\n", cli_ip,ntohs(client_addr.sin_port));
  89. if(connfd > 0)
  90. {
  91. //由于同一个进程内的所有线程共享内存和变量,因此在传递参数时需作特殊处理,值传递。
  92. pthread_create(&thread_id, NULL, ( void *)client_fun, ( void *)connfd); //创建线程
  93. pthread_detach(thread_id); // 线程分离,结束时自动回收资源
  94. }
  95. }
  96. close(sockfd);
  97. return 0;
  98. }

运行结果:


注意
1.上面pthread_create()函数的最后一个参数是void *类型,为啥可以传值connfd
  1. while( 1)
  2. {
  3. int connfd = accept(sockfd, ( struct sockaddr*)&client_addr, &cliaddr_len);
  4. pthread_create(&thread_id, NULL, ( void *)client_fun, ( void *)connfd);
  5. pthread_detach(thread_id);
  6. }

因为void *是4个字节,而connfd为int类型也是4个字节,故可以传值。如果connfd为char、short,上面传值就会出错


2.上面pthread_create()函数的最后一个参数是可以传地址吗?可以,但会对服务器造成不可预知的问题

  1. while( 1)
  2. {
  3. int connfd = accept(sockfd, ( struct sockaddr*)&client_addr, &cliaddr_len);
  4. pthread_create(&thread_id, NULL, ( void *)client_fun, ( void *)&connfd);
  5. pthread_detach(thread_id);
  6. }

原因:假如有多个客户端要连接这个服务器,正常的情况下,一个客户端连接对应一个 connfd,相互之间独立不受影响,但是,假如多个客户端同时连接这个服务器,A 客户端的连接套接字为 connfd,服务器正在用这个 connfd 处理数据,还没有处理完,突然来了一个 B 客户端,accept()之后又生成一个 connfd, 因为是地址传递, A 客户端的连接套接字也变成 B 这个了,这样的话,服务器肯定不能再为 A 客户端服务器了


2.如果我们想将多个参数传给线程函数,我们首先考虑到就是结构体参数,而这时传值是行不通的,只能传递地址

这时候,我们就需要考虑多任务的互斥或同步问题了,这里通过互斥锁来解决这个问题,确保这个结构体参数值被一个临时变量保存过后,才允许修改。

  1. #include <pthread.h>
  2. pthread_mutex_t mutex; // 定义互斥锁,全局变量
  3. pthread_mutex_init(&mutex, NULL); // 初始化互斥锁,互斥锁默认是打开的
  4. // 上锁,在没有解锁之前,pthread_mutex_lock()会阻塞
  5. pthread_mutex_lock(&mutex);
  6. int connfd = accept(sockfd, ( struct sockaddr*)&client_addr, &cliaddr_len);
  7. //给回调函数传的参数,&connfd,地址传递
  8. pthread_create(&thread_id, NULL, ( void *)client_process, ( void *)&connfd); //创建线程
  9. // 线程回调函数
  10. void *client_process( void *arg)
  11. {
  12. int connfd = *( int *)arg; // 传过来的已连接套接字
  13. // 解锁,pthread_mutex_lock()唤醒,不阻塞
  14. pthread_mutex_unlock(&mutex);
  15. return NULL;
  16. }

示例代码:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <unistd.h>
  5. #include <sys/socket.h>
  6. #include <netinet/in.h>
  7. #include <arpa/inet.h>
  8. #include <pthread.h>
  9. pthread_mutex_t mutex; // 定义互斥锁,全局变量
  10. /************************************************************************
  11. 函数名称: void *client_process(void *arg)
  12. 函数功能: 线程函数,处理客户信息
  13. 函数参数: 已连接套接字
  14. 函数返回: 无
  15. ************************************************************************/
  16. void *client_process( void *arg)
  17. {
  18. int recv_len = 0;
  19. char recv_buf[ 1024] = ""; // 接收缓冲区
  20. int connfd = *( int *)arg; // 传过来的已连接套接字
  21. // 解锁,pthread_mutex_lock()唤醒,不阻塞
  22. pthread_mutex_unlock(&mutex);
  23. // 接收数据
  24. while((recv_len = recv(connfd, recv_buf, sizeof(recv_buf), 0)) > 0)
  25. {
  26. printf( "recv_buf: %s\n", recv_buf); // 打印数据
  27. send(connfd, recv_buf, recv_len, 0); // 给客户端回数据
  28. }
  29. printf( "client closed!\n");
  30. close(connfd); //关闭已连接套接字
  31. return NULL;
  32. }
  33. //===============================================================
  34. // 语法格式: void main(void)
  35. // 实现功能: 主函数,建立一个TCP并发服务器
  36. // 入口参数: 无
  37. // 出口参数: 无
  38. //===============================================================
  39. int main(int argc, char *argv[])
  40. {
  41. int sockfd = 0; // 套接字
  42. int connfd = 0;
  43. int err_log = 0;
  44. struct sockaddr_in my_addr; // 服务器地址结构体
  45. unsigned short port = 8080; // 监听端口
  46. pthread_t thread_id;
  47. pthread_mutex_init(&mutex, NULL); // 初始化互斥锁,互斥锁默认是打开的
  48. printf( "TCP Server Started at port %d!\n", port);
  49. sockfd = socket(AF_INET, SOCK_STREAM, 0); // 创建TCP套接字
  50. if(sockfd < 0)
  51. {
  52. perror( "socket error");
  53. exit( -1);
  54. }
  55. bzero(&my_addr, sizeof(my_addr)); // 初始化服务器地址
  56. my_addr.sin_family = AF_INET;
  57. my_addr.sin_port = htons(port);
  58. my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
  59. printf( "Binding server to port %d\n", port);
  60. // 绑定
  61. err_log = bind(sockfd, ( struct sockaddr*)&my_addr, sizeof(my_addr));
  62. if(err_log != 0)
  63. {
  64. perror( "bind");
  65. close(sockfd);
  66. exit( -1);
  67. }
  68. // 监听,套接字变被动
  69. err_log = listen(sockfd, 10);
  70. if( err_log != 0)
  71. {
  72. perror( "listen");
  73. close(sockfd);
  74. exit( -1);
  75. }
  76. printf( "Waiting client...\n");
  77. while( 1)
  78. {
  79. char cli_ip[INET_ADDRSTRLEN] = ""; // 用于保存客户端IP地址
  80. struct sockaddr_in client_addr; // 用于保存客户端地址
  81. socklen_t cliaddr_len = sizeof(client_addr); // 必须初始化!!!
  82. // 上锁,在没有解锁之前,pthread_mutex_lock()会阻塞
  83. pthread_mutex_lock(&mutex);
  84. //获得一个已经建立的连接
  85. connfd = accept(sockfd, ( struct sockaddr*)&client_addr, &cliaddr_len);
  86. if(connfd < 0)
  87. {
  88. perror( "accept this time");
  89. continue;
  90. }
  91. // 打印客户端的 ip 和端口
  92. inet_ntop(AF_INET, &client_addr.sin_addr, cli_ip, INET_ADDRSTRLEN);
  93. printf( "----------------------------------------------\n");
  94. printf( "client ip=%s,port=%d\n", cli_ip,ntohs(client_addr.sin_port));
  95. if(connfd > 0)
  96. {
  97. //给回调函数传的参数,&connfd,地址传递
  98. pthread_create(&thread_id, NULL, ( void *)client_process, ( void *)&connfd); //创建线程
  99. pthread_detach(thread_id); // 线程分离,结束时自动回收资源
  100. }
  101. }
  102. close(sockfd);
  103. return 0;
  104. }


运行结果:


注意:这种用互斥锁对服务器的运行效率有致命的影响

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值