服務器模型


在使用socket進行網絡編程時,首先要選擇一個合適的服務器模型是很重要的。在網絡程序里,通常都是一個服務器服務多個客戶機,為了處理多個客戶機的請求,服務器端的程序有不同的處理方式。

目前最常用的服務器模型分為兩大類,循環服務器模型並發服務器模型

循環服務器模型

UDP循環服務器模型

UDP循環服務器每次獲取一個客戶端的請求,處理后將結果返回給客戶端。

//UDP循環服務器模型偽代碼
main()
{
  listenfd = socket(...);//創建監聽套接字 
  bind(...);//將地址信息和listenfd綁定
  while(1)
  {
    recvfrom(...);
//從客戶端讀取
    
process(...);//處理     sendto(...);//發送回客戶端   }
  close(listenfd); }

TCP循環服務器模型

同樣是每次從等待客戶端取出一個,對其進行處理然后將結果返回客戶端

//TCP循環服務器模型偽代碼
main()
{
  listenfd = socket(...);//創建監聽套接字
  bind(...);//將地址信息和listenfd綁定
  listen(..);//監聽 
  while(1)
  {
    accept(...);//接受客戶端連接請求
    while(1)
    {
      read/recv(...);//接受
      procecc(...);//處理
      write/send(..);//返回
    }
    close(sockfd);   }
  close(listenfd); } 

 

並發服務器模型

為了彌補循環服務器一次只能服務於一個客戶端的缺陷,人們又設計了並發服務器模型。

多進程並發服務器模型

多進程並發服務器模型。為了避免一個客戶端獨占服務器,在客戶端建立連接時會為每個客戶端創建一個子進程。這樣一來多個客戶端同時響應,就會對操作系統的效率有所影響,但不可否認滿足了同時服務多個客戶端的需求。具體做法是在監聽到客戶端連接請求時,首先fork一個子進程服務於客戶端,父進程繼續監聽新的客戶端連接。實際可用進程池解決使用時才創建進程的資源開銷問題。

//多進程並發服務器模型偽代碼
main()
{
  listenfd = socket(...);//創建監聽套接字
  //裝填服務器地址信息
  bind(...);//將監聽套接字listenfd與地址信息綁定
  listen(listenfd, 10);//開始監聽,並設置監聽數量
  while(1)
  {
    //有客戶端連接請求時,獲取到客戶端sockfd,沒有請求時阻塞
    sockfd  = accept(listenfd, ..., ...);
    pid = fork();//創建子進程,服務於客戶端
    if (pid == 0)
    {
      while(1)
      {
        close(listenfd);//首先在子進程關閉掉監聽套接字,防止子進程對其他客戶端請求進行監聽
        recv(...);
        //處理;
        send(...);
      }
      close(sockfd);//處理結束后關閉套接字
      exit(0);//結束子進程

    }
    close(sockfd);
  }
  close(listenfd)
}

 

多線程並發服務器模型

多線程服務器與多進程服務器模型類似。相較於多進程並發服務器,使用多線程技術完成並發服務器對系統開銷要小得多。使用多線程並發服務器模型時,要注意對臨界資源(能被多個線程訪問,但同時只應被一個線程訪問)進行保護。實際使用時,可以采用線程池技術避免每次客戶端連接請求到來時創建子線程時,不必要的系統開銷。

//多線程並發服務器模型偽代碼
//服務程序
void *serv_routine(void *arg)
{   
  sockfd = (int )arg;
   while(1)
   {
      read(sockfd, buf, sizeof buf);
      //處理
      write(sockfd, buf, ret);
   }
}
main()
{
  //初始化線程池
  thread_pool_init();
  //創建監聽套接字
  listenfd = socket(...);
  //填充地址信息
  bind(...);//將地址信息與監聽套接字綁定
  listen(listenfd, 10);//開始監聽
  while(1)
  {
    //接受客戶端連接請求,獲取器sockfd
    sockfd = accept(...);
    //向進程池添加客戶端服務程序
    thread_pool_addtask(..., serv_routine, (void*)sockfd);
  }
  close(sockfd);
  close(listenfd);
  //銷毀線程池
  thread_pool_destroy(...);  
}  

I/O多路服用並發服務器

I/O多路復用可以解決多線程和多進程資源限制的問題。此模型實際上是將UDP循環模型用在了TCP上面。但是它也存在問題,由於它也是一次處理客戶端的請求,可能會導致有些客戶端等待時間過長。

//I/O多路復用——select模型
int main()
{
  //創建監聽套接字描述符
   listenfd = socket(AF_INET, SOCK_STREAM, 0);
  //裝填地址
  //將監聽套接字描述符與裝填好的地址綁定
  bind(listenfd, (struct sockaddr*)&myaddr, len));
  //開始監聽
  listen(listenfd, 10);
   fd_set readfds;  //設置監聽讀文件描述符集合
   fd_set writefds;  //設置監聽寫文件描述符集合
   FD_ZERO(&readfds);  //清空這些集合
   FD_ZERO(&writefds);
   FD_SET(listenfd, &readfds);  //將listenfd添加到讀文件描述符集合中
   fd_set temprfds = readfds;  //定義這個兩個temp集合是為了在每次有可讀寫文件描述符時,都可以在處理完成后繼續監聽之前加入的文件描述符
   fd_set tempwfds = writefds;
   int maxfd = listenfd;
#define BUFSIZE 100
#define MAXNFD  1024 
   int nready;
   char buf[MAXNFD][BUFSIZE] = {0};
   while(1)
   {
      temprfds = readfds;
      tempwfds = writefds;
     //獲取可可讀或可寫的文件描述符,放到集合中, select返回可讀、寫的文件描述符個數
      nready = select(maxfd+1, &temprfds, &tempwfds, NULL, NULL);
     //有客戶端訪問時監聽套接字描述符可讀,可以通過FD_ISSET來判斷具體是哪個文件描述符
      if(FD_ISSET(listenfd, &temprfds))
      {
       //接收客戶端連接請求、並獲取其sockfd
         int sockfd = accept(listenfd, (struct sockaddr*)&clientaddr, &len);
       //將獲取到的套接字描述符加入到讀操作監聽集合中
         FD_SET(sockfd, &readfds);
         maxfd = maxfd>sockfd?maxfd:sockfd;
         if(--nready==0)
         continue;
      }
  
      int fd = 0;
      //遍歷文件描述符集合,對就緒的文件秒速符進行處理
      for(;fd<=maxfd; fd++)
      {
         if(fd == listenfd)
          continue;
       //讀操作就緒的套接字描述符
         if(FD_ISSET(fd, &temprfds))
         {
            int ret = read(fd, buf[fd], sizeof buf[0]);
            if(0 == ret)
            {
               close(fd);
            //處理完成后,將其沖監聽集合中移除
               FD_CLR(fd, &readfds);
               if(maxfd==fd) --maxfd;
               continue;
            }
         //以為要把處理后的結果發送回客戶端,因此將套接字描述符添加到寫操作監聽集合中
            FD_SET(fd, &writefds); 
         }
       //寫操作就緒的套接字描述符
         if(FD_ISSET(fd, &tempwfds))
         {
            int ret = write(fd, buf[fd], sizeof buf[0]);
            printf("ret %d: %d\n", fd, ret);
            FD_CLR(fd, &writefds);
         }
      }
   }
   close(listenfd);
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM