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. }


运行结果:


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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/383690.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

计算机网络【三】物理层数据通信

物理层传输媒介 导向传输媒体&#xff0c;比如光纤和铜线 双绞线&#xff08;屏蔽双绞线STP 五屏蔽双绞线UTP&#xff09;电线扭曲在一起可以降低互相之间的电磁干扰 同轴电缆 (50欧姆的基带同轴电缆&#xff0c;75欧姆的宽带同轴电缆) 10M和100M网络只使用了四根线&#xf…

02_算法分析

02_算法分析 0.1 算法的时间复杂度分析0.1.1 函数渐近增长概念&#xff1a;输入规模n>2时&#xff0c;算法A1的渐近增长小于算法B1 的渐近增长随着输入规模的增大&#xff0c;算法的常数操作可以忽略不计测试二&#xff1a;随着输入规模的增大&#xff0c;与最高次项相乘的常…

Linux网络编程——I/O复用之select详解

https://blog.csdn.net/lianghe_work/article/details/46506143一、I/O复用概述I/O复用概念&#xff1a;解决进程或线程阻塞到某个 I/O 系统调用而出现的技术&#xff0c;使进程不阻塞于某个特定的 I/O 系统调I/O复用使用的场合&#xff1a;1.当客户处理多个描述符&#xff08;…

Linux网络编程——tcp并发服务器(I/O复用之select)

https://blog.csdn.net/lianghe_work/article/details/46519633与多线程、多进程相比&#xff0c;I/O复用最大的优势是系统开销小&#xff0c;系统不需要建立新的进程或者线程&#xff0c;也不必维护这些线程和进程。代码示例&#xff1a;#include <stdio.h> #include &l…

操作系统【二】死锁问题以及处理方法

死锁的概念 死锁&#xff1a;在并发环境下&#xff0c;个进程因为竞争资源而造成的一种互相等待对方手里的资源&#xff0c;导致各进程都阻塞&#xff0c;无法向前推进的现象。 区别&#xff1a; 饥饿&#xff1a;由于长期得不到想要的资源进程无法向前推进的现象。死循环&a…

Linux网络编程——I/O复用之poll函数

https://blog.csdn.net/lianghe_work/article/details/46534029一、回顾前面的selectselect优点&#xff1a;目前几乎在所有的平台上支持&#xff0c;其良好跨平台支持也是它的一个优点select缺点&#xff1a;1.每次调用 select()&#xff0c;都需要把 fd 集合从用户态拷贝到内…

操作系统【一】进程同步和信号量

基本概念 进程异步性特征&#xff1a;各并发执行的进程以各自独立的&#xff0c;不可预知的速度向前推进。 进程同步又称作直接制约关系&#xff0c;他是指为完成某种任务而建立的两个或者多个进程&#xff0c;这些进程因为需要在某些位置上协调他们的工作顺序而产生的制约关…

计算机网络【四】数据链路层基本概念+点到点通信(PPP协议)

数据链路层基本概念 路由器是网络层设备 数据链路层&#xff1a;数据管道&#xff0c;传输的是数据包加上发送地址&#xff0c;接收地址&#xff0c;校验的数据帧 数据链路层的信道类型&#xff1a; 点到点信道&#xff1a;使用一对一的点到点通信方式&#xff08;两个设备…

Linux网络编程——tcp并发服务器(poll实现)

https://blog.csdn.net/lianghe_work/article/details/46535859想详细彻底地了解poll或看懂下面的代码请参考《Linux网络编程——I/O复用之poll函数》 代码&#xff1a;#include <string.h>#include <stdio.h>#include <stdlib.h>#include <unistd.h>#…

Linux网络编程——I/O复用函数之epoll

https://blog.csdn.net/lianghe_work/article/details/46544567一、epoll概述epoll 是在 2.6 内核中提出的&#xff0c;是之前的 select() 和 poll() 的增强版本。相对于 select() 和 poll() 来说&#xff0c;epoll 更加灵活&#xff0c;没有描述符限制。epoll 使用一个文件描述…

操作系统【三】内存管理基础+连续内存分配

内存的基础知识 内存分为按字节编址&#xff08;8位&#xff09;和字编制&#xff08;不同计算机不一样&#xff0c;64位计算机就是64位&#xff0c;即8个字节&#xff09; 相对地址逻辑地址 绝对地址物理地址 从逻辑地址到物理地址的转换由装入解决。 装入的三种方式 绝对…

MSG_PEEK标志

https://blog.csdn.net/aspnet_lyc/article/details/28937229 MSG_PEEK标志可以用来读取套接字接收队列中可读的数据&#xff0c;一些情况会用到它&#xff0c;比如为了避免不阻塞而先检查套接字接收队列中可读的数据长度&#xff0c;再采取相应操作。当然&#xff0c;不阻塞也…

C++的单例模式与线程安全单例模式(懒汉/饿汉)

https://www.cnblogs.com/qiaoconglovelife/p/5851163.html1 教科书里的单例模式我们都很清楚一个简单的单例模式该怎样去实现&#xff1a;构造函数声明为private或protect防止被外部函数实例化&#xff0c;内部保存一个private static的类指针保存唯一的实例&#xff0c;实例的…

计算矩阵的逆和行列式的值(高斯消元+LU分解)

计算矩阵的逆 选主元的高斯消元法 朴素的高斯消元法是将矩阵A和单位矩阵放在一起&#xff0c;通过行操作&#xff08;或者列操作&#xff09;将A变为单位矩阵&#xff0c;这个时候单位矩阵就是矩阵A的逆矩阵。从上到下将A变为上三角矩阵的复杂度为O(n3n^3n3)&#xff0c;再从下…

Linux网络编程——tcp并发服务器(epoll实现)

https://blog.csdn.net/lianghe_work/article/details/46551871通过epoll实现tcp并发回执服务器&#xff08;客户端给服务器发啥&#xff0c;服务器就给客户端回啥&#xff09; 代码如下&#xff1a;#include <string.h>#include <stdio.h>#include <stdlib.h&g…

证明AVL树的上界和下界

对于n个节点的AVL树&#xff0c;其高度最低的时候肯定为叶子节点只在最后一层和倒数第二层的时候。即对于2k−1<n≦2k1−12^k-1< n\leqq 2^{k1}-12k−1<n≦2k1−1的时候下界都为kkk。因此下界为h┌log2(n1)┐−1h\ulcorner log_2(n1)\urcorner-1h┌log2​(n1)┐−1 对…

浅谈dup和dup2的用法

https://blog.csdn.net/u012058778/article/details/78705536一、dup和dup2函数 这两个函数都可以来复制一个现有的文件描述符&#xff0c;他们的声明如下&#xff1a;#include <unistd.h>int dup(int fd);int dup2(int fd, int fd 2); 123 关于dup函数&#xff0c;当我…

C++ cin 实现循环读入

习惯了使用while(~scanf("%d",x)){}来实现循环读入&#xff0c;但是有时候使用泛型编程的时候就必须使用C中的cin&#xff0c;但是当我想要实现循环读入的时候却发现有些困难。 我们可以看一下下面这个简单的例子&#xff1a; #include <iostream>using name…

BFPTR算法详解+实现+复杂度证明

BFPTR算法是由Blum、Floyed、Pratt、Tarjan、Rivest这五位牛人一起提出来的&#xff0c;其特点在于可以以最坏复杂度为O(n)O(n)O(n)地求解top−ktop-ktop−k问题。所谓top−ktop-ktop−k问题就是从一个序列中求解其第k大的问题。 top−ktop-ktop−k问题有许多解决方法&#xff…