《UNIX网络编程》笔记 - select和poll

简介

IO复用:让进程等待一系列IO条件而不是一个IO条件数组

经过selectpoll函数咱们能够同时监听多个描述符,在描述符就绪时进行对应的操做。ide

select

定义:函数

//maxfdpl: 待测试的描述符个数
//返回就绪描述符的个数,若超时则为0, 若出错则为-1
int select(int maxfdpl, fd_set *readset, fd_set *writeset, fd_set *exceptset, struct timeval *timeout);
 
//超时选项 
//NULL:wait forever;0:don't wait
struct timeval {
	long    tv_sec;         /* seconds */
	long    tv_usec;        /* and microseconds */
};

//每一个fds_bit的每一位对应一个描述符
typedef struct fd_set {
	int     fds_bits[FD_SETSIZE/sizeof(int)/NBBY]; /* NBBY=bits in a byte ; usually 8*/
} fd_set;

#define FD_SETSIZE 1024 /* fd_set中描述符的总数 */
void FD_ZERO(fd_set *fdset);            /* clear all bits in fdset */
void FD_SET(int fd, fd_set *fdset);     /* turn on the bit for fd in fdset */
void FD_CLR(int fd, fd_set *fdset);     /* turn off the bit for fd in fdset */
void FD_ISSET(int fd, fd_set *fdset);   /* is the bit for fd on in fdset ? */
复制代码

select的使用方法:测试

int fds[FD_SETSIZE]; 保存当前全部描述符
    fd_set rset, wset, eset; //定义读、写、异常对应的fd_set
    //初始化fd_set,很是重要且不能省略,由于若是不初始化可能会影响FD_ISSET的调用结果
    FD_ZERO(&rset); 
    FD_ZERO(&wset);
    FD_ZERO(&eset);
    for (;;) {
        //在循环中调用select
        select(FD_SETSIZE, &rset, &wset, &eset, NULL);
        //遍历当前全部的fd,处理就绪的fd
        for (int i = 0; i < FD_SETSIZE; i++)
        {
            if (FD_ISSET(fds[i], &rset))
            {
                //handle read
            }
            if (FD_ISSET(fds[i], &wset))
            {
                //handle write
            }
            if (FD_ISSET(fds[i], &eset))
            {
                //handle exception
            }
        }
    }
复制代码

fd_set的限制spa

在很早以前就看到网上的介绍说select在描述符个数上是有限制的,如今终于知道这个限制是从哪来的了,这实际上跟fd_set的实现机制有关。rest

fd_set中使用int数组中的各个位来保存多个描述符的状态,这个数组称为描述符集,好比数组的第一个数有32位,那么第一个数的每一位就表示第0~31个描述符的状态,这样一来当咱们调用FD_ISSET来判断某一个描述符状态时,咱们只须要找到其对应的位判断其是0或者1就好了;同理当咱们须要设置某个描述符状态时,只须要设置对应的位的状态便可。而fd_set中数组的大小是经过FD_SETSIZE这个值算出来的,FD_SETSIZE是一个宏定义,一般它的默认值比较小,在个人mac上查看其默认值是1024,也就是说在个人mac上select可以支持的最大的描述符数量是1024个。固然FD_SETSIZE也能够从新定义,但若是要调整须要从新编译内核。code

描述符读就绪条件orm

  1. 接收缓冲区数据字节数大于低水位(默认是1),这时读取操做返回大于0
  2. 读半关闭,也就是对端发来了FIN,这时返回0,也就是EOF
  3. 当前套接字是监听套接字,并且已完成链接数不为0,这时能够进行accept操做
  4. 描述符上有套接字错误须要处理

描述符写就绪条件进程

  1. 发送缓冲区数据字节数大于低水位(一般为2048);
  2. 套接字已链接或不须要链接(UDP)
  3. 写半关闭,这时若是再写会收到SIGPIPE信号
  4. 使用非阻塞式connect的套接字已创建链接或者connect失败
  5. 描述符上有套接字错误须要处理

shutdown&closeip

有两个函数能够关闭套接字:shutdownclose,它们的区别以下:

  1. close会将引用计数减1,当计数为0时关闭套接字;shutdown能够直接触发关闭。
  2. close会终止读和写两个方向;shutdown能够经过参数howto指定关闭某个方向
int close(int fd);
int shutdown(int fd, int howto);

/* * howto arguments for shutdown(2), specified by Posix.1g. */
#define SHUT_RD 0 /* shut down the reading side */
#define SHUT_WR 1 /* shut down the writing side */
#define SHUT_RDWR 2 /* shut down both sides */

复制代码

poll

pollselect的功能相似,也支持IO复用,可是poll没有使用描述符集,而是使用pollfd这种结构来表示描述符的状态。

//nfds:array的长度,受进程能打开的最大文件数限制
//返回就绪描述符的个数,若超时则为0, 若出错则为-1
int poll(struct pollfd *fdarray, unsigned long nfds, int timeout);

struct pollfd {
	int     fd;         /* descriptor to check */
	short   events;     /* events of intrests on fd */
	short   revents;    /* events that occurred on fd */
};
复制代码

poll的使用方法:

struct pollfd pollfds[OPEN_MAX]; //定义pollfd数组,将须要监听的描述符保存起来
    for (;;)
    {
        //在循环中调用poll
        poll(pollfds, OPEN_MAX, INFTIM);
        for (int i = 0; i < OPEN_MAX; i++)
        {
            //遍历pollfd数组处理就绪的描述符
            struct pollfd pfd = pollfds[i];
            if (pfd.revents & POLLIN) {
                //handle read
            }
            if (pfd.revents & POLLOUT) {
                //handle write
            }
        }
    }
复制代码

poll识别的数据类型:普通(normal)、优先级带(priority band)、高优先级(high priority); 这些术语来自基于流的实现。(没太明白,先标记下)

events常量列举:

常量 出如今events 出如今revents 说明
POLLIN y y 普通或优先级带数据可读
POLLRDNORM y y 普通数据可读
POLLRDBAND y y 优先级带数据可读
POLLPRI y y 高优先级带数据可读
POLLOUT y y 普通数据可写
POLLWRNORM y y 普通数据可写
POLLWRBAND y y 优先级带数据可写
POLLERR n y 发生错误
POLLHUP n y 发生挂起
POLLNVAL n y 描述符不是一个打开的文件

poll的就绪条件:

  • 全部正规TCP数据和全部UDP数据视为普通数据
  • TCP带外数据视为优先级带数据
  • 当TCP读半关闭时(收到对端传来的FIN),也视为普通数据,随后的读操做将返回0
  • TCP链接存在错误既能够视为普通数据,也能够视为错误(POLLERR)。随后的读操做将返回-1,并设置全局的errno变量。
  • 监听套接字上有新的链接既能够视为普通数据,也能够视为优先级数据。
  • 非阻塞式的connect的完成视为使对应的套接字可写。

总结

selectpoll都支持IO复用,其思路都是调用函数监听多个描述符,当有描述符就绪或者超时的时候函数调用就会返回,对应的描述符集合状态也会改变,这时候再遍历描述符集合,处理其中就绪的部分便可。

这种方式在须要监听的描述符比较小,或者是每次就绪的描述符不少的状况下比较有效;但当描述符不少并且每次只有少数描述符就绪时,效率就比较低了。后面出现的epoll就避免了这种线性遍历的问题。

另外select还受FD_SETSIZE的限制,只能处理较少的描述符,而poll则没有这个限制。poll监听的集合大小只受进程能打开的文件数量(RLIMIT_NOFILE)的限制。

相关文章
相关标签/搜索