zoukankan      html  css  js  c++  java
  • 多路选择I/O

     

    多路选择I/O提供另一种处理I/O的方法,相比于传统的I/O方法,这种方法更好,更具有效率。多路选择是一种充分利用系统时间的典型。

    1、多路选择I/O的概念

    当用户需要从网络设备上读数据时,会发生的读操作一般分为两步。

    (1)等待数据准备好,等待数据的到达,并且将其复制到内核的缓冲区,该缓冲区在系统态。

    (2)复制数据,将数据从内核缓冲区中复制到用户指定的缓冲区中。

    一般的读操作形式为:

     Int nbytes = read(sfd, buf, MAX); 

    如果需要的数据没有准备好,例如,数据尚未到达时,read函数发生阻塞,直到所有的数据到达,read函数才将其复制到用户指定的缓冲区,并且返回。如果数据一直未到达,那么read函数将一直阻塞下去,该进程会陷入僵尸状态、这种I/O模型称为阻塞I/O。

    为了防止I/O阻塞使进程进入僵死状态,可以使用多路选择I/O。

    这种方法的思想是先构造一张需要读取文件描述符的表,调用一个函数轮循这个表中的文件描述符,知道有一个文件描述符可以读写该函数才返回,多路选择I/O需要使用两个系统调用,一个负责检查并返回可用的文件描述符;另一个负责对该文件描述符进行读写。

    2、实现多路选择I/O

    Linux环境下使用select函数实现多路选择I/O,函数原型如下:

     Int select(int maxfdp1, fd_set *restrict readfds, fd_set *restrict writefds, fd_set *restrict exceptfds, struct timeval *restrict tvptr); 

    头文件: #include <sys/select.h> 

    参数说明:

    第一个参数maxfdp1表示所关心状态的描述符的个数,正确解释是最大描述符加1。如果maxfdp1的值是2,表示用户关心的描述符数为2;最大的文件描述符为1时,描述符0,和1都会被该函数茶韵,大于1则不关心。一个进程最多可以用于1024个文件描述符,因此maxfdp1的值为(0~1023);

    第2、3、4这3个参数是readfds、writefds和exceptfds,分别表示用户关心的可读、可写和异常的各个描述符,这3个参数是3个位向量,每一位对应一个文件描述符的状态,每一位对应一个文件描述符的状态。

    第5个参数表示用户期望等待的时间。如果tvptr==NULL表示一直等。

    返回值:出错返回-1,返回0表示没有设备准备好,返回值大于0表示准备好的设备数目。

    在这个函数中第2、3、4这三个参数是特殊的参数,这三个参数是fd_set数据类型的,fd_set本质上市一个位向量,是一个无符号的整型。其中每一位代表一个设备的状态,如果是1表示被设置,如果是0表示没有被设置。上边的的三个参数分别代表的是可读、可写和异常三种状态,这三个位向量中的每一位代表一个状态。比如readfds是“111000”表示前3个文件可读,后三个文件不可读。

    最后通过一个实例来演示使用select函数同时处理多个连接请求的服务器端程序。

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <ctype.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <errno.h>
    #include "iolib.h"
    #define MAX_LINE 80
    int port = 8000;
    
    void my_fun(char *p)
    {
           if(p == NULL)
                  exit(1);
           for(; *p != ''; p++)
                  if(*p >= 'A' && *p <= 'Z')
                         *p = *p - 'A' + 'a';
    }
     
    int main(void)
    {
           struct sockaddr_in sin;
           struct sockaddr_in cin;
           int lfd;
           int cfd;
           int sfd;
           int rdy;
           int client[FD_SETSIZE];          ///客户端连接的套接字描述符数组
           int maxi;
           int maxfd;                                  ///最大连接数
           fd_set rest;
           fd_set allset;
           socklen_t addr_len;                     ///地址结构的长度
           char buf[MAX_LINE];
           char addr_p[INET_ADDRSTRLEN];
           int i, n;
           int len;
           int opt = 1;                         ///套接字选项
           bzero(&sin, sizeof(sin)); ///填充地址结构
           sin.sin_family = AF_INET;
           sin.sin_addr.s_addr = INADDR_ANY;
           sin.sin_port = hton(port);
            /*创建一个面向连接的套接字*/
           lfd = socket(AF_INET, SOCK_STREAM, 0);
           if(lfd == -1)
           {
                  perror("call to sock");
                  exit(1);
           }
     
           /*设置套接字选项,使用默认选项*/
           setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
     
           /*绑定套接字到地址结构*/
           n = bind(lfd, (struct sockaddr_in *) &sin, sizeof(opt));
           if(n == -1)
           {
                  perror("call to bind");
                  exit(1);
           }
     
           /*开始监听连续请求*/
           n = listern(lfd, 20);
           if(n == -1)
           {
                  perror("call to listern");
                  exit(1);
           }
           printf("Accepting connecting ...
    ");
           maxfd = lfd;          ///对最大文件描述符进行初始化
           maxi = -1;
           for(i = 0; i < FD_SETSIZE; i++)     ///初始化客户端连接描述符集合
                  client[i] = -1;
           FD_ZERO(&allset);               ///清空文件描述符集合
           FD_SET(lfd, &allset);             ///将监听接字设置在集合内
          
           /*开始服务器程序的死循环*/
           while(1)
           {
                  rset = allset;
                  /*得到当前可以读的文件描述符*/
                  rdy = select(maxfd + 1, &rset, NULL, NULL, NULL);
                  if(FD_ISSET(lfd, &rest))
                  {
                         addr_len = sizeof(cin);
                         /*创建一个链接描述符*/
                         cfd = accept(lfd, (struct sockaddr_in *) &cin, &addr_len);
                         if(cfd == -1)
                         {
                                perror("fail to accept");
                                exit(1);
                         }
                         /*查找一个空闲的位置*/
                         for(i = 0; i < FD_SETSIZE; i++)
                         {
                                if(client[i] < 0)
                                {
                                       client[i] = cfd;
                                       break;
                                }
                         }
                         /*太多的客户端连接,服务器拒绝连接,跳出循环*/
                         if(i == FD_SETSIZE)
                         {
                                printf("too many clients
    ");
                                exit(1);
                         }
                         FD_SET(cfd, &allset);            ///设置连接集合
                         if(cfd > max_fd)
                                maxfd = cfd;
                         if(i > maxi)
                                maxi = i;
                         if(--rdy <= 0)
                                continue;
                  }
     
                  for(i = 0; i < maxi; i++)        ///对每一个连接描述符做处理
    
                  {
    
                         if((sfd = client[i] < 0))
    
                                continue;
    
                         if(FD_ISSET(sfd, &rset))
                         {
                                n = my_read(sfd, buf, MAX_LINE);      ///读取数据
                                if(n == 0)
                                {
                                       printf("the other side has been closed
    ");
                                       fflush(stdout);        ///刷新到输出终端
                                       close(sfd);
                                       FD_CLR(sfd, &allset);    ///清空连接描述符数组
                                       client[i] = -1;
                                }
                               else
                                {
                                       inet_ntop(AF_INET, &cin.sin_addr, addr_p, sizeof(addr_p));
                                       printf("client IP is %s, port is %d
    ", addr_p, ntohs(cin.sin_port));
                                       my_fun(buf);
                                       n = my_write(sfd, buf, len + 1);
                                       if(n == -1)
                                              exit(1);
    
                                }
                                if(--rdy <= 0)
                                       break;
                         }
                  }
           }
           close(lfd);
           return 0;
    }
  • 相关阅读:
    api封装
    计算字符串内的字出现几次
    Generator 函数的语法
    js原型链的看法
    购物车之上线版思路
    Java并发多线程面试题 Top 50
    Java NIO和IO的区别
    【转载】线程池的原理及实现
    【转载】关于SimpleDateFormat安全的时间格式化线程安全问题
    【转载】Java对象的生命周期
  • 原文地址:https://www.cnblogs.com/Mr--Yang/p/6791446.html
Copyright © 2011-2022 走看看