[UNP]:IO复用_select_poll

  • 为什么使用IO复用

    在之前的服务器关闭场景中,如果服务器主动关闭,会导致服务器发送FIN报文段给客户端,但是如果客户此时正阻塞于

    Fgets(), 即客户正尝试从标准输入中读取数据,那么客户将忽略该FIN报文段,直到其解除阻塞,读取sockfd为止

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    `#include "unp.h"

    void str_cli(FILE *fp, int sockfd)
    {
    char sendline[MAXLINE], recvline[MAXLINE];

    while(Fgets(sendline, NAXLINE, fp) != NULL)
    {
    Writen(sockfd, sendline, strlen(sendline));

    if(Readline(sockfd, recvline, MALINE) == 0)
    err_quit("str_cli: server terminated prematurely");
    Fputs(recvline, stdout);

    }
    }

    在某些情况下,这是无法忍受的, IO多路复用提供了这样一种能力:

    可以提前给进程指定多个I/O条件,当这些条件都不满足时,进程阻塞, 当这些条件中的某一个满足时,进程从阻塞中返回

1. select()

select()函数可以在一段指定的时间内,监听用户感兴趣的文件描述符的可读、可写及异常事件,如果没有条件满足,进程将会阻塞在

select()函数

  • select()

    1
    2
    3
    4
    5
    6
    #include <sys/select.h>
    #include <sys/time.h>

    int select(int maxfdp1, fd_set *readset, fd_set *writeset,
    fd_set *exceptset,const struct timeval *timeout)
    // 返回值:就绪描述符的数目,超时返回0,出错返回-1
    • timeout

      告知内核可以等待的最长时间, timeval的结构如下:

      • timeval

        1
        2
        3
        4
        struct timeval {
        long tv_sec; // 秒
        long tv_usec; // 微秒
        }

      timeout的取值有三种可能:

      1. NULL

        此时如果没有条件满足,进程将用于阻塞下去

      2. timeout.tv_sec != 0 || timeout.tv_sec != 0

        此时进程会等待一个固定时间,如果到了时间还没有任何条件满足,那么将会返回0

      3. timeout.tv_sec == 0 && timeout.tv_usec == 0

        此时select()调用会直接返回,不阻塞,这被称为轮询(poiling)

    • readset

      读描述符集合,当集合中的任意一个描述符满足读条件时,就返回

    • writeset

      写描述符集合,当集合中的任意一个描述符满足写条件时,就返回

    • exceptset

      异常描述符集合,当集合中的任意一个描述符有异常条件待处理时,就返回

    描述符集合的数据结构是fd_set, UNP中没有给出其具体结构,但是给出了其中的一种可能实现:
    fd_set是一个整数数组,每一位代表了一个描述符, 假设第一个元素有8位,那么其就可以表示描述符0~7

    使用以下宏可以对fd_set结构进行操作:

    1
    2
    3
    4
    void FD_ZERO(fd_set *fdset);             //清除set的所有位  
    void FD_SET(int fd, fd_set *fdset); //设置set的第fd位
    void FD_CLR(int fd, fd_set *fdset); //清除set的第fd位
    int FD_ISSET(int fd, fd_set *fdset); //测试set的第fd为是否被设置

    这三个参数都是值-结果类型参数,当传入时,表示select函数会检查哪些描述符,当传出是,fd_set是这样一种结构:

    若其中的某一位为1,表示该位锁代表的描述符准备就绪,其余为准备就绪的位都会被置为0

    • maxfdp1

      表示待测试的描述符的个数,通常就是传入的最大描述符 + 1

描述符就绪条件

1

2. shutdown()

shutdown()函数与close()相似,都是用于关闭网络连接,不过它们之间有着较大差异:

  1. shutdown()可以单向关闭,close()只能双向关闭
  2. shutdown()会立即发送FIN, close()只会减少套接字引用计数,等到引用计数为0时才会发送FIN
  • shutdown()

    1
    2
    3
    4
    #include <sys/socket.h>

    int shutdown(int sockfd, int howto);
    // 返回:成功 0,失败 -1
    • sockfd

      待关闭套接字

    • howto

      指定关闭的方式,有三种取值

      • SHUT_RD

        关闭读取通道,套接字接受缓冲区中的数据都将被丢弃,此后进程不能够再对该套接字调用读函数

      • SHUT_WR

        关闭写入通道,这被称为半关闭,发送缓冲区的数据将被全部发送,紧接着发送FIN报文,进程不能够再对该套接字调用任何写函数

      • SHUT_RDWR

        相当于调用一次SHUT_RD然后调用一次SHUT_WR

3. pselect()

pselect()实现的功能为:在被调用时,先以给定信号集合作为当前的信号掩码,在调用完成时,再将信号掩码恢复

  • pselect()

    1
    2
    3
    4
    5
    6
    #include <sys/select.h>
    #include <signal.h>
    #include <time.h>
    int pselect(int maxfdp1, fd_set *readset, fd_set *writeset,
    fd_set *execptset, const struct timespec *timeout,const sigset_t *sigmask)
    // 返回值:就绪描述符的数目,超时返回0,出错返回-1
    • maxfdp1

      最大描述符 + 1

    • readset, writeset, execptset

      读,写,异常描述符集合

    • timeout

      超时事件

    • sigmask

      调用pselect()时设置的的掩码

4. poll()

poll()提供的功能与select()类似,但是在处理流设备时,能够提供额外的信息

  • poll

    1
    2
    3
    #include <poll.h>
    int poll(struct pollfd *fdarray, unsigned long nfds, int timeout);
    // 返回值:就绪描述符的数目,超时返回0,出错返回-1
    • fdarray

      描述符数组,每一个条目都是一个pollfd结构

      • pollfd

        1
        2
        3
        4
        5
        struct pollfd{
        int fd; /* 确认描述符 */
        short events /* 测试条件 */
        short revents /* 返回状态 */
        }

        eventsrevents分别是测试条件与返回的状态

        • select()当中,使用读,写,异常将描述符集合分类,如果描述符满足了相应条件,就将set当中指定的位数置为1
        • 而在poll()当中,则是给每个描述符一个测试条件,用于表示读,写,异常这些类型,当从poll()调用中返回时,从revents的值可以看出这些描述符的返回状态
      • events与revents取值表

        2

        poll()为流设备提供了额外信息,体现在上面的数据类型上,poll()可以识别三类数据:

        1. 普通
        2. 优先级带
        3. 高优先级
    • timeout

      超时时间, 可取值如下

      3

注: poll()函数会忽略fd < 0的描述符


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!