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

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

运行结果:


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

示例代码:

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


运行结果:


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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值