socket伺服器程式設計的多程序,多執行緒實現
socket伺服器程式設計:
顧名思義就是使用socket套接字來編寫伺服器程式的過程。不熟悉socket程式設計的小夥伴可以看我之前的文章,但是當時所實現的功能伺服器同時只能和一個客戶端進行互動,效率太低,利用多程序或者多執行緒方式來實現伺服器可以做到同時和多個客戶端進行互動。提高伺服器的效能。
多程序的實現方式:
我們可以在程式中使用fork系統呼叫來建立一個子程序,父程序負責接收客戶端的連線,並從監聽佇列中取出已經連線成功的一個客戶端套接字,傳給子程序,子程序來負責處理和唯一的一個客戶端的互動。當我們將上面這個過程實現在一個while迴圈中時,父程序迴圈接收,迴圈建立子程序,每個子程序都可以處理一個客戶端的請求,這樣我們就可以同時和多個客戶端進行互動。
原理:這裡我們使用了fork系統呼叫的一個性質------->父子程序會共享fork之前建立的檔案描述符。子程序創建出來之後,會將父程序的PCB結構複製一份,檔案描述符也就會被複制過來,而父子程序的兩個檔案描述符,指向了同一個struct_file的結構體,在這個結構體中,有一個count屬性,專門用來記錄指向該結構體的檔案描述符個數。
當count的值是0時,該結構體被釋放,子程序創建出來後該屬性的值從1變成2(父程序+子程序),但是我們要將父程序中的檔案描述符關閉,因為我們指定子程序來處理和客戶端的互動,當完成互動過程的時候,該結構體理應被釋放,也就是count應該由1減為0,如果我們不關閉父程序中的檔案描述符,完成互動時,只會將count減一,並不會釋放該結構體。導致每有一個客戶端連線伺服器,父程序就會多出一個未被釋放的檔案描述符,當我們有20個客戶端建立過連線後其他客戶端將無法再和該伺服器進行連線(一個程序最多同時開啟20個檔案,一個系統最多同時開啟64個struct_file結構體
還要注意的是由於我們父程序是一直迴圈接收客戶端的連線,而子程序只負責和一個客戶端的互動,當一次互動完成後,一個子程序也就該結束掉了,當父程序未結束時,已經結束的子程序就會變成僵死程序,如果我們不採用手段來手動清除這些僵死程序,就會造成資源的極大浪費。這裡我們使用訊號響應機制來處理僵死子程序。
多執行緒的實現方式:
和多程序方式相似,多執行緒方式就是使用創建出來的函式執行緒進行和一個特定客戶端的互動,而主執行緒負責接收所有客戶端的連線。使用多執行緒的方式不能在主執行緒中關閉檔案描述符,因為在同一個程序中,除了棧區的資料其他靜態或者全域性的資料都是共享的,也就是說,函式執行緒和主執行緒其實使用的是同一個檔案描述符,如果主執行緒將其關閉,函式執行緒也無法使用
需要注意的是:在將檔案描述符傳遞給函式執行緒時,不能使用地址傳遞的方式,因為主執行緒和函式執行緒使用的是同一個檔案描述符,而主執行緒需要不斷的接收連線,有可能我們主執行緒將檔案描述符的地址傳遞給函式執行緒時剛好接受了一個新的連線,而此時函式執行緒中從該地址讀取到的檔案描述符就是這個新連線到的值,上一個客戶端的請求就會被遺漏,這時必須注意的一點。
多程序,多執行緒的異同:
- 程式設計角度:多執行緒比多程序要簡單一些,不用做太多的控制
- 佔用資源角度:多執行緒比多程序節省了更多的資源
- 主執行緒(父程序),函式執行緒(子程序)之間的切換:執行緒切換的效率高,程序相對較慢
- 資料共享方面:執行緒除棧區資料之外,靜態或者全域性的資料全部共享,程序幾乎不共享資料
- 連線數量:程序1024個(一個系統最多同時維護1024個PCB結構),執行緒20個(一個程序最多同時開啟20個檔案描述符)
完整原始碼:
- 多程序:
#include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<string.h> #include<assert.h> #include<sys/types.h> #include<sys/socket.h> #include<sys/un.h> #include<netinet/in.h> #include<arpa/inet.h> #include<pthread.h> #include<signal.h> /*子程序用來處理互動過程的函式*/ void func(int c) { while(1) { char buff[128] = {0}; int err = recv(c, buff, 127, 0); if(err <= 0) { printf("%dclient disconnect!\n",c); break; } printf("%d: %s\n",c,buff); char sendbuff[128] = {0}; printf("please input:"); fflush(stdout); fgets(sendbuff, 127, stdin); err = send(c, sendbuff, strlen(sendbuff)-1, 0); if(err == -1) { printf("error\n"); } } close(c); } /*避免僵死子程序,訊號響應函式*/ void Zombie(int sign) { wait(NULL); } int main() { signal(SIGCHLD, Zombie);/*改變訊號的響應方式*/ int sockfd = socket(PF_INET, SOCK_STREAM, 0); assert(sockfd != -1); struct sockaddr_in ser,cli; ser.sin_family = AF_INET; ser.sin_port = htons(6500); ser.sin_addr.s_addr = inet_addr("127.0.0.1"); int err = bind(sockfd, (struct sockaddr*)&ser, sizeof(ser)); assert(err != -1); err = listen(sockfd, 5); assert(err != -1); while(1) { int len = sizeof(cli); int c = accept(sockfd, (struct sockaddr*)&cli, &len); if(c == -1) { printf("accept error\n"); continue; } int n = fork(); if(n == 0) { func(c); break; } else { close(c);//父程序中關閉檔案描述符 } } close(sockfd); return 0; }
- 多執行緒:
#include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<string.h> #include<assert.h> #include<sys/types.h> #include<sys/socket.h> #include<sys/un.h> #include<netinet/in.h> #include<arpa/inet.h> #include<pthread.h> /*函式執行緒*/ void *func(void* arg) { int c = (int)arg; while(1) { char buff[128] = {0}; int err = recv(c, buff, 127, 0); if(err <= 0) { printf("%dclient disconnect!\n",c); break; } printf("%d: %s\n",c,buff); err = send(c, "ok", 2, 0); if(err == -1) { printf("error\n"); } } close(c); } int main() { int sockfd = socket(PF_INET, SOCK_STREAM, 0); assert(sockfd != -1); struct sockaddr_in ser,cli; ser.sin_family = AF_INET; ser.sin_port = htons(6500); ser.sin_addr.s_addr = inet_addr("127.0.0.1"); int err = bind(sockfd, (struct sockaddr*)&ser, sizeof(ser)); assert(err != -1); err = listen(sockfd, 5); assert(err != -1); while(1) { int len = sizeof(cli); int c = accept(sockfd, (struct sockaddr*)&cli, &len); if(c == -1) { printf("accept error\n"); continue; } pthread_t id; int res = pthread_create(&id, NULL, func, (void*)c);/*採用值傳遞的方式傳遞檔案描述符*/ assert(res == 0); } close(sockfd); return 0; }