UNIX網絡編程——經常使用服務器模型總結

下面有9種服務器模型分別是:linux

  • 迭代服務器。
  • 併發服務器,爲每一個客戶fork一個進程。
  • 預先派生子進程,每一個子進程都調用accept,accept無上鎖保護。
  • 預先派生子進程,以文件鎖的方式保護accept。 
  • 預先派生子進程,以線程互斥鎖上鎖的方式保護accept。
  • 預先派生子進程,由父進程向子進程傳遞套接口描述字。 
  • 併發服務器,爲每一個客戶請求建立一個線程。
  • 預先建立線程,以互斥鎖上鎖方式保護accept。 
  • 預先建立線程,由主線程調用accept,並把每一個客戶鏈接傳遞給線程池中某個可用線程。

一、迭代服務器nginx

       典型代碼:
[cpp]  view plain  copy
 
  1. socket  
  2. bind  
  3. listen  
  4. for(;;)  
  5. {  
  6. connfd = accept(listenfd, (SA*)&cliaddr, &clilen);  
  7. process_connection(connfd);  
  8. close(connfd);  
  9. }  
       優勢:主要是編程簡單。
       缺點:處理完一個鏈接以後才能處理下一個鏈接,無併發可言,應用不多。
 
2.併發服務器,爲每一個客戶fork一個進程
       典型代碼:
[cpp]  view plain  copy
 
  1. init_address(server_addr)  
  2. listenfd = socket(AF_INET,SOCKET_STREAM,0);  
  3. bind(listenfd, (SA*)server_addr, sizeof(serveraddr));  
  4. listen(listenfd,BACKLOG);  
  5. for(;;)  
  6. {  
  7. connfd = accept(listenfd, (SA*)&cliaddr, &clilen);  
  8. if(connfd < 0)  
  9. {  
  10. if(EINTR == errno)  
  11. continue;  
  12. else  
  13. //error  
  14. }  
  15.   
  16. if(fork() == 0)  
  17. {  
  18. close(c=listenfd);  
  19. process_connection(connfd); //child process  
  20. exit(0);  
  21. }  
  22. close(connfd);//parent, close connected socket  
  23. }  
       早期的網絡服務器天天處理幾百或者幾千個客戶鏈接時,這種服務器模型仍是能夠應付的。
       可是隨着互聯網業務的迅猛發展,繁忙的web服務器天天可能須要處理千萬以上的鏈接, 發服務器的問題在於爲每一個客戶現場fork一個子進程比較消耗cpu時間。
 
3.預先派生子進程,每一個子進程都調用accept,accept無上鎖保護
     缺點:
  • 服務器必須在啓動的時候判斷須要預先派生多少子進程
  • 驚羣現象(一個鏈接到來喚醒全部監聽進程),不過較新版本的linux貌似修正了這個問題
     優勢:無須引入父進程執行fork的開銷就能處理新到的客戶
4.預先派生子進程,以文件鎖的方式保護accept
       本模型與3的區別僅僅是對accept(listenfd)使用了文件鎖。
       這種模型是爲了解決以庫函數的形式實現的 accept不能在多個進程中引用同一個監聽套接口的 問題(源自BSD的unix,在內核中實現的accept能夠引用)。使用文件鎖保證了每一個鏈接到來,只有一個進程阻塞在accept調用上。對於已經在內核中實現了accept的系統來講,這種模型至少增長了加鎖解鎖的開銷,因此相對於第3種模型性能較低(特別是在消除了驚羣問題的系統上)
 
5.預先派生子進程,以線程互斥鎖上鎖的方式保護accept
       典型代碼:
[cpp]  view plain  copy
 
  1. static pthread_mutex_t *mptr;  
  2. void  
  3. my_lock_init(char *pathname)  
  4. {  
  5. int fd;  
  6. pthread_mutexattr_t mattr;  
  7. //由於是相關進程因此能夠使用/dev/zero設備建立共享內存  
  8. //優點是調用mmap建立共享存儲區以前無需一個實際的文件  
  9. //映射/dev/zero自動建立一個指定長度的映射區  
  10. fd = open("/dev/zero", O_RDWR, 0,);  
  11. // 將mptr映射到共享存儲區  
  12. mptr = mmap(0, sizeof(pthread_mutex_t), PORT_READ | PORT_WRITE, MAP_SHARED, fd, 0);  
  13. close(fd);  
  14. pthread_mutexattr_init(&mattr);  
  15. pthread_mutexattr_setpshared(&mptr, PTHREAD_PROCESS_SHARED);  
  16. pthread_mutex_init(mptr, &mattr);  
  17. }  
  18.   
  19. void  
  20. my_lock_wait()  
  21. {  
  22. pthread_mutex_lock(mptr);  
  23. }  
  24.   
  25. void  
  26. my_lock_release()  
  27. {  
  28. pthread_mutex_unlock(mptr);  
  29. }  
  30.   
  31. int  
  32. main(int argc, char **argv)  
  33. {  
  34. //init socket and address  
  35. my_lock_init(pathname);  
  36. for(i = 0; i < nchildren; ++i)  
  37. {  
  38. pids[i] = child_make(i, listenfd, addrlen);  
  39. }  
  40.   
  41. for(;;)  
  42. pause();  
  43. }  
  44.   
  45. pid_t  
  46. child_make(int i, int listenfd, int addrlen)  
  47. {  
  48. pid_t pid;  
  49.   
  50. if((pid = fork) > 0)  
  51. return pid;  
  52.   
  53. child_main(i, listenfd, addrlen);  
  54. }  
  55.   
  56. void  
  57. child_main()  
  58. {  
  59. for(;;)  
  60. {  
  61. my_lock_wait();  
  62. connfd = accept(listenfd, chiladdr, &clilen);  
  63. my_lock_release();  
  64. web_child(connfd);  
  65. close(connfd);  
  66. }  
  67. }  
       這種模型在模型4上作出了進一步的改進,因爲以文件鎖的方式實現保護會涉及文件系統,這樣可能比較耗時,因此改進的辦法是以pthread mutex互斥量代替文件鎖。使用線程上鎖保護accept不只適用於同一進程內各線程上鎖,也適用於不一樣進程間上鎖在多進程環境下使用線程互斥鎖實現同步有兩點要求:
1.互斥鎖必須放在由全部進程共享的內存區
2.必須告知線程庫這是在不一樣的進程間共享的互斥鎖
       注意:目前很火的高性能web服務器的表明nginx就是採用的這種模型,網絡上對nginx的研究不少。
 
6.預先派生子進程,由父進程向子進程傳遞套接口描述字
     優點:不須要對accept上鎖保護
     劣勢:
  • 編碼複雜—父進程必須跟蹤子進程的忙閒狀態,以便給空閒子進程傳遞新的套接口。在前述的預先派生子進程的例子中,父進程無需關心由哪個子進程接收一個客戶鏈接,操做系統會根據調度算法處理這些細節。採用這種模型的結果是這些進程沒法均衡的處理鏈接。
  • 父進程經過字節流管道把描述子傳遞到各個子進程,而且各個子進程經過字節流管道寫回單個字節,比起不管是使用共享內存區中的互斥鎖仍是使用文件鎖實施的上鎖和解鎖都更費時。
 
7.併發服務器,爲每一個客戶請求建立一個線程
       典型代碼:
[cpp]  view plain  copy
 
  1. for(;;)  
  2. {  
  3. connfd = accept(listenfd, cliaddr, &clilen,);  
  4. pthread_create(&tid, NULL, &doit, (void *)connfd);  
  5. }  
  6.   
  7. void *  
  8. doit(void *arg)  
  9. {  
  10. pthread_detach(pthread_self());  
  11. web_child((int)arg);  
  12. close((int)arg)  
  13. return (NULL);  
  14. }  
       優勢:編碼簡單。
       缺點:現場爲每一個鏈接建立線程相對於預先派生線程池來講比較耗時。
 
8.預先建立線程,以互斥鎖上鎖方式保護accept
    優點:
  • 編程簡介,易於理解
  • 線程池的方式避免了現場建立線程的開銷
  • OS線程調度算法保證了線程負載的均衡性
       這就是leader-follower模式一個線程等待鏈接到來,其餘線程休眠;新鏈接到來後leader去處理鏈接,釋放listenfd,其餘線程競搶監聽套接口listenfd(可能有驚羣的問題)。leader在處理完鏈接之後成爲follower。
 
9.預先建立線程,由主線程調用accept,並把每一個客戶鏈接傳遞給線程池中某個可用線程
       劣勢:相對於模型8,該模型不只須要使用pthread mutex額外還須要使用pthread cond。
相關文章
相關標籤/搜索