UNPv1第六章:IO复用select&poll

有些进程需要一种预先告知内核的能力,使得内核一旦发现进程指定的一个或多个I/O条件就绪(也就是说输入已准备好被读取,或者描述符已能承受更多的输出),他就通知进程,这个能力称为I/O复用

1.IO模型

5种基本I/O模型

阻塞式I/O
非阻塞式I/O
I/O复用(select和poll)
信号驱动式I/O(SIGIO)
异步I/O

一个输入操作通常包括两个不同的阶段

(1)等待数据准备

(2)从内核向进程复制数据

对于一个套接口上的输入操作,第一步一般是等待数据到达网络,当分组到达时,它被拷贝到内核中的某个缓冲区,第二步是将数据从内核缓冲区拷贝到应用缓冲区。

(1)阻塞式I/O

最流行的I/O模型是阻塞式I/O(blocking I/O)模型,默认情形下,所有的套接字都是阻塞的

上图中进程在从调用recvfrom开始到它返回的整段时间内被阻塞,recvfrom成功返回后,应用进程开始数据处理

(2)非阻塞式I/O

进程把一个套接字设置成非阻塞是在通知内核:当所请求的I/O操作非得把本进程投入睡眠才能完成,不能把本进程投入睡眠,而是返回一个错误。

前三次调用recvfrom时没有数据可以返回,因此内核转而立即返回一个EWOULDBLOCK错误,第四次调用recvfrom时已经有数据报准备好,它被复制到应用程序缓冲区,于是recvfrom成功返回

当一个应用程序使用了非阻塞模式的套接字,它需要使用一个循环来不停的测试是否一个文件描述符有数据可读(称做 polling,轮询) 。应用程序不停的 polling内核来检查是否 I/O操作已经就绪。这将是一个极浪费CPU资源的操作。这种模式使用中不是很普遍。

(3)IO复用模型

有了I/O复用,我们就可以调用select或poll,阻塞在这两个系统调用中的某一个之上,而不是阻塞真正的I/O系统之上

我们阻塞于select调用,等待数据报套接字变为可读,当select返回套接字可读这一条件时,调用recvfrom把所读的数据复制到应用程序缓冲区内。另外使用select的优势在于我们可以等待多个描述符就绪

(4)信号驱动IO模型

可以用信号让内核在描述符就绪时发送SIGIO信号通知我们

无论如何处理SIGIO信号,这种模型的优势在于等待数据报到达期间进程不被阻塞。主循环可以继续执行,只要等待来自信号处理函数的通知:既可以是数据已处理好被处理,也可以是数据已准备被读取

(5)异步IO模型

异步 I/O 和 信号驱动I/O的区别是:

a) 信号驱动 I/O 模式下,内核在操作可以被操作的时候通知给我们的应用程序发送SIGIO 消息。

b) 异步 I/O 模式下,内核在所有的操作都已经被内核操作结束之后才会通知我们的应用程序。

2 select函数

该函数允许进程指示内核等待多个事件的任何一个发生,并只在有一个或多个事件发生或经历一段指定的时间才唤醒它,也就是说我们调用select告知内核对哪些描述符(就读、写或异常条件)感兴趣以及等待多长时间,当然感兴趣的描述符可以不仅局限于套接字,任何描述符都可以用select测试

#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 -出错

我们从此函数的最后一个参数开始介绍,它告诉内核等待一组指定的描述字中的任一个准备好可花多长时间,结构timeval指定了秒数和微秒数成员

struct timeval {
    long tv_sec;       /* seconds */
    long tv_usec;      /* microseconds */
};

这个参数有以下三种可能:

a. 永远等待下去:仅在有一个描述字准备好I/O时才返回,为此,我们将参数timeout设置为空指针。

b. 等待固定时间:在有一个描述字准备好I/O是返回,但不超过由timeout参数所指timeval结构中指定的秒数和微秒数。

c. 根本不等待:检查描述字后立即返回,这称为轮询(polling)。为了实现这一点,参数timeout必须指向结构timeval,且定时器的值(由结构timeval指定的秒数和微秒数)必须为0

在前两者情况的等待中,如果进程捕获了一个信号并从信号处理程序返回,那么等待一般被中断。

中间三个参数readset,wirteset和exceptset指定我们要让内核测试读写和异常条件所需的描述字,参数maxfdp1说明了被测试的描述符的个数,它的值是要被测试的最大的描述符+1

为了分配一个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 on the bit for fd in fdset */

int FD_ISSET(int fd, fd_set * fdset); /* is the bit for fd on in fdset */

描述符就绪条件

1).满足下面四个中任意条件,则一个套接字准备好读:

a.套接字接收缓冲区的数据字节数大于等于,套接字接收缓冲区低水位线,可以用SO_RCVLOWAT套接选项来设置低水位线,对于TCP和UDP套按字,默认值为1

b.该连接的读半部分关闭(接收到了FIN的TCP连接).对这样的套接字读操作,返回0(EOF)

c.该套接字是一个监听套接字且已经完成的连接数不为0.对这样的套按字的accept通常不会阻塞

d.其上有一个套接字错误待处理.对这样的套按字的读操作将不阻塞并返回-1(错误),同时把errno设置成错误条件,这些待处理错误也可以通过指定SO_ERROR套接字选项调用getsockopt获取.

2).满足下面四个中任意条件,则一个套接字准备好写:

a.该套接字发送缓冲区的可用字节数大于等于套接字发送缓冲区低水位线的当前大小.并且或者该套接已经连接,或者套按字不需要连接(UDP),如果我们把这套接字设置成非阻塞,写操作将不阻塞并返回一个正值.可以使用SO_SNDLOWAT设置一个该套接字的低水位标记.对于TCP和UDP默认值通常为2048.

b.该连接的写半部关闭.对这样的套接写的写操作将产生SIGPIPE信号.

c.使用非阻塞式的connect的套按字已经建立连接,或者connect已经失败.

d.其上有一个套接字错误等处理。对这样的套接字进行写操作会返回-,且,把ERROR设置成错误条件,可以通过指定SO_ERROR套按选项调用getsockopt获取并清除.

3).如果一个套接字存在带外数据或者仍处于带外标记,那么它有异常条件待处理

3 shutdown函数

终止网络连接的正常方法是调用close,但close有两个限制可由函数shutdown来避免:

1). close将描述字的访问计数减1,仅在此计数为0时才关闭套接口。用shutdown我们可以激发TCP的正常连接终止序列,而不管访问计数。

2). close终止了数据传送的两个方向:读和写。由于TCP连接是全双工的,有很多时候我们要通知另一端我们已经完成了数据发送,即使那一端仍有许多数据要发送也是如此。

#include <sys/socket.h>
int shutdown(int sockfd, int howto);
// 返回: 0-成功, -1-出错

该函数的行为依赖于howto参数的值:

SHUT_RD – 关闭套接字的读取数据方向的连接

SHUT_WR – 关闭套接字的写入数据方向的连接

SHUT_RDWR – 关闭套接字双向的连接

4 pselect函数

#include <sys/select.h>
#include <signal.h>
#include <time.h>
int pselect(int maxfdp1, fd_set * readset, fd_set * writeset, fd_set * exceptset,
            const struct timespec * timeout, const sygset_t * sigmask);
//返回: 准备好描述字的个数, 0-超时 -1-出错

pselect相对于select有两个变化

1).pselect函数采用timespec结构,这个结构支持纳秒

struct timespec{
     time_t tv_sec;    // seconds
     long tv_nsec;     // nanoseconds
};

2).pselect函数增加了第六个函数:一个指向信号掩码的指针

5 poll函数

poll提供了与select相似的功能,但当涉及到流设备时,它还提供了附加信息

#include <poll.h>
int poll(struct pollfd * fdarray, unsigned long nfds, int timeout);
// 返回: 准备好描述字的个数, 0-超时, -1-出错

第一个参数是指向一个结构数组第一个元素的指针,每个数组元素都是一个pollfd结构,它规定了为测试一给定描述字fd的一些条件。

struct pollfd{
     int fd;              /* descriptor to check */
     short events         /* events of interest on fd */
     short revents        /* events that occurred on fd */
};

参数nfds说明我们关心的描述字的个数,参数timeout 超时等待的时间,单位是毫秒

时间: 2024-07-28 20:09:20

UNPv1第六章:IO复用select&poll的相关文章

IO复用之——poll

一. 关于poll 对于IO复用模型,其优点无疑是免去了对一个个IO事件就绪的等待,转而代之的是同时对多个IO数据的检测,当检测等待的事件中至少有一个就绪的时候,就会返回告诉用户进程"已经有数据准备好了,快看看是哪个赶紧处理",而对于IO复用的实现,除了可以用select函数,另外一个函数仍然支持这种复用IO模型,就是poll函数: 二. poll函数的用法 虽然同样是对多个IO事件进行检测等待,但poll和select多少还是有些不同的: 函数参数中, 先来说nfds,这个是和sel

高性能网络服务器--I/O复用 select poll epoll_wait之间的区别

一.select 使用的集合的方式,最多只能监听1024个文件描述符,内部使用位操作,将相应的位置为1或者置为0,需要将可读.可写.异常的三类事件分开来用,内部使用轮询的方法,每次返回都需要将所有的套接字从内核到用户空间之间进行拷贝. 二.poll 比select稍微好一点,也是在指定时间内轮询一定数量的文件描述符,以测试其中是否有就绪. 三.epoll_wait 把用户关心的文件描述符上事件放在内核里的一个事件表中从而无需像select和poll那样每次调用都要重复传入文件描述符集或者事件集.

转一贴,今天实在写累了,也看累了--【Python异步非阻塞IO多路复用Select/Poll/Epoll使用】

下面这篇,原理理解了, 再结合 这一周来的心得体会,整个框架就差不多了... http://www.haiyun.me/archives/1056.html 有许多封装好的异步非阻塞IO多路复用框架,底层在linux基于最新的epoll实现,为了更好的使用,了解其底层原理还是有必要的.下面记录下分别基于Select/Poll/Epoll的echo server实现.Python Select Server,可监控事件数量有限制: 1 2 3 4 5 6 7 8 9 10 11 12 13 14

python的协程和异步io【select|poll|epoll】

协程又叫做微线程,协程是一种用户态的轻量级的线程,操作系统根本就不知道协程的存在,完全由用户来控制,协程拥有自己的的寄存器的上下文和栈,协程调度切换时,将寄存器上下文和栈保存到其他地方,在切换回来后,恢复之前保存的寄存器的上下文关系,因此协程能保留上一次调用的状态,每次过程重入的时候,就相当于进入上一次调用的状态 协程一定在单线程中,协程的切换是在线程中切换,和单个线程在cpu之间不停的切换是一样的但是线程切换是cpu控制的,而协程的切换是用户控制的,操作系统根本无感知:协程的切换比线程的切换速

UNIX网络编程笔记(5)—I/O复用select/poll

I/O复用:select和poll函数 1. 概述 考虑一种情况,当客户端阻塞于fgets调用时,服务器进程被杀死:此时服务器TCP虽然正确地给客户TCP发送了一个FIN,但是由于客户进程阻塞于标准输入的过程,直到从套接字读时为止.这样的进程就需要一种机制,使得内核一旦发现进程指定的一个或多个I/O条件就绪,就通知进程.这个能力就叫做I/O复用.由select和poll函数支持的. I/O复用典型使用在下列网络应用场合: (1)客户处理多个描述符. (2) 客户通知处理多个套接字. (3) 服务

IO复用select实现

1 #include<unistd.h> 2 #include<stdio.h> 3 #include<sys/select.h> 4 #include<sys/socket.h> 5 #include<netinet/in.h> 6 #include<string.h> 7 #include<stdlib.h> 8 #include<assert.h> 9 10 #define SERVER_IP "

python IO 多路复用 select poll epoll

三个多路复用模型的概念 select select 原理 select 是通过系统调用来监视着一个由多个文件描述符(file descriptor)组成的数组,当select()返回后,数组中就绪的文件描述符会被内核修改标记位(其实就是一个整数),使得进程可以获得这些文件描述符从而进行后续的读写操作.select饰通过遍历来监视整个数组的,而且每次遍历都是线性的. select 优点 select目前几乎在所有的平台上支持,良好跨平台性. select 缺点 每次调用select,都需要把fd集

TCP IO复用 select并发服务端 Linux socket编程入门(3)

在写这段代码的时候,发现很多地方容易弄错.select有可能会出错,返回-1. 比如 int FD_ISSET(int fd,fd_set *fdset); void FD_CLR(int fd,fd_set *fdset); void FD_SET(int fd,fd_set *fdset); void FD_ZERO(int fd,fd_set *fdset); 这里的几个宏都是传入指针,而不是值传递. select函数声明如下: int select(int maxfdp1,fd_set

Linux统系统开发12 Socket API编程3 TCP状态转换 多路IO高并发select poll epoll udp组播 线程池

[本文谢绝转载原文来自http://990487026.blog.51cto.com] Linux统系统开发12 Socket API编程3 TCP状态转换 多路IO高并发select  poll  epoll udp组播 线程池 TCP 11种状态理解: 1,客户端正常发起关闭请求 2,客户端与服务端同时发起关闭请求 3,FIN_WAIT1直接转变TIME_WAIT 4,客户端接收来自服务器的关闭连接请求 多路IO转接服务器: select模型 poll模型 epoll模型 udp组播模型 线