linux之linux 并发编程(2)

多线程服务器是对多进程的服务器的改进,由于多进程服务器在创建进程时要消耗较大的系统资源,所以用线程来取代进程,这样服务处理程序可以较快的创建。据统计,创建线程与创建进程要快 10100 倍,所以又把线程称为“轻量级”进程。线程与进程不同的是:一个进程内的所有线程共享相同的全局内存、全局变量等信息,这种机制又带来了同步问题。
以下是多线程服务器模板:
参考代码:
  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_process(void *arg)
 11 函数功能:    线程函数,处理客户信息
 12 函数参数:    已连接套接字
 13 函数返回:    无
 14 ************************************************************************/
 15 void *client_process(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     
 27     printf("client closed!\n");
 28     close(connfd);    //关闭已连接套接字
 29     
 30     return     NULL;
 31 }
 32 //===============================================================
 33 // 语法格式:    void main(void)
 34 // 实现功能:    主函数,建立一个TCP并发服务器
 35 // 入口参数:    无
 36 // 出口参数:    无
 37 //===============================================================
 38 int main(int argc, char *argv[])
 39 {
 40     int sockfd = 0;                // 套接字
 41     int connfd = 0;
 42     int err_log = 0;
 43     struct sockaddr_in my_addr;    // 服务器地址结构体
 44     unsigned short port = 8080; // 监听端口
 45     pthread_t thread_id;
 46     
 47     printf("TCP Server Started at port %d!\n", port);
 48     
 49     sockfd = socket(AF_INET, SOCK_STREAM, 0);   // 创建TCP套接字
 50     if(sockfd < 0)
 51     {
 52         perror("socket error");
 53         exit(-1);
 54     }
 55     
 56     bzero(&my_addr, sizeof(my_addr));       // 初始化服务器地址
 57     my_addr.sin_family = AF_INET;
 58     my_addr.sin_port   = htons(port);
 59     my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
 60     
 61     
 62     printf("Binding server to port %d\n", port);
 63     
 64     // 绑定
 65     err_log = bind(sockfd, (struct sockaddr*)&my_addr, sizeof(my_addr));
 66     if(err_log != 0)
 67     {
 68         perror("bind");
 69         close(sockfd);        
 70         exit(-1);
 71     }
 72     
 73     // 监听,套接字变被动
 74     err_log = listen(sockfd, 10);
 75     if( err_log != 0)
 76     {
 77         perror("listen");
 78         close(sockfd);        
 79         exit(-1);
 80     }
 81     
 82     printf("Waiting client...\n");
 83     
 84     while(1)
 85     {
 86         char cli_ip[INET_ADDRSTRLEN] = "";       // 用于保存客户端IP地址
 87         struct sockaddr_in client_addr;           // 用于保存客户端地址
 88         socklen_t cliaddr_len = sizeof(client_addr);   // 必须初始化!!!
 89         
 90         //获得一个已经建立的连接    
 91         connfd = accept(sockfd, (struct sockaddr*)&client_addr, &cliaddr_len);                               
 92         if(connfd < 0)
 93         {
 94             perror("accept this time");
 95             continue;
 96         }
 97         
 98         // 打印客户端的 ip 和端口
 99         inet_ntop(AF_INET, &client_addr.sin_addr, cli_ip, INET_ADDRSTRLEN);
100         printf("----------------------------------------------\n");
101         printf("client ip=%s,port=%d\n", cli_ip,ntohs(client_addr.sin_port));
102         
103         if(connfd > 0)
104         {
105             //由于同一个进程内的所有线程共享内存和变量,因此在传递参数时需作特殊处理,值传递。
106             pthread_create(&thread_id, NULL, (void *)client_process, (void *)connfd);  //创建线程
107             pthread_detach(thread_id); // 线程分离,结束时自动回收资源
108         }
109     }
110     
111     close(sockfd);
112     
113     return 0;
114 }
注意,上面例子给线程传参有很大的局限性,最简单的一种情况,如果我们需要给线程传多个参数,这时候我们需要结构体传参,这种值传递编译都通不过,这里之所以能够这么值传递,是因为, int 长度时 4 个字节, void * 长度也是 4 个字节。
 
int connfd = accept(sockfd, (struct sockaddr*)&client_addr, &cliaddr_len);

pthread_create(&thread_id, NULL, (void *)client_process, (void *)connfd);

 

如果考虑类型匹配的话,应该是这么传参,pthread_create()最后一个参数应该传地址( &connfd ),而不是值:
int connfd = accept(sockfd, (struct sockaddr*)&client_addr, &cliaddr_len);

pthread_create(&thread_id, NULL, (void *)client_process, (void *)&connfd);

 

 
但是,如果按地址传递的话,又会有这么一个问题,假如有多个客户端要连接这个服务器,正常的情况下,一个客户端连接对应一个 connfd,相互之间独立不受影响,但是,假如多个客户端同时连接这个服务器,A 客户端的连接套接字为 connfd,服务器正在用这个 connfd 处理数据,还没有处理完,突然来了一个 B 客户端,accept()之后又生成一个 connfd, 因为是地址传递, A 客户端的连接套接字也变成 B 这个了,这样的话,服务器肯定不能再为 A 客户端服务器了,这时候,我们就需要考虑多任务的互斥或同步问题了,这里通过互斥锁来解决这个问题,确保这个connfd值被一个临时变量保存过后,才允许修改。
 
 
 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     
14     // 解锁,pthread_mutex_lock()唤醒,不阻塞
15     pthread_mutex_unlock(&mutex);
16     
17     return     NULL;
18 }

修改的完整代码如下:

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

 

posted @ 2020-03-27 17:46  Sunny_Boy_H  阅读(160)  评论(0编辑  收藏  举报