網路程式設計實驗四——利用多程序和多執行緒實現伺服器端的併發處理
一、實驗目的
1.在TCP檔案傳輸程式碼的基礎上,利用多程序實現伺服器端的併發處理。 2.利用多執行緒實現伺服器端的併發處理。
二、實驗原理
併發的面向連線伺服器演算法:
主1、建立套接字並將其繫結到所提供服務的熟知地址上。讓該套接字保持為無連線的。
主2、將該埠設定為被動模式。
主3、反覆呼叫accept以便接收來自客戶的下一個連線請求,並建立新的從執行緒或者程序來處理響應。
從1、由主執行緒傳遞來的連線請求開始。
從2、用該連線與客戶進行互動;讀取請求併發迴響應。
從3、關閉連線並退出。
三、實驗程式碼
- 利用多程序實現伺服器端:
//程式碼分析: /* 在之前實驗基礎上改進而成,socket初始化、繫結埠、進行監聽環節都沒有改變。封裝在 int passiveTCP (const char*service) 中。 在每一次呼叫accept接收來自客戶的下一個連線請求時,利用fork()建立新的程序來處理響應。 在子程序中進行檔案的選擇與傳送。 在父程序中關閉客戶端的連線,並繼續迴圈呼叫accept處理下一個連線請求。*/ //主要程式碼: int main(int argc, char **argv[]) { char filename[FILE_NAME_MAX_SIZE]; int sockfd,connfd; struct sockaddr_in clientaddr; pid_t pid; sockfd = passiveTCP(PORT); while(1) { socklen_t length=sizeof(clientaddr); //accept connfd=accept(sockfd,(struct sockaddr*)&clientaddr,&length); if(connfd<0) { perror("connect"); exit(1); } /*建立一個新的程序處理到來的連線*/ pid = fork(); /*分叉程序*/ if( pid == 0 ){ /*子程序中*/ printf("My id is %d\n",getpid()); printf("\n %d request\n",connfd); //Input the file name bzero(filename,FILE_NAME_MAX_SIZE); printf("Please input the file name you wana to send:"); scanf("%s",&filename); getchar(); //send filename imformation char buff[BUFFSIZE]; int count; bzero(buff,BUFFSIZE); strncpy(buff,filename,strlen(filename)>FILE_NAME_MAX_SIZE?FILE_NAME_MAX_SIZE:strlen(filename)); count=send(connfd,buff,BUFFSIZE,0); if(count<0) { perror("Send file information"); exit(1); } //read file FILE *fd=fopen(filename,"rb"); if(fd==NULL) { printf("File :%s not found!\n",filename); } else { bzero(buff,BUFFSIZE); int file_block_length=0; while((file_block_length=fread(buff,sizeof(char),BUFFSIZE,fd))>0) { //printf("file_block_length:%d\n",file_block_length); if(send(connfd,buff,file_block_length,0)<0) { perror("Send"); exit(1); } bzero(buff,BUFFSIZE); } fclose(fd); printf("Transfer file finished !\n"); } close(sockfd);//關閉伺服器socket break; } else{ close(connfd); /*在父程序中關閉客戶端的連線*/ } } return 0; }
-
利用多執行緒實現伺服器端:
//程式碼分析: /* socket的初始化方面還是利用的之前封裝的passiveTCP()函式。 實現過程與多程序基本一樣,只是這裡對每個連線的請求是建立的執行緒。 同時對執行緒的建立處理過程進行了封裝,只是將客戶端socket的描述符傳了過去。 */ //主函式部分: int main(int argc, char **argv[]) { char filename[FILE_NAME_MAX_SIZE]; int sockfd,connfd; struct sockaddr_in clientaddr; pid_t pid; sockfd = passiveTCP(PORT); while(1) { socklen_t length=sizeof(clientaddr); //accept connfd=accept(sockfd,(struct sockaddr*)&clientaddr,&length); if(connfd<0) { perror("connect"); exit(1); } /*建立一個新的執行緒處理到來的連線*/ thread_create(connfd); } close(sockfd); return 0; } //建立執行緒: void thread_create(int connfd) { pthread_t thread; //printf("111111111\n"); if(pthread_create(&thread, NULL, my_thread, connfd) != 0) { //建立執行緒 printf("Creating thread has failed!\n"); } } //執行緒中的檔案傳輸部分: void *my_thread(int connfd) { char filename[FILE_NAME_MAX_SIZE]; printf("\n %d request\n",connfd); //Input the file name bzero(filename,FILE_NAME_MAX_SIZE); printf("Please input the file name you wana to send:"); scanf("%s",&filename); getchar(); //send filename imformation char buff[BUFFSIZE]; int count; bzero(buff,BUFFSIZE); strncpy(buff,filename,strlen(filename)>FILE_NAME_MAX_SIZE?FILE_NAME_MAX_SIZE:strlen(filename)); count=send(connfd,buff,BUFFSIZE,0); if(count<0) { perror("Send file information"); exit(1); } //read file FILE *fd=fopen(filename,"rb"); if(fd==NULL) { printf("File :%s not found!\n",filename); } else { bzero(buff,BUFFSIZE); int file_block_length=0; while((file_block_length=fread(buff,sizeof(char),BUFFSIZE,fd))>0) { //printf("file_block_length:%d\n",file_block_length); if(send(connfd,buff,file_block_length,0)<0) { perror("Send"); exit(1); } bzero(buff,BUFFSIZE); } fclose(fd); printf("Transfer file finished !\n"); } close(connfd);//關閉客戶端的連線 pthread_exit(NULL); }
-
客戶端實現:
int main() { SOCKET sock; //客戶端程序建立套接字 char buf[BUFFER_SIZE]; //buf陣列存放客戶端傳送的訊息 int inputLen; //用於輸入字元自增變數 while(1) { printf("Socket\\Client>"); inputLen=0; memset(buf,0,sizeof(buf)); while((buf[inputLen++]=getchar())!='\n') //輸入以回車鍵為結束標識 {;} if(buf[0]=='e' && buf[1]=='x' && buf[2]=='i' && buf[3]=='t') { printf("The End.\n"); break; } sock=connectTCP(IP,PORT); //send(sock,buf,BUFFER_SIZE,0); //向伺服器傳送資料 recvTCP(sock); closesocket(sock); //關閉套接字 WSACleanup(); //終止對Winsock DLL的使用,並釋放資源,以備下一次使用 } return 0; }
四、實驗結果
1.多程序
本次實驗測試,首先傳輸一個較大的檔案,在這個大檔案的傳輸過程中,利用併發再對一個新的客戶端連線進行一個小檔案的傳輸。(多程序和多執行緒顯示效果基本一致,只是程式碼實現不同)
伺服器端:
可以看到第一個程式連線上來,進行了檔案的傳輸,在傳輸過程中,對第二個連線進行了併發的檔案傳輸。
傳送檔案:
客戶端:可以看到,大檔案傳輸過程中,客戶端2發起請求並實現的小檔案的傳輸。
接收結果:
2.多執行緒
伺服器端:編譯的時候注意Linux下命令格式gcc fileserver_thread.c -o 1 -lpthread
傳送檔案:
客戶端:
接收結果:
五、實驗問題
本次實驗遇到的問題:
1.在使用codeblocks執行客戶端時,一開始我不知道怎麼開啟兩個視窗執行客戶端,百度了一下發現,解決辦法如下:Setting-Enviroment Settings-General Settings,去掉“allow only one running instance”, 去掉“ use an already running instanct”關閉codeblocks 後重新啟動,即可啟動多個例項,具體如下圖:
2.需要注意在linux下編譯多執行緒檔案時,命令要改成gcc fileserver_thread.c -o 1 -lpthread ,而不是之前的gcc -o 命令。
3.對close呼叫:在fork之後,父程序子程序都可以使用開啟的套接字。因此,主程序的執行緒要對新連線呼叫close關閉從程序的套接字,從程序的執行緒要對主程序呼叫close關閉主程序中的套接字。但是他們並不消失,將繼續使用套接字。
六、實驗感悟
伺服器端的併發處理有兩種實現方式,分別為多執行緒方式和多程序方式。兩者相比,多程序方式主要有兩個優點:更高的效率和共享的儲存器;電視由於執行緒間共享儲存器和程序狀態,一個執行緒的動作可能會對同一個程序內的其他執行緒產生影響,另外,多執行緒使伺服器缺乏健壯性。因此具體選用哪一種併發處理的方式還要根據具體情況來進行討論。