linux------------I/O复用

1. I/O模型

(1)阻塞式I/O

  

   

(2)非阻塞式I/O

  

  通过指定recvfrom的flag参数为 MSG_DONTWAIT,当没接收到数据时会直接返回而不阻塞,并设置errno为 EWOULDBLOCK

  轮询/循环调用recvfrom直到有数据就绪,但会占用很多CPU时间

(3)I/O复用(select、poll)

  

  进程在select上阻塞,直到有数据就绪了就去调用recvfrom确保recvfrom一定能读取到数据。

  相比前面的两个,select的优势在于可以等待多个描述符(类似的做法:用多线程+阻塞I/O,每个线程处理一个文件描述符,这样线程调用recvfrom阻塞不会影响其他的线程)

(4)信号驱动式I/O

  

  通过注册信号处理函数,让内核在描述符就绪时发送SIGIO信号,然后调用recvfrom确保能读取到数据 

(5)异步I/O

  

  与信号驱动的区别:信号驱动式是内核通知进程什么时候启动具体的I/O操作,而异步I/O是内核通知进程I/O操作何时完成

  

  

1 struct aiocb {
2     int             aio_fildes;    //文件描述符
3     off_t           aio_offset;    //文件偏移量
4     volatile void   *aio_buf;      //缓冲区
5     size_t          aio_nbytes;    //
6     int             aio_reqprio;   //请求优先级
7     struct sigevent aio_sigevent;  //通知方式
8     int             aio_lio_opcode;//
9 };

2.  I/O复用

(1)select

  

  返回值:-1,出错;0,没有设备准备好;大于0,准备好的设备的个数

  nfds:关注的描述符的个数(值为最大描述符+1)

  timeout:等待集合中任意一个描述符就绪/可用的时间

    设为NULL:一直等待,当有一个描述符准备好I/O时select才返回

    指定秒、微秒:如果没有一个描述符准备好,则等待该时间后返回

    timeval结构不为NULL,但秒和微秒设为0:不等待,检查描述符后立即返回

struct timeval {
    long tv_sec;   //
    long tv_usec;  //微秒(10^-6秒)
};
struct timespec {
    long tv_sec;
    long tv_nsec;  //纳秒(10^-9秒)
};

  readset、writeset、excepset:读、写、异常描述符集合

    每个参数是一个整型数组,每个元素的每一位对应一个描述符(若int为32位,则第一个元素对应了描述符0~31)

  描述符集合测试宏:

  

  FD_CLR:将fd在fdset中对应的位置0

  FD_SET:将fd在fdset中对应的位置1

  FD_ZERO:fdset中所有位清零

  FD_ISSET:检查fd对应的位是否置1

       

  调用select后,我们关注的但是还未就绪的描述符对应的位会被清零,因此每次重新调用select时,需要再次把关注的描述符对应位置1

(2)pselect

  

  最后一个参数sigmask:

    屏蔽信号,在pselect返回后,再将屏蔽的信号恢复(原子操作),不用单向pselect调用时被信号中断 

    不能屏蔽SIGKILL

(3)poll

  独立为每一个要监控的文件描述符建立一个进而构体pollfd(描述了监控的行为、事件)

  

   

1 struct pollfd {
2     int fd;
3     short events;   //对fd关注的事件
4     short revents;  //fd上发生的事件
5 };

  events、revents:events可设为读写标志,不能设为异常标志    revents为传出参数

  

  timeout:

    INFTIM:永远等待,直到①fds数组中有一个文件描述符准备好了,此时返回1 或②捕捉到信号返回,返回值-1,置错误码errno poll返回

    0:不等待,函数检测fds数组中所有成员后返回

    大于0,等待timeout时间 

(4)ppoll

  

(5)效率更好的 epoll   [solaris上有 /dev/poll   FreeBSD有kqueue]

  select、poll都是对所有关注的套接字进行检测,(从用户态向内核太复制了大量数据)当连接数很大时效率低

  

  

  

   

  

3. select的简单例程

  

  假设:服务器一个监听套接字,最大监听5个连接。    

    select检测监听套接字和连接套接字:如果监听套接字有新连接时就建立连接,将连接描述回复保存连到一个数组(数组大小为5,因为listen5个)。

                           如果已连接的描述符有数据准备好了,就读写数据

  下面只写了关键部分,代码并不全:

 1 int serverfd;
 2 int listennum = 5;
 3 servefd = socket(...);
 4 bind(serverfd,...)
 5 listen(serverfd,listennum);
 6 
 7 fd_set client_fdset;
 8 int maxsock = serverfd;   
 9 int client_sockfd[5]; bzero(client_sockfd);
10 int conn_amount = 0;
11 
12 while(1) {
13     //select前的初始化
14     FD_ZERO(&client_fdset);
15     FD_SET(serverfd,&client_fdset);   //监控描述符加入
16     for(int i=0;i<5;i++) {
17         if(client_sockfd[i]!=0) {
18             FD_SET(client_sockfd[i],&client_fdset);  //连接描述符加入
19         }
20     }
21     int ret = select(maxsock+1,&client_fdset,NULL,NULL,&timeva);
22     
23     for(int i=0;i<conn_amount;i++) {
24         //检查连接套接字是否能读
25         if(FD_ISSET(client_sockfd[i],&client_fdset)) {
26             ret = recv(client_sockfd[i],buf,1024,0); //能读立刻读
27             if(ret<=0) {
28                 close(lient_sockfd[i]);
29                 FD_CLR(client_sockfd[i],&client_fdset);
30                 client_sockfd[i] = 0;
31             }
32         }
33     }
34     //检查是否有新连接,有就接收连接,加入client_sockfd中
35     if(FD_ISSET(serverfd,&client_fdset)) {
36         int sock_client = accept(serverfd...);
37         if(conn_amount<listennum) {
38             client_sockfd[conn_amount++] = sock_client;
39             if(sock_client>maxsock) {
40                 maxsock = sock_client;
41             }
42         }
43     }
44     
45 }

4. epoll的基本原理

  

猜你喜欢

转载自www.cnblogs.com/taoXiang/p/12517072.html