1. 程式人生 > >網路程式設計實驗四——利用多程序和多執行緒實現伺服器端的併發處理

網路程式設計實驗四——利用多程序和多執行緒實現伺服器端的併發處理

一、實驗目的

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關閉主程序中的套接字。但是他們並不消失,將繼續使用套接字。

六、實驗感悟

        伺服器端的併發處理有兩種實現方式,分別為多執行緒方式和多程序方式。兩者相比,多程序方式主要有兩個優點:更高的效率和共享的儲存器;電視由於執行緒間共享儲存器和程序狀態,一個執行緒的動作可能會對同一個程序內的其他執行緒產生影響,另外,多執行緒使伺服器缺乏健壯性。因此具體選用哪一種併發處理的方式還要根據具體情況來進行討論。