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 }