本文,记录多路IO复用的使用。
顺序
- 接口示例(CS模型示例)
- 应用模型(事件驱动)
- 使用事件驱动模型实现高效稳定的网络服务器程序,链接
- 对比分析(select和epoll)
- 内核源码(select和epoll)
接口示例
select
void
str_cli(FILE *fp, int sockfd)
{
int maxfdp1;
fd_set rset;
char sendline[MAXLINE], recvline[MAXLINE];
FD_ZERO(&rset);
for ( ; ; ) {
FD_SET(fileno(fp), &rset);
FD_SET(sockfd, &rset);
maxfdp1 = max(fileno(fp), sockfd) + 1;
Select(maxfdp1, &rset, NULL, NULL, NULL);
if (FD_ISSET(sockfd, &rset)) { /* socket is readable */
if (Readline(sockfd, recvline, MAXLINE) == 0)
err_quit("str_cli: server terminated prematurely");
Fputs(recvline, stdout);
}
if (FD_ISSET(fileno(fp), &rset)) { /* input is readable */
if (Fgets(sendline, MAXLINE, fp) == NULL)
return; /* all done */
Writen(sockfd, sendline, strlen(sendline));
}
}
}
epoll
ev.events = EPOLLIN;
ev.data.fd = listen_sock;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listen_sock, &ev) == -1) {
perror("epoll_ctl: listen_sock");
exit(EXIT_FAILURE);
}
for (;;) {
nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);
if (nfds == -1) {
perror("epoll_wait");
exit(EXIT_FAILURE);
}
for (n = 0; n < nfds; ++n) {
if (events[n].data.fd == listen_sock) {
conn_sock = accept(listen_sock,
(struct sockaddr *) &addr, &addrlen);
if (conn_sock == -1) {
perror("accept");
exit(EXIT_FAILURE);
}
setnonblocking(conn_sock);
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = conn_sock;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, conn_sock,
&ev) == -1) {
perror("epoll_ctl: conn_sock");
exit(EXIT_FAILURE);
}
} else {
do_use_fd(events[n].data.fd);
}
}
}
对比分析(区别、优缺点)
看总结推荐
- 先阅读理解源码例子的实现功能
- 内核源码,分析执行过程
select分析
- 观察发现,__FD_SETSIZE被定义为1024,所以检测fd_set最大值受限
- 调用select,将被监测的fd_set从进程空间拷贝到内核空间
- 事件监测过程中,轮询遍历所有fd_set集合,造成IO效率随FD数目增大而线性下降
- select返回,将就绪的fd_set从内核空间拷贝到进程空间
- 修改了原fd_set集合,导致不能重复利用,所以有每次select循环都需要重新将所有监测fd房间fd_set集合里面,导致应用程序繁琐冗余
- 导致下一次调用select都需要将新的fd_set再次拷贝,如此下去,造成多次重复拷贝
epoll分析
- epoll检测事件的数量,是根据系统内存和系统fd数量共同决定的,所以能够处理fd会非常大(/proc可查)
- epoll_ctl调用,会对所有fd_set进行一次从进程空间到内核空间的拷贝,不会在epoll_wait重复拷贝
- 事件检测过程,epoll_wait不会遍历所有,而是通过回调函数,将就绪fd放到一个就绪链表里面,epoll_wait醒来判断就绪链表是否为空
分析总结
- 是否多次重复拷贝
- 应用程序中,每次轮询检测调用select/epoll_wait是否需要重新设置fd_set
- 检测过程,是否遍历所有fd_set