https://blog.csdn.net/lianghe_work/article/details/46504243
tcp多線程并發服務器
多線程服務器是對多進程服務器的改進,由于多進程服務器在創建進程時要消耗較大的系統資源,所以用線程來取代進程,這樣服務處理程序可以較快的創建。據統計,創建線程與創建進程要快 10100 倍,所以又把線程稱為“輕量級”進程。線程與進程不同的是:一個進程內的所有線程共享相同的全局內存、全局變量等信息,這種機制又帶來了同步問題。tcp多線程并發服務器框架:
我們在使用多線程并發服務器時,直接使用以上框架,我們僅僅修改client_fun()里面的內容。代碼示例:
/************************************************************************ 函數名稱: void *client_fun(void *arg) 函數功能: 線程函數,處理客戶信息 函數參數: 已連接套接字 函數返回: 無 ************************************************************************/ void *client_fun(void *arg) { int recv_len = 0; char recv_buf[1024] = ""; // 接收緩沖區 int connfd = (int)arg; // 傳過來的已連接套接字 // 接收數據 while((recv_len = recv(connfd, recv_buf, sizeof(recv_buf), 0)) > 0) { printf("recv_buf: %s\n", recv_buf); // 打印數據 send(connfd, recv_buf, recv_len, 0); // 給客戶端回數據 } printf("client closed!\n"); close(connfd); //關閉已連接套接字 return NULL; } //=============================================================== // 語法格式: void main(void) // 實現功能: 主函數,建立一個TCP并發服務器 // 入口參數: 無 // 出口參數: 無 //=============================================================== int main(int argc, char *argv[]) { int sockfd = 0; // 套接字 int connfd = 0; int err_log = 0; struct sockaddr_in my_addr; // 服務器地址結構體 unsigned short port = 8080; // 監聽端口 pthread_t thread_id; printf("TCP Server Started at port %d!\n", port); sockfd = socket(AF_INET, SOCK_STREAM, 0); // 創建TCP套接字 if(sockfd < 0) { perror("socket error"); exit(-1); } bzero(&my_addr, sizeof(my_addr)); // 初始化服務器地址 my_addr.sin_family = AF_INET; my_addr.sin_port = htons(port); my_addr.sin_addr.s_addr = htonl(INADDR_ANY); printf("Binding server to port %d\n", port); // 綁定 err_log = bind(sockfd, (struct sockaddr*)&my_addr, sizeof(my_addr)); if(err_log != 0) { perror("bind"); close(sockfd); exit(-1); } // 監聽,套接字變被動 err_log = listen(sockfd, 10); if( err_log != 0) { perror("listen"); close(sockfd); exit(-1); } printf("Waiting client...\n"); while(1) { char cli_ip[INET_ADDRSTRLEN] = ""; // 用于保存客戶端IP地址 struct sockaddr_in client_addr; // 用于保存客戶端地址 socklen_t cliaddr_len = sizeof(client_addr); // 必須初始化!!! //獲得一個已經建立的連接 connfd = accept(sockfd, (struct sockaddr*)&client_addr, &cliaddr_len); if(connfd < 0) { perror("accept this time"); continue; } // 打印客戶端的 ip 和端口 inet_ntop(AF_INET, &client_addr.sin_addr, cli_ip, INET_ADDRSTRLEN); printf("----------------------------------------------\n"); printf("client ip=%s,port=%d\n", cli_ip,ntohs(client_addr.sin_port)); if(connfd > 0) { //由于同一個進程內的所有線程共享內存和變量,因此在傳遞參數時需作特殊處理,值傳遞。 pthread_create(&thread_id, NULL, (void *)client_fun, (void *)connfd); //創建線程 pthread_detach(thread_id); // 線程分離,結束時自動回收資源 } } close(sockfd); return 0; }
運行結果:注意:
1.上面pthread_create()函數的最后一個參數是void *類型,為啥可以傳值connfd?
while(1) { int connfd = accept(sockfd, (struct sockaddr*)&client_addr, &cliaddr_len); pthread_create(&thread_id, NULL, (void *)client_fun, (void *)connfd); pthread_detach(thread_id); }
因為void *是4個字節,而connfd為int類型也是4個字節,故可以傳值。如果connfd為char、short,上面傳值就會出錯
2.上面pthread_create()函數的最后一個參數是可以傳地址嗎?可以,但會對服務器造成不可預知的問題
while(1) { int connfd = accept(sockfd, (struct sockaddr*)&client_addr, &cliaddr_len); pthread_create(&thread_id, NULL, (void *)client_fun, (void *)&connfd); pthread_detach(thread_id); }
原因:假如有多個客戶端要連接這個服務器,正常的情況下,一個客戶端連接對應一個 connfd,相互之間獨立不受影響,但是,假如多個客戶端同時連接這個服務器,A 客戶端的連接套接字為 connfd,服務器正在用這個 connfd 處理數據,還沒有處理完,突然來了一個 B 客戶端,accept()之后又生成一個 connfd, 因為是地址傳遞, A 客戶端的連接套接字也變成 B 這個了,這樣的話,服務器肯定不能再為 A 客戶端服務器了
2.如果我們想將多個參數傳給線程函數,我們首先考慮到就是結構體參數,而這時傳值是行不通的,只能傳遞地址。
這時候,我們就需要考慮多任務的互斥或同步問題了,這里通過互斥鎖來解決這個問題,確保這個結構體參數值被一個臨時變量保存過后,才允許修改。
pthread_mutex_t mutex; // 定義互斥鎖,全局變量 pthread_mutex_init(&mutex, NULL); // 初始化互斥鎖,互斥鎖默認是打開的 // 上鎖,在沒有解鎖之前,pthread_mutex_lock()會阻塞 pthread_mutex_lock(&mutex); int connfd = accept(sockfd, (struct sockaddr*)&client_addr, &cliaddr_len); //給回調函數傳的參數,&connfd,地址傳遞 pthread_create(&thread_id, NULL, (void *)client_process, (void *)&connfd); //創建線程 // 線程回調函數 void *client_process(void *arg) { int connfd = *(int *)arg; // 傳過來的已連接套接字 // 解鎖,pthread_mutex_lock()喚醒,不阻塞 pthread_mutex_unlock(&mutex); return NULL; }
示例代碼:
pthread_mutex_t mutex; // 定義互斥鎖,全局變量 /************************************************************************ 函數名稱: void *client_process(void *arg) 函數功能: 線程函數,處理客戶信息 函數參數: 已連接套接字 函數返回: 無 ************************************************************************/ void *client_process(void *arg) { int recv_len = 0; char recv_buf[1024] = ""; // 接收緩沖區 int connfd = *(int *)arg; // 傳過來的已連接套接字 // 解鎖,pthread_mutex_lock()喚醒,不阻塞 pthread_mutex_unlock(&mutex); // 接收數據 while((recv_len = recv(connfd, recv_buf, sizeof(recv_buf), 0)) > 0) { printf("recv_buf: %s\n", recv_buf); // 打印數據 send(connfd, recv_buf, recv_len, 0); // 給客戶端回數據 } printf("client closed!\n"); close(connfd); //關閉已連接套接字 return NULL; } //=============================================================== // 語法格式: void main(void) // 實現功能: 主函數,建立一個TCP并發服務器 // 入口參數: 無 // 出口參數: 無 //=============================================================== int main(int argc, char *argv[]) { int sockfd = 0; // 套接字 int connfd = 0; int err_log = 0; struct sockaddr_in my_addr; // 服務器地址結構體 unsigned short port = 8080; // 監聽端口 pthread_t thread_id; pthread_mutex_init(&mutex, NULL); // 初始化互斥鎖,互斥鎖默認是打開的 printf("TCP Server Started at port %d!\n", port); sockfd = socket(AF_INET, SOCK_STREAM, 0); // 創建TCP套接字 if(sockfd < 0) { perror("socket error"); exit(-1); } bzero(&my_addr, sizeof(my_addr)); // 初始化服務器地址 my_addr.sin_family = AF_INET; my_addr.sin_port = htons(port); my_addr.sin_addr.s_addr = htonl(INADDR_ANY); printf("Binding server to port %d\n", port); // 綁定 err_log = bind(sockfd, (struct sockaddr*)&my_addr, sizeof(my_addr)); if(err_log != 0) { perror("bind"); close(sockfd); exit(-1); } // 監聽,套接字變被動 err_log = listen(sockfd, 10); if( err_log != 0) { perror("listen"); close(sockfd); exit(-1); } printf("Waiting client...\n"); while(1) { char cli_ip[INET_ADDRSTRLEN] = ""; // 用于保存客戶端IP地址 struct sockaddr_in client_addr; // 用于保存客戶端地址 socklen_t cliaddr_len = sizeof(client_addr); // 必須初始化!!! // 上鎖,在沒有解鎖之前,pthread_mutex_lock()會阻塞 pthread_mutex_lock(&mutex); //獲得一個已經建立的連接 connfd = accept(sockfd, (struct sockaddr*)&client_addr, &cliaddr_len); if(connfd < 0) { perror("accept this time"); continue; } // 打印客戶端的 ip 和端口 inet_ntop(AF_INET, &client_addr.sin_addr, cli_ip, INET_ADDRSTRLEN); printf("----------------------------------------------\n"); printf("client ip=%s,port=%d\n", cli_ip,ntohs(client_addr.sin_port)); if(connfd > 0) { //給回調函數傳的參數,&connfd,地址傳遞 pthread_create(&thread_id, NULL, (void *)client_process, (void *)&connfd); //創建線程 pthread_detach(thread_id); // 線程分離,結束時自動回收資源 } } close(sockfd); return 0; }
運行結果:
注意:這種用互斥鎖對服務器的運行效率有致命的影響