日常学习记录8:select、poll、epoll

您所在的位置:网站首页 select是哪个 日常学习记录8:select、poll、epoll

日常学习记录8:select、poll、epoll

#日常学习记录8:select、poll、epoll| 来源: 网络整理| 查看: 265

select、poll、epoll是后端必掌握知识,这篇文章来盘一下。

1. select、poll、epoll是什么东西

select、poll、epoll是linux下I/O多路复用的三个系统调用

那什么是I/O多路复用?见日常学习记录7:I/O模型总结

2. select模型

select维护了一个记录要监听的socket的集合fd_set,当调用select()时,由内核根据IO状态在线修改fd_set的内容,由此来通知哪些被监视的socket或文件可读。

(1)每次调用select都会把fd从用户态拷贝到内核态,因此效率较低。

(2)select会在线改变fd_set,所以每次调用都要拷贝备份。

(3)一个进程所打开的fd是有限制的,由FD_SETSIZE设置,默认值是1024,因此select有最大并发数限制。

(4)select只通知有socket准备好,但不直接指出是哪些,因此每次都要遍历所有的fd来查看。

3. poll模型

poll维护了pollfd链表来记录要监听fd,它没有了select的最大并发数限制,但是其他问题没有解决(每次都要拷贝、遍历所有列表)。

4. epoll模型

epoll维护了一个链表用来存放已经就绪的事件,维护了一个红黑树来管理要监视的事件,这样一来每次有事件就绪时就不需要遍历所有的fd看哪个是就绪的了,直接用链表里的即可。

epoll把要监视的事件挂载到红黑树上,并且注册与网卡驱动关联的回调函数,当事件发生时就调用回调函数把事件加入链表。

epoll的高效 = 红黑树 + 链表 + 回调函数

支持LT水平触发与ET边沿触发,select/poll只支持LT水平触发。 LT水平触发:只要事件没有处理完毕,每一次epoll_wait都触发该事件。 ET边沿触发:无论事件是否处理完毕,仅触发一次。

(1)epoll也没有最大并发数限制,它所支持的文件描述符上限是整个系统最大可以打开的文件数目,例如在1GB内存的机器上,这个限制大概为10万左右。

(2)红黑树是维护在内核里的,因此没有了从用户态到内核态的拷贝,提高了效率。

(3)就绪了的事件都已经在链表上,因此没有了遍历所有事件,只需要取链表里的事件即可。

在这里插入图片描述

5. 代码记录

(1)select

#include #include #include #include #include #include #include // 初始化服务端的监听端口。 int initserver(int port); int main(int argc,char *argv[]) { if (argc != 2) { printf("usage: ./tcpselect port\n"); return -1; } // 初始化服务端用于监听的socket。 int listensock = initserver(atoi(argv[1])); printf("listensock=%d\n",listensock); if (listensock // 调用select函数时,会改变socket集合的内容,所以要把socket集合保存下来,传一个临时的给select。 fd_set tmpfdset = readfdset; int infds = select(maxfd+1,&tmpfdset,NULL,NULL,NULL); // printf("select infds=%d\n",infds); // 返回失败。 if (infds printf("select() timeout.\n"); continue; } // 检查有事情发生的socket,包括监听和客户端连接的socket。 // 这里是客户端的socket事件,每次都要遍历整个集合,因为可能有多个socket有事件。 for (int eventfd=0; eventfd // 如果发生事件的是listensock,表示有新的客户端连上来。 struct sockaddr_in client; socklen_t len = sizeof(client); int clientsock = accept(listensock,(struct sockaddr*)&client,&len); if (clientsock // 客户端有数据过来或客户端的socket连接被断开。 char buffer[1024]; memset(buffer,0,sizeof(buffer)); // 读取客户端的数据。 ssize_t isize=read(eventfd,buffer,sizeof(buffer)); // 发生了错误或socket被对方关闭。 if (isize for (int ii=maxfd;ii>0;ii--) { if (FD_ISSET(ii,&readfdset)) { maxfd = ii; break; } } printf("maxfd=%d\n",maxfd); } continue; } printf("recv(eventfd=%d,size=%d):%s\n",eventfd,isize,buffer); // 把收到的报文发回给客户端。 write(eventfd,buffer,strlen(buffer)); } } } return 0; } // 初始化服务端的监听端口。 int initserver(int port) { int sock = socket(AF_INET,SOCK_STREAM,0); if (sock printf("bind() failed.\n"); close(sock); return -1; } if (listen(sock,5) != 0 ) { printf("listen() failed.\n"); close(sock); return -1; } return sock; }

(2)poll

#include #include #include #include #include #include #include #include // ulimit -n #define MAXNFDS 1024 // 初始化服务端的监听端口。 int initserver(int port); int main(int argc,char *argv[]) { if (argc != 2) { printf("usage: ./tcppoll port\n"); return -1; } // 初始化服务端用于监听的socket。 int listensock = initserver(atoi(argv[1])); printf("listensock=%d\n",listensock); if (listensock int infds = poll(fds,maxfd+1,5000); // printf("poll infds=%d\n",infds); // 返回失败。 if (infds printf("poll() timeout.\n"); continue; } // 检查有事情发生的socket,包括监听和客户端连接的socket。 // 这里是客户端的socket事件,每次都要遍历整个集合,因为可能有多个socket有事件。 for (int eventfd=0; eventfd // 如果发生事件的是listensock,表示有新的客户端连上来。 struct sockaddr_in client; socklen_t len = sizeof(client); int clientsock = accept(listensock,(struct sockaddr*)&client,&len); if (clientsock printf("clientsock(%d)>MAXNFDS(%d)\n",clientsock,MAXNFDS); close(clientsock); continue; } fds[clientsock].fd=clientsock; fds[clientsock].events=POLLIN; fds[clientsock].revents=0; if (maxfd printf("client(eventfd=%d) disconnected.\n",eventfd); close(eventfd); // 关闭客户端的socket。 fds[eventfd].fd=-1; // 重新计算maxfd的值,注意,只有当eventfd==maxfd时才需要计算。 if (eventfd == maxfd) { for (int ii=maxfd;ii>0;ii--) { if ( fds[ii].fd != -1) { maxfd = ii; break; } } printf("maxfd=%d\n",maxfd); } continue; } printf("recv(eventfd=%d,size=%d):%s\n",eventfd,isize,buffer); // 把收到的报文发回给客户端。 write(eventfd,buffer,strlen(buffer)); } } } return 0; } // 初始化服务端的监听端口。 int initserver(int port) { int sock = socket(AF_INET,SOCK_STREAM,0); if (sock printf("bind() failed.\n"); close(sock); return -1; } if (listen(sock,5) != 0 ) { printf("listen() failed.\n"); close(sock); return -1; } return sock; }

(3)epoll

#include #include #include #include #include #include #include #include #include #include #include #define MAXEVENTS 100 // 把socket设置为非阻塞的方式。 int setnonblocking(int sockfd); // 初始化服务端的监听端口。 int initserver(int port); int main(int argc,char *argv[]) { if (argc != 2) { printf("usage:./tcpepoll port\n"); return -1; } // 初始化服务端用于监听的socket。 int listensock = initserver(atoi(argv[1])); printf("listensock=%d\n",listensock); if (listensock struct epoll_event events[MAXEVENTS]; // 存放有事件发生的结构数组。 // 等待监视的socket有事件发生。 int infds = epoll_wait(epollfd,events,MAXEVENTS,-1); // printf("epoll_wait infds=%d\n",infds); // 返回失败。 if (infds printf("epoll_wait() timeout.\n"); continue; } // 遍历有事件发生的结构数组。 for (int ii=0;ii // 如果发生事件的是listensock,表示有新的客户端连上来。 struct sockaddr_in client; socklen_t len = sizeof(client); int clientsock = accept(listensock,(struct sockaddr*)&client,&len); if (clientsock // 客户端有数据过来或客户端的socket连接被断开。 char buffer[1024]; memset(buffer,0,sizeof(buffer)); // 读取客户端的数据。 ssize_t isize=read(events[ii].data.fd,buffer,sizeof(buffer)); // 发生了错误或socket被对方关闭。 if (isize int sock = socket(AF_INET,SOCK_STREAM,0); if (sock printf("bind() failed.\n"); close(sock); return -1; } if (listen(sock,5) != 0 ) { printf("listen() failed.\n"); close(sock); return -1; } return sock; } // 把socket设置为非阻塞的方式。 int setnonblocking(int sockfd) { if (fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFD, 0)|O_NONBLOCK) == -1) return -1; return 0; } 参考文献:

0.C语言技术网–代码来源 1.深度理解select、poll和epoll 2.Linux epoll模型详解及源码分析 3.探讨epoll原理(红黑树、rdlist的实现)



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3