I/O复用可同时监听多个文件描述符发生事件,很大程度提高了程序的性能。I/O复用适用的场合一般有如下几点:
- 客户端程序要同时处理多个socket;
- 客户端程序要同时处理用户输入和网络连接。如聊天室程序;
- TCP服务器同时监听socket和连接socket。如服务器连接负载均衡;
- 服务器要同时处理TCP请求和UDP请求。如回射服务器;
- 服务器需要同时监听多个端口,或者处理多种服务。如xinetd服务器;
I/O复用本身是阻塞的,即没有文件描述符有事件发生就为阻塞状态。并且当多个文件描述符同时就绪时,如果不采取其他措施,I/O复用也只能按顺序对一个一个文件描述符进行处理,即服务器是串行工作的。可以采取多进程或多线程的方式来实现并发。Linix下I/O复用的系统调用主要是select、poll和epoll,以下是介绍poll系统调用的简单使用。
1. poll 相关API
poll系统调用和select类似,也是在用户指定的时间内轮询一定数量的文件描述符,以检测是否有就绪的文件描述符,原型如下:
#include<poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
- 参数
- fds参数是一个pollfd结构体数组,它指定所有用户感兴趣的文件描述符上的可读、可写和异常事件。pollfd结构体定义如下:
其中,events成员告诉poll所要监听fd上的哪些事件,它是一系列事件的按位或;revents成员则由内核修改,以反馈给应用程序fd上实际发生了哪些事件。poll支持的事件类型有以下几种:struct pollfd { int fd; /* 文件描述符 */ short events; /* 注册的事件 */ short revents; /* 实际发生的事件,由内核填充 */ };
- nfds参数指定被监听事件集合fds的大小。其类型nfds_t的定义如下:
typedef unsigned long int nfds_t;
- timeout参数指定poll的超时值,单位为毫秒。设置为-1时,poll调用一直阻塞,直到有事件发生。设置为0立即返回。返回值与select相同。
2. poll的使用
#define SIZE 5 /* 最大监听文件描述符的个数 */
void Init_fd(struct pollfd *fds) /* 初始化pollfd结构体数组 */
{
for(int i = 0;; i < SIZE; ++i)
{
fds[i].fd = -1;
fds[i].events = 0;
fds[i].revents = 0;
}
}
void Insert_fd(struct pollfd *fds, int fd, short event) /* 添加文件描述符及关注的事件 */
{
for(int i = 0;; i < SIZE; ++i)
{
if(-1 == fds[i].fd)
{
fds[i].fd = fd;
fds[i].events = event;
break;
}
}
}
void Delete_fd(struct pollfd *fds, int fd) /* 清除文件描述符 */
{
int i = 0;
for(; i < SIZE; ++i)
{
if(fds[i].fd == fd)
{
fds[i].fd = -1;
fds[i].events = 0;
break;
}
}
}
int main()
{
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
assert(sockfd != -1);
struct sockaddr_in ser,cli;
ser.sin_family = AF_INET;
ser.sin_port = htons(6000);
ser.sin_addr.s_addr = inet_addr("127.0.0.1");
int res = bind(sockfd, (struct sockaddr *)&ser, sizeof(ser));
assert(res != -1);
res = listen(sockfd, 10);
struct pollfd fds[SIZE]; /* pollfd结构体数组 */
Init_fd(fds); /* 初始化fds */
Insert_fd(fds, sockfd, POLLIN); /* 添加sockfd套接字 */
while(1)
{
int n = poll(fds, SIZE, -1); /* 调用poll系统调用 */
if(n <= 0)
{
printf("poll error!\n");
continue;
}
int i = 0;
for(; i < SIZE; ++i)
{
int fd = fds[i].fd;
if(fds[i].fd != -1)
{
if(fds[i].revents & POLLRDHUP) /* 对方关闭连接 */
{
close(fd);
Delete_fd(fds, fd);
}
else if(fds[i].revents & POLLIN) /* 可读事件发生 */
{
if(fd == sockfd) /* 有新的连接请求 */
{
printf("linked one client!\n");
int len = sizeof(cli);
int c = accept(sockfd, (struct sockaddr *)&cli, &len);
if(c < 0)
{
printf("accept error!\n");
continue;
}
Insert_fd(fds, c, POLLIN | POLLRDHUP);
}
else /* 接受数据 */
{
char buff[128] = {0};
int n = recv(fd, buff, 127, 0);
if(n <= 0)
{
printf("recv error!\n");
continue;
}
printf("%d %s\n",fd, buff);
send(fd, "OK", 2, 0);
}
}
}
}
}
return 0;
}