zoukankan      html  css  js  c++  java
  • 服务器模型

    在使用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
    ", fd, ret);
                FD_CLR(fd, &writefds);
             }
          }
       }
       close(listenfd);
    }
  • 相关阅读:
    杀死JS错误提示
    年月日时分秒加星期即时显示的JS日期时间特效
    用JS自动缩小超出大小的图片
    实现简单的FAQ折叠效果
    复制本贴地址传给QQ/MSN好友的代码
    java初学者笔记总结day1
    java初学者笔记总结day2
    java初学者笔记总结day3
    IIS7.5应用程序池集成模式和经典模式的区别介绍
    div模拟textarea文本域轻松实现高度自适应
  • 原文地址:https://www.cnblogs.com/chen-farsight/p/6063411.html
Copyright © 2011-2022 走看看