亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 學院 > 操作系統 > 正文

客戶-服務器程序設計方法

2024-06-28 13:21:12
字體:
來源:轉載
供稿:網友
客戶-服務器程序設計方法客戶-服務器程序設計方法

《unix網絡編程》第一卷中將客戶服務器程序設計方法講得透徹,這篇文章將其中編碼的細節略去,通過偽代碼的形式展現,主要介紹各種方法的思想;

示例是一個經典的TCP回射程序: 客戶端發起連接請求,連接后發送一串數據;收到服務端的數據后輸出到終端; 服務端收到客戶端的數據后原樣回寫給客戶端;

客戶端偽代碼:

sockfd = socket(AF_INET,SOCK_STREAM,0);//與服務端建立連接connect(sockfd);//連接建立后從終端讀入數據并發送到服務端;//從服務端收到數據后回寫到終端while(fgets(sendline,MAXLINE,fileHandler)!= NULL){    writen(sockfd,sendline,strlen(sendline));    if(readline(sockfd,recvline,MAXLINE) == 0){        cout << "recive over!";    }    fputs(recvline,stdout);}

下面介紹服務端程序處理多個客戶請求的開發范式;

多進程處理

對于多個客戶請求,服務器端采用fork的方式創建新進程來處理;

處理流程:

  1. 主進程綁定ip端口后,使用accept()等待新客戶的請求;
  2. 每一個新的用戶請求到來,都創建一個新的子進程來處理具體的客戶請求;
  3. 子進程處理完用戶請求,結束本進程;

服務端偽代碼:

listenFd = socket(AF_INET,SOCK_STREAM,0);bind(listenFd,addR);listen(listenFD);while(true){    //服務器端在這里阻塞等待新客戶連接    connfd = accept(listenfd);     if( fork() ==0){//子進程        close(listenfd);        while(n=read(connfd,buf,MAXLINE)>0){            writen(connfd,buf);        }    }    close(connfd);}

這種方法開發簡單,但對操作系統而言,進程是一種昂貴的資源,對于每個新客戶請求都使用一個進程處理,開銷較大; 對于客戶請求數不多的應用適用這種方法;

預先分配進程池,accept無上鎖保護

上一種方法中,每來一個客戶都創建一個進程處理請求,完畢后再釋放; 不間斷的創建和結束進程浪費系統資源; 使用進程池預先分配進程,通過進程復用,減少進程重復創建帶來的系統消耗和時間等待;

優點:消除新客戶請求到達來創建進程的開銷; 缺點:需要預先估算客戶請求的多少(確定進程池的大小)

源自Berkeley內核的系統,有以下特性: 派生的所有子進程各自調用accep()監聽同一個套接字,在沒有用戶請求時都進入睡眠; 當有新客戶請求到來時,所有的客戶都被喚醒;內核從中選擇一個進程處理請求,剩余的進程再次轉入睡眠(回到進程池);

利用這個特性可以由操作系統來控制進程的分配; 內核調度算法會把各個連接請求均勻的分散到各個進程中;

處理流程:

  1. 主進程預先分配進程池,所有子進程阻塞在accept()調用上;
  2. 新用戶請求到來,操作系統喚醒所有的阻塞在accpet上的進程,從其中選擇一個建立連接;
  3. 被選中的子進程處理用戶請求,其它子進程回到睡眠;
  4. 子進程處理完畢,再次阻塞在accept上;

服務端偽代碼:

listenFd = socket(AF_INET,SOCK_STREAM,0);bind(listenFd,addR);listen(listenFD);for(int i = 0;i< children;i++){    if(fork() == 0){//子進程        while(true){            //所有子進程監聽同一個套接字,等待用戶請求            int connfd = accept(listenfd);            close(listenfd);            //連接建立后處理用戶請求,完畢后關閉連接            while(n=read(connfd,buf,MAXLINE)>0){                writen(connfd,buf);            }            close(connfd);        }    }}

如何從進程池中取出進程? 所有的進程都通過accept()阻塞等待,等連接請求到來后,由內核從所有等待的進程中選擇一個進程處理;

處理完的進程,如何放回到池子中? 子進程處理完客戶請求后,通過無限循環,再次阻塞在accpet()上等待新的連接請求;

注意: 多個進程accept()阻塞會產生“驚群問題”:盡管只有一個進程將獲得連接,但是所有的進程都被喚醒;這種每次有一個連接準備好卻喚醒太多進程的做法會導致性能受損;

預先分配進程池,accept上鎖(文件鎖、線程鎖)

上述不上鎖的實現存在移植性的問題(只能在源自Berkeley的內核系統上)和驚群問題, 更為通用的做法是對accept上鎖;即避免讓多個進程阻塞在accpet調用上,而是都阻塞在獲取鎖的函數中;

服務端偽代碼:

listenFd = socket(AF_INET,SOCK_STREAM,0);bind(listenFd,addR);listen(listenFD);for(int i = 0;i< children;i++){    if(fork() == 0){        while(true){            my_lock_wait();//獲取鎖            int connfd = accept(listenfd);            my_lock_release();//釋放鎖            close(listenfd);            while(n=read(connfd,buf,MAXLINE)>0){                writen(connfd,buf);            }            close(connfd);        }    }}

上鎖可以使用文件上鎖,線程上鎖;

  • 文件上鎖的方式可移植到所有的操作系統,但其涉及到文件系統操作,可能比較耗時;
  • 線程上鎖的方式不僅適用不同線程之間的上鎖,也適用于不同進程間的上鎖;

關于上鎖的編碼細節詳見《網絡編程》第30章;

預先分配進程池,傳遞描述符;

與上面的每個進程各自accept接收監聽請求不同,這個方法是在父進程中統一接收accpet()用戶請求,在連接建立后,將連接描述符傳遞給子進程;

處理流程:

  1. 主進程阻塞在accpet上等待用戶請求,所有子進程不斷輪詢探查是否有可用的描述符;
  2. 有新用戶請求到來,主進程accpet建立連接后,從進程池中取出一個進程,通過字節流管道將連接描述符傳遞給子進程;
  3. 子進程收到連接描述符,處理用戶請求,處理完成后向父進程發送一個字節的內容(無實際意義),告知父進程我任務已完成;
  4. 父進程收到子進程的單字節數據,將子進程放回到進程池;

服務端偽代碼:

listenFd = socket(AF_INET,SOCK_STREAM,0);bind(listenFd,addR);listen(listenFD);//預先建立子進程池for(int i = 0;i< children;i++){    //使用Unix域套接字創建一個字節流管道,用來傳遞描述符    socketpair(AF_LOCAL,SOCK_STREAM,0,sockfd);    if(fork() == 0){//預先創建子進程        //子進程字節流到父進程        dup2(sockfd[1],STDERR_FILENO);        close(listenfd);        while(true){            //收到連接描述符            if(read_fd(STDERR_FILENO,&connfd) ==0){;                 continue;            }            while(n=read(connfd,buf,MAXLINE)>0){ //處理用戶請求                writen(connfd,buf);            }            close(connfd);            //通知父進程處理完畢,本進程可以回到進程池            write(STDERR_FILENO,"",1);        }    }}while(true){    //監聽listen套接字描述符和所有子進程的描述符    select(maxfd+1,&rset,NULL,NULL,NULL);    if(FD_ISSET(listenfd,&rset){//有客戶連接請求        connfd = accept(listenfd);//接收客戶連接        //從進程池中找到一個空閑的子進程        for(int i = 0 ;i < children;i++){            if(child_status[i] == 0)                break;        }        child_status[i] = 1;//子進程從進程池中分配出去        write_fd(childfd[i],connfd);//將描述符傳遞到子進程中        close(connfd);    }    //檢查子進程的描述符,有數據,表明已經子進程請求已處理完成,回收到進程池    for(int i = 0 ;i < children;i++){        if(FD_ISSET(childfd[i],&rset)){            if(read(childfd[i])>0){                child_status[i] = 0;            }        }    }}
多線程處理

為每個用戶創建一個線程,這種方法比為每個用戶創建一個進程要快出許多倍;

處理流程:

  1. 主線程阻塞在accpet上等待用請求;
  2. 有新用戶請求時,主線程建立連接,然后創建一個新的線程,將連接描述符傳遞過去;
  3. 子線程處理用戶請求,完畢后線程結束;

服務端偽代碼:

listenFd = socket(AF_INET,SOCK_STREAM,0);bind(listenFd,addR);listen(listenFD);while(true){    connfd = accept(listenfd);        //連接建立后,創建新線程處理具體的用戶請求    pthread_create(&tid,NULL,&do_function,(void*)connfd);    close(connfd);}--------------------//具體的用戶請求處理函數(子線程主體)void * do_function(void * connfd){    pthread_detach(pthread_self());    while(n=read(connfd,buf,MAXLINE)>0){        writen(connfd,buf);    close((int)connfd);}
預先創建線程池,每個線程各自accept

處理流程:

  1. 主線程預先創建線程池,第一個創建的子線程獲取到鎖,阻塞在accept()上,其它子線程阻塞在線程鎖上;
  2. 用戶請求到來,第一個子線程建立連接后釋放鎖,然后處理用戶請求;完成后進入線程池,等待獲取鎖;
  3. 第一個子線程釋放鎖之后,線程池中等待的線程有一個會獲取到鎖,阻塞在accept()等待用戶請求;
listenFd = socket(AF_INET,SOCK_STREAM,0);bind(listenFd,addR);listen(listenFD);//預先創建線程池,將監聽描述符傳給每個新創建的線程for(int i = 0 ;i <threadnum;i++){    pthread_create(&tid[i],NULL,&thread_function,(void*)connfd);}--------------------//具體的用戶請求處理//通過鎖保證任何時刻只有一個線程阻塞在accept上等待新用戶的到來;其它的線程都//在等鎖;void * thread_function(void * connfd){    while(true){        pthread_mutex_lock(&mlock); // 線程上鎖        connfd = accept(listenfd);        pthread_mutex_unlock(&mlock);//線程解鎖        while(n=read(connfd,buf,MAXLINE)>0){            writen(connfd,buf);        close(connfd);    }}

使用源自Berkeley的內核的Unix系統時,我們不必為調用accept而上鎖, 去掉上鎖的兩個步驟后,我們發現沒有上鎖的用戶時間減少(因為上鎖是在用戶空間中執行的線程函數完成的),而系統時間卻增加很多(每一個accept到達,所有的線程都變喚醒,引發內核的驚群問題,這個是在線程內核空間中完成的); 而我們的線程都需要互斥,讓內核執行派遣還不讓自己通過上鎖來得快;

這里沒有必要使用文件上鎖,因為單個進程中的多個線程,總是可以通過線程互斥鎖來達到同樣目的;(文件鎖更慢)

預先創建線程池,主線程accept后傳遞描述符

處理流程:

  1. 主線程預先創建線程池,線程池中所有的線程都通過調用pthread_cond_wait()而處于睡眠狀態(由于有鎖的保證,是依次進入睡眠,而不會發生同時調用pthread_cond_wait引發競爭)
  2. 主線程阻塞在acppet調用上等待用戶請求;
  3. 用戶請求到來,主線程accpet建立建立,將連接句柄放入約定位置后,發送pthread_cond_signal激活一個等待該條件的線程;
  4. 線程激活后從約定位置取出連接句柄處理用戶請求;完畢后再次進入睡眠(回到線程池);

激活條件等待的方式有兩種:pthread_cond_signal()激活一個等待該條件的線程,存在多個等待線程時按入隊順序激活其中一個;而pthread_cond_broadcast()則激活所有等待線程。

注:一般應用中條件變量需要和互斥鎖一同使用; 在調用pthread_cond_wait()前必須由本線程加鎖(pthread_mutex_lock()),而在更新條件等待隊列以前,mutex保持鎖定狀態,并在線程掛起進入等待前解鎖。在條件滿足從而離開pthread_cond_wait()之前,mutex將被重新加鎖,以與進入pthread_cond_wait()前的加鎖動作對應。

服務端偽代碼:

listenFd = socket(AF_INET,SOCK_STREAM,0);bind(listenFd,addR);listen(listenFD);for(int i = 0 ;i <threadnum;i++){    pthread_create(&tid[i],NULL,&thread_function,(void*)connfd);}while(true){    connfd = accept(listenfd);    pthread_mutex_lock(&mlock); // 線程上鎖    childfd[iput] = connfd;//將描述符的句柄放到數組中傳給獲取到鎖的線程;    if(++iput == MAX_THREAD_NUM)        iput= 0;    if(iput == iget)        err_quit("thread num not enuough!");    pthread_cond_signal(&clifd_cond);//發信號,喚醒一個睡眠線程(輪詢喚醒其中的一個)    pthread_mutex_unlock(&mlock);//線程解鎖}--------------------void * thread_function(void * connfd){    while(true){        pthread_mutex_lock(&mlock); // 線程上鎖        //當無沒有收到連接句柄時,睡眠在條件變量上,并釋放mlock鎖        //滿足條件被喚醒后,重新加mlock鎖        while(iget == iput)            pthread_cond_wait(&clifd_cond,&mlock);        connfd = childfd[iget];        if(++iget == MAX_THREAD_NUM)            iget = 0;        pthread_mutex_unlock(&mlock);//線程解鎖        //處理用戶請求        while(n=read(connfd,buf,MAXLINE)>0){            writen(connfd,buf);        close(connfd);    }}

測試表明這個版本的服務器要慢于每個線程各自accpet的版本,原因在于這個版本同時需要互斥鎖和條件變量,而上一個版本只需要互斥鎖;

線程描述符的傳遞和進程描述符的傳遞的區別? 在一個進程中打開的描述符對該進程中的所有線程都是可見的,引用計數也就是1; 所有線程訪問這個描述符都只需要通過一個描述符的值(整型)訪問; 而進程間的描述符傳遞,傳遞的是描述符的引用;(好比一個文件被2個進程打開,相應的這個文件的描述符引用計數增加2);

總結
  • 當系統負載較輕時,每個用戶請求現場派生一個子進程為之服務的傳統并發服務器模型就足夠了;
  • 相比傳統的每個客戶fork一次的方式,預先創建一個子進程池或線程池能夠把進程控制cpu時間降低10倍以上;當然,程序會相應復雜一些,需要監視子進程個數,隨著客戶用戶數的動態變化而增加或減少進程池;
  • 讓所有子進程或線程自行調用accept通常比讓父進程或主線程獨自調用accpet并發描述符傳遞給子進程或線程要簡單和快速;
  • 使用線程通常要快于使用進程;
參考資料

《unix網絡編程》第一卷 套接字聯網API

Posted by: 大CC | 05APR,2015 博客:blog.me115.com [訂閱] 微博:新浪微博


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
成人97在线观看视频| 亚洲第五色综合网| 亚洲桃花岛网站| 久久精品国产亚洲| 日韩精品中文字幕视频在线| 懂色av影视一区二区三区| 亚洲日韩中文字幕| 国产精品久久久久久久久久尿| 国产99久久精品一区二区 夜夜躁日日躁| 中文字幕亚洲欧美一区二区三区| 国产精品va在线播放我和闺蜜| 国产va免费精品高清在线观看| 日韩一区二区三区xxxx| 日韩av电影在线免费播放| 国产精品av在线播放| 亚洲自拍小视频免费观看| 亚洲精品之草原avav久久| 91精品啪aⅴ在线观看国产| 久久黄色av网站| 精品久久久久久久久久久久| 97精品伊人久久久大香线蕉| 亚洲精品av在线| 成人免费xxxxx在线观看| 国产亚洲成精品久久| 黄色一区二区三区| 亚洲福利在线播放| 成人免费高清完整版在线观看| 国产亚洲精品久久久久动| 日韩电影中文字幕在线| 亚洲色图激情小说| 精品国内亚洲在观看18黄| 久久精品免费电影| 欧美精品少妇videofree| 精品女厕一区二区三区| 国产一区红桃视频| 一区二区中文字幕| 国产福利精品av综合导导航| 91精品一区二区| 亚洲第一精品夜夜躁人人躁| 久久久久久久久久久网站| 91国内产香蕉| 成人h片在线播放免费网站| 久久久久国产精品一区| 992tv成人免费影院| 亚洲欧美999| 日韩精品中文在线观看| 九色成人免费视频| 国产精品久久久久久久久男| 欧美一区二区三区精品电影| 久久91精品国产91久久跳| 97精品一区二区三区| 亚洲第一中文字幕| 色yeye香蕉凹凸一区二区av| 成人网页在线免费观看| 国产精品成人aaaaa网站| 26uuu另类亚洲欧美日本一| 久久综合色影院| 久久亚洲精品一区| 欧美高清在线视频观看不卡| 日韩最新中文字幕电影免费看| 亚洲人成欧美中文字幕| 国产精品香蕉av| 狠狠色狠狠色综合日日五| 亚洲一区www| 欧美极品欧美精品欧美视频| 亚洲品质视频自拍网| 亚洲国产精品久久久| 国产精品海角社区在线观看| 久久免费视频在线| 亚洲图中文字幕| 中文字幕在线亚洲| 91精品中文在线| 亚洲精美色品网站| 亚洲免费视频网站| 国产精品99蜜臀久久不卡二区| 一区二区亚洲精品国产| 亚洲精品成人免费| 国产精品爽爽爽爽爽爽在线观看| 欧美日韩激情视频8区| 性欧美在线看片a免费观看| 精品成人69xx.xyz| 久热精品在线视频| 韩国精品美女www爽爽爽视频| 亚洲精品国偷自产在线99热| 日韩福利伦理影院免费| 久久久国产成人精品| 国产精品十八以下禁看| 亚洲天堂免费视频| 日韩欧美在线视频免费观看| 亚洲欧洲日韩国产| 色噜噜狠狠狠综合曰曰曰| 欧美在线视频导航| 福利一区视频在线观看| 美女扒开尿口让男人操亚洲视频网站| 国产成人午夜视频网址| 中文字幕亚洲自拍| 成人欧美一区二区三区在线| 欧美性视频在线| 中文精品99久久国产香蕉| 国产成人一区二区三区电影| 国产成人aa精品一区在线播放| 国产精品中文字幕在线观看| 在线精品高清中文字幕| 95av在线视频| 精品视频中文字幕| 九九热精品视频国产| 亚洲成人激情在线观看| 最近中文字幕日韩精品| 日韩亚洲国产中文字幕| 亚洲天堂av在线免费观看| 日韩欧美精品网站| 国产成人自拍视频在线观看| 欧美午夜激情视频| 亚洲一区二区三区在线视频| 欧美尺度大的性做爰视频| 亚洲欧美综合精品久久成人| 精品伊人久久97| 国产视频亚洲视频| 久久99热精品这里久久精品| 国产在线a不卡| 欧美视频不卡中文| 亚洲成色777777女色窝| 久久久久久亚洲精品不卡| 亚洲欧洲日产国码av系列天堂| 亚洲欧美999| 国产精品人成电影在线观看| 国语自产精品视频在线看一大j8| 欧美国产极速在线| 久久人人看视频| 国产精品久久久久久久久久ktv| 欧美精品久久久久久久| 日韩av在线网| 国产精品成人v| 日韩在线观看免费高清完整版| 日韩有码在线播放| 亚洲高清一区二| 欧美刺激性大交免费视频| 色中色综合影院手机版在线观看| 亚洲人精选亚洲人成在线| 亚洲视频在线观看免费| 中文字幕久热精品视频在线| 91亚洲精品在线观看| 九色91av视频| 红桃av永久久久| 国产精品视频不卡| 91精品国产自产在线观看永久| 蜜月aⅴ免费一区二区三区| 欧美激情视频在线观看| 亚洲欧美综合另类中字| 精品国内亚洲在观看18黄| 亚洲免费电影一区| 国产精品久久一| 色一区av在线| 国产狼人综合免费视频| 97精品伊人久久久大香线蕉| 欧美国产乱视频| 国产精品视频导航| 欧美超级乱淫片喷水| 热re99久久精品国产66热| 欧美剧在线观看| 91色精品视频在线| 欧美制服第一页| 日韩电影在线观看免费| 亚洲欧美www|