select select
实现多路复用的方式是,将已连接的 socket
都放到一个文件描述符集合,然后调用 select
函数将文件描述符集合拷贝到内核里,让内核来检查是否有网络事件产生,通过遍历文件描述符集合的方式,当检查到有事件产生后,将此 socket
标记为可读或可写, 接着再把整个文件描述符集合拷贝回用户态里,然后用户态还需要再通过遍历的方法找到可读或可写的 socket
,然后再对其处理。
所以,对于 select
这种方式,需要进行 2 次「遍历」文件描述符集合 ,一次是在内核态里,一个次是在用户态里 ,而且还会发生 2 次「拷贝」文件描述符集合 ,先从用户空间传入内核空间,由内核修改后,再传出到用户空间中。
select 函数原型 1 2 3 #include <sys/select.h> int select (int nfds, fd_set *restrict readfds, fd_set *restrict writefds, fd_set *restrict errorfds, struct timeval *restrict timeout) ;
返回值
若有就绪描述符则为其数目,若超时则为 0
,若出错则为 -1
参数
maxfd
: 待测试的描述符基数,它的值是待测试的最大描述符加 1
readfds
:读描述符集合
writefds
:写描述符集合
errorfds
:异常描述符集合
timeout
: 超时设置
操作描述集合 1 2 3 4 void FD_ZERO (fd_set *fdset) ; void FD_SET (int fd, fd_set *fdset) ; void FD_CLR (int fd, fd_set *fdset) ; int FD_ISSET (int fd, fd_set *fdset) ;
FD_ZERO
清空描述符集合;
FD_SET
向描述符集合增加 fd
;
FD_CLR
向描述符集合删除 fd
;
FD_ISSET
判断描述符集合中的 fd
是否有响应;
超时设置 timeval
结构体时间:
1 2 3 4 struct timeval { long tv_sec; long tv_usec; };
最后一个参数,可以设置 3
种值:
设置成空 (NULL)
,表示如果没有 I/O
事件发生,则 select
一直等待下去
设置一个非零的值,等待超时时间阻塞返回
tv_sec
和 tv_usec
都设置成 0
,表示不等待,检测完毕立即返回
使用 🌰 在使用 select
时, 两个注意点:
描述符基数是当前最大描述符 +1;
每次 select
调用完成之后,要重置待测试集合。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 int socket_fd = ...;fd_set ready_fds; while (true ) { FD_ZERO(&read_fds); FD_SET(socket_fd, &read_fds); int rc = select(socket_fd + 1 , &read_fds, NULL , NULL , NULL ); if (rc == -1 ) { perror("select" ); return 1 ; } if (FD_ISSET(socket_fd, &read_fds)) { ... } }
**select
有一个缺点,那就是所支持的文件描述符的个数是有限的。在 Linux
系统中,select
的默认最大值为 1024
**。
poll
poll
可以突破 select
文件描述符的个数限制, 函数原型如下:
1 int poll (struct pollfd *fds, unsigned long nfds, int timeout) ;
返回值
若有就绪描述符则为其数目,若超时则为 0
,若出错则为 -1
参数
fds
: pollfd
数组
nfds
: 描述 fds
数组的大小
timeout
: 超时设置, 单位 ms
pollfd
数组pollfd
结构如下:
1 2 3 4 5 struct pollfd { int fd; short events; short revents; };
fd
: 文件描述
events
: 待检测的事件类型
revents
: 响应的事件类型
events
类型的事件可以分为三大类。
第一类是可读事件,有以下几种:
1 2 3 4 #define POLLIN 0x0001 #define POLLPRI 0x0002 #define POLLRDNORM 0x0040 #define POLLRDBAND 0x0080
我们一般使用 POLLIN
, 系统内核通知套接字缓冲区已准备好,通过 read
函数执行读操作不会被阻塞。
第二类是可写事件,有以下几种:
1 2 3 #define POLLOUT 0x0004 #define POLLWRNORM POLLOUT #define POLLWRBAND 0x0100
我们一般使用 POLLOUT
, 系统内核通知套接字缓冲区已准备好,通过 write
函数执行写操作不会被阻塞。
还有另一大类是错误事件,没有办法通过 poll
向系统内核递交检测请求,只能通过 returned events
来加以检测:
1 2 3 #define POLLERR 0x0008 #define POLLHUP 0x0010 #define POLLNVAL 0x0020
不想对某个 pollfd
结构进行事件检测 ,可以把它对应的 pollfd
结构的 fd
成员设置成一个负值 。这样,poll
函数将忽略这样的 events
事件,检测完成以后,所对应的returned events
的成员值也将设置为 0
。
超时设置
< 0
,表示如果没有 I/O
事件发生,则 poll
一直等待下去
> 0
,等待超时时间阻塞返回
= 0
,表示不等待,检测完毕立即返回
使用 🌰 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 #define INIT_SIZE 128 int main (int argc, char **argv) { int listen_fd, connected_fd; int ready_number; ssize_t n; char buf[MAXLINE]; struct sockaddr_in client_addr ; listen_fd = tcp_server_listen(SERV_PORT); struct pollfd event_set [INIT_SIZE ]; event_set[0 ].fd = listen_fd; event_set[0 ].events = POLLRDNORM; int i; for (i = 1 ; i < INIT_SIZE; i++) { event_set[i].fd = -1 ; } for (;;) { if ((ready_number = poll(event_set, INIT_SIZE, -1 )) < 0 ) { error(1 , errno, "poll failed " ); } if (event_set[0 ].revents & POLLRDNORM) { socklen_t client_len = sizeof (client_addr); connected_fd = accept(listen_fd, (struct sockaddr *) &client_addr, &client_len); for (i = 1 ; i < INIT_SIZE; i++) { if (event_set[i].fd < 0 ) { event_set[i].fd = connected_fd; event_set[i].events = POLLRDNORM; break ; } } if (i == INIT_SIZE) { error(1 , errno, "can not hold so many clients" ); } if (--ready_number <= 0 ) continue ; } for (i = 1 ; i < INIT_SIZE; i++) { int socket_fd; if ((socket_fd = event_set[i].fd) < 0 ) continue ; if (event_set[i].revents & (POLLRDNORM | POLLERR)) { if ((n = read(socket_fd, buf, MAXLINE)) > 0 ) { if (write(socket_fd, buf, n) < 0 ) { error(1 , errno, "write error" ); } } else if (n == 0 || errno == ECONNRESET) { close(socket_fd); event_set[i].fd = -1 ; } else { error(1 , errno, "read error" ); } if (--ready_number <= 0 ) break ; } } } }
epoll