Linux下的I/O复用
作者:hahaya
日期:
在早期的网络编程中,服务端的处理方式为:当有客户端连接上来时,就为客户端创建一个单独的进程或线程,用于处理客户端请求。由于系统中能创建的进程或线程数有限并且只能监听一个socket,所以这种处理方式的性能并不是太高。那么有什么方式能提高服务端的性能呢?答案是—I/O复用。
Linux下实现I/O复用的系统调用有:select、poll、epoll等(还有pselect等),下面分别介绍~
一 I/O复用的意义
I/O复用使程序能同时监听多个文件描述符(file descriptor),程序会在I/O复用系统调用处等待,直到被监视的文件描述符有一个或多个发生状态改变。在Linux下,文件描述符其实就是一个整数,我们比较熟悉的有0(标准输入stdin)、1(标准输出stdout)、2(标准错误输出stderr),其他的还有文件句柄FILE、套接字socket等。由于文章主要讨论网络相关的内容,所以文中文件描述符指的是socket套接字。
二 I/O复用的使用场景
- 程序需要同时处理多个socket连接
- 程序需要同时处理用户输入(文件描述符的值为0)和网络连接
- TCP服务器需要同时处理监听socket和连接socket
- 服务器需要同时处理TCP请求和UDP请求
- 服务器需要要同时监听多个端口或多个服务
三 I/O复用的注意事项
- I/O虽然能同时监听多个文件描述符,但是它本身是阻塞的(在select、poll、epoll系统调用出阻塞,直到有监视的文件描述符发生状态变化,并不是阻塞在I/O系统调用)
- 当多个文件描述符同时就绪时,如果不采取额外的措施,程序就只能依次处理其中的每一个文件描述符,这使得服务器程序看起来是串行处理的。这时如果要实现并发,只有使用多进程或多线程等编程手段。
四 socket文件描述符就绪条件
1 socket可读
2 socket可写
3 socket异常
五 select系统调用
1 作用
在一段指定的时间内,监听用户感兴趣的文件描述符的可读、可写、异常事件
2 select函数介绍
select函数原型如下:
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
select函数说明:
应用程序调用select函数时,通过readfds、writefds、exceptfds传入感兴趣的文件描述符,内核将修改它们来通知应用程序哪些文件描述符已经就绪。
select函数参数说明:
nfds: 被监听的文件描述符的总数,因为是用位记录要监听的文件描述符,比如需要监听文件描述符2,则表示要记录第2位,则会设置fd_set中的第2位,故最大值为2 + 1 =3,所以nfds通常设置为监听的所有文件描述符中的最大值加1,因为文件描述符、记录位是从0开始计数的。比如有a,b,c三个要监听的文件描述符,并且a的值最大,则nfds应该设置为a + 1
readfds: 可读的文件描述符集合
writefds: 可写的文件描述符集合
exceptfds: 异常的文件描述符集合
timeout: 设置select函数的超时时间
select返回值:
select函数成功时,返回就绪(可读、可写、异常)文件描述符的总数
如果在超时时间timeout内没有任何文件描述符就绪,则select函数返回0
select函数失败时,返回-1,并设置errno
如果在select函数等待期间,程序接收到信号,则select函数立即返回-1,并设置errno为EINTR
fd_set说明:
fd_set结构体仅包含一个整形数组,该数组的每一个元素的每一位(bit)标记一个文件描述符,由于位操作过于麻烦,所以Linux中提供下面一组函数来操作fd_set:
void FD_ZERO(fd_set *set); //清除set的所有位
void FD_SET(int fd, fd_set *set); //设置set的第fd位
void FD_CLR(int fd, fd_set *set); //清除set的第fd位
void FD_ISSET(int fd, fd_set *set); //测试set的第fd为是否被设置 <br /> **timeval说明:**
struct timeval {
long tv_sec; // 秒
long tv_usec; // 微秒
}; select函数的最后一个参数timeout是timeval类型的,用来设置select函数的超时时间,timeout是一个timeval类型的指针,所以内核能修改修改它,从而告诉应用程序select函数等待了多长时间,不过我们不能完全信任select函数调用后返回的timeout值,比如调用失败时,timeout的值是不确定的。 通过timeval的定义,我们可以发现,select函数给我们提供了一个微秒级别的定时器。如果给timeout变量的tv_sec和tv_usec都设置成0,则select函数会立即返回。如果将timeout设置为NULL,则select函数将一直阻塞,直到某个文件描述符就绪。
3 select函数使用示例
编译完成后,可以尝试使用telnet连接该服务端,进行接下来的测试~
出处:http://hahaya.github.com/linux-io-multiplexing
本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。