服务器模型

在使用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);
}

 

posted @ 2016-11-14 22:24  GreenHand#  阅读(2603)  评论(1编辑  收藏  举报