日期 Aug. 18, 2022
简介 本文介绍select-poll-epoll,IO多路复用
[TOC]
select、poll、和epoll是用于实现多路复用的,即一个线程使用它们即可使用多个socket。由此,线程不能被任何一个被管理的Socket阻塞,且任意一个Socket来数据之后都得告知select/poll/epoll线程。
0. IO多路复用(multiplexing)
操作系统在处理IO的时候,主要有两个阶段:
- 等待数据传输到IO设备;
- IO设备将数据复制到用户空间。
IO多路复用是指在多个文件描述符fd上执行IO操作的能力。通常输入操作如读取、接收以及接收消息调用将会在没有入数据时阻塞,这会导致错过其他fd的数据。
IO多路复用在调用时阻塞进程,返回时返回给进程fd集合,这些fd已经可以IO了(ready状态)。进程可以为这些fd处理IO,直到下一次IO多路复用调用。
IO多路复用使用场景:
- 当客户处理多个描述字时(一般是交互式输入和网络套接口),必须使用I/O复用。
- 当一个客户同时处理多个套接口时,而这种情况是可能的,但很少出现。
- 如果一个TCP服务器既要处理监听套接口,又要处理已连接套接口,一般也要用到I/O复用。
- 如果一个服务器即要处理TCP,又要处理UDP,一般要使用I/O复用。
- 如果一个服务器要处理多个服务或多个协议,一般要使用I/O复用。
最好的教程就是Man Page,而最好的解释方式则是使用代码真正运行。有关三个函数的在线man page以及Linux中的实现方式如下:
- man 2 select
- man 2 poll
- man 7 epoll
pollandselect: include/linux/poll.h, fs/select.cepoll: fs/poll.c
select
#include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *execptfds, struct timeval *timeout);
select监视3个fd集合,分别是读(readfds)、写(writefds)、异常(exceptfds),可能为NULL,nfds时最高fd号加1。timeout值为NULL表示永久阻塞。
当3个fd集合中有一个或多个ready for IO时,或者超时时,select返回。
优点:直接将fd_set拷贝到了内核态。
缺陷:它有监视数量限制,即最大为FD_SETSIZE,是一个常数1024。fd_set不可重用。用户态和内核态切换仍有开销。多次遍历O(n)
sockfd = socket(AF_INET, SOCK_STREAM, 0);
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(2000);
addr.sin_addr.s_addr = IADDR_ANY;
bind(sockfd, (struct sockaddr*)&addr, sizeof(addr));
listen(sockfd, 5);
for (i=0; i<5; ++i)
{
memset(&client, 0, sizeof(client));
addrlen = sizeof(client);
fds[i] = accept(sockfd, (struct sockaddr*)&client, &addrlen);
if (fds[i] > max)
max = fds[i];
}
while (1)
{
FD_ZERO(&rset); // rset本质是一个bitmap,默认最大为1024位
for (i=0; i<5; ++i)
{
FD_SET(fds[i], &rset);
}
puts("round again");
select(max+1, &rset, NULL, NULL, NULL);
for (i=0; i<5; ++i)
{
if (FD_ISSET(fds[i], &rset))
{
memset(buffer, 0, MAXBUF);
read(fds[i], buffer, MAXBUF);
puts(buffer);
}
}
}
poll
/*
struct pollfd
{
int fd;
short events;
short revents;
};
*/
for (i=0; i<5; ++i)
{
memset(&client, 0, sizeof(client));
addrlen = sizeof(client);
pollfds[i].fd = accept(sockfd, (struct sockaddr*)&client, &addrlen);
pollfds[i].events = POLLIN;
}
sleep(1);
while (1)
{
puts("round again");
poll(pollfds, 5, 50000);
for (i=0; i<5; ++i)
{
if (pollfds[i].revents & POLLIN)
{
pollfds[i].revents = 0;
memset(buffer, 0, MAXBUF);
read(pollfds[i].fd, buffer, MAXBUF);
puts(buffer);
}
}
}
epoll
struct epoll_event events[5];
int epfd = epoll_create(10);
...
...
for (i=0; i<5; ++i)
{
static struct epoll_event ev;
memset(&client, 0, sizeof(client));
addrlen = sizeof(client);
ev.data.fd = accept(sockfd, (struct sockaddr*)&client, &addrlen);
ev.events = EPOLLIN;
epoll_ctl(epfd, EPOLL_CTL_ADD, ev.data.fd, &ev);
}
while (1)
{
puts("roud again");
nfds = epoll_wait(epfd, events, 5, 10000);
for (i=0; i<nfds; ++i)
{
memset(buffer, 0, MAXBUF);
read(events[i].data.fd, buffer, MAXBUF);
puts(buffer);
}
}