標籤:多線程
tcp多線程並發伺服器
多線程伺服器是對多進程伺服器的改進,由於多進程伺服器在建立進程時要消耗較大的系統資源,所以用線程來取代進程,這樣服務處理常式可以較快的建立。據統計,建立線程與建立進程要快 10100 倍,所以又把線程稱為“輕量級”進程。線程與進程不同的是:一個進程內的所有線程共用相同的全域記憶體、全域變數等資訊,這種機制又帶來了同步問題。
tcp多線程並發伺服器架構:
我們在使用多線程並發伺服器時,直接使用以上架構,我們僅僅修改client_fun()裡面的內容。程式碼範例:
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include <pthread.h>/************************************************************************函數名稱: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.如果我們想將多個參數傳給線程函數,我們首先考慮到就是結構體參數,而這時傳值是行不通的,只能傳遞地址。
這時候,我們就需要考慮多任務的互斥或同步問題了,這裡通過互斥鎖來解決這個問題,確保這個結構體參數值被一個臨時變數儲存過後,才允許修改。
#include <pthread.h> 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; }
範例程式碼:
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include <pthread.h>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;}
運行結果:
注意:這種用互斥鎖對伺服器的運行效率有致命的影響
代碼下載:
Linux網路編程——tcp並發伺服器(多線程)