关于selece,poll和epoll模型的总结

   首先select,poll,epoll都是IO多路复用的机制,先是监听多个文件的描述符fd,一旦某个fd就绪后,就可以进行相应的读写操作。select,poll,epoll从本质上讲都是同步I/O,都需要在读写时间就绪之后自己负责读写,这个过程时阻塞的。


上图就是多路复用模型,同其他io不同的是,IO多路复用一次可以等待多个文件描述符,大大提高了等待数据准备好的时间的效率,此时系统提供了三个系统调用来完成等的效率

1.select模型
select用于io复用,用于监视多个文件描述符的集合,判断是否有符合条件的事件发生

1.1原理
使用select函数可以先对需要操作的文件描述符进行查询,查看是否目标文件描述符可以进行读写或者错误操作,然后当文件描述符满足操作的条件时才进行真正的io操作

1.2函数原型
int select(int nfds,//最大文件描述符+1
fd_set *readfds,//监控的所有读文件描述符集合
fd_set *writefds,//写集合
fd_set *exceptfds,//异常集合
struct timeval *timeout);//超时时间
其返回值:> 0 正常,== 0 超时,== -1 发生错误

从某个文件描述符的集合中取出某个文件描述符
void FD_CLR(int fd, fd_set *set);

测试某个文件描述符是否在某个集合中
int FD_ISSET(int fd, fd_set *set);

向某个文件描述符集合中加入文件描述符
void FD_SET(int fd, fd_set *set);

清理文件描述符集合
void FD_ZERO(fd_set *set);
注意:文件描述符的集合存在最大的限制,其最大值为FD_SETSIZE=1024

1.3使用方法
用fd_set(一个数组)来保存要监视的fd,用FD_SET来往里添加想要监视的fd,轮询(死循环)调用select,当监视的信息发生变化了,select解除阻塞状态走到下面,再遍历所有fd,用FD_ISSET确定是想要的fd发生了变化(比如当i=serv_sock时我就调accept去创建用于传数据的套接字,如果不等于就说明是有要接收的数据,直接read就行)。注意:如果新建了一个套接字,需要也用FD_SET把他加入fd_set作为监视的fd。

例如用select来实现echo服务器

客户端和服务端的功能如下:
客户端从标准输入读入一行,发送到服务端
服务端从网络读取一行,然后输出到客户端
客户端收到服务端的响应,输出这一行到标准输出echo服务器和客户端

server.c  服务端#include  <unistd.h>
#include  <sys/types.h>       /* basic system data types */
#include  <sys/socket.h>      /* basic socket definitions */
#include  <netinet/in.h>      /* sockaddr_in{} and other Internet defns */
#include  <arpa/inet.h>       /* inet(3) functions */
#include <sys/select.h>       /* select function*/#include <stdlib.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>#define MAXLINE 10240void handle(int * clientSockFds, int maxFds, fd_set* pRset, fd_set* pAllset);int  main(int argc, char **argv)
{//端口号int  servPort = 6888;//listenq指定的其实就是accept队列也就是ESTABLISHED状态的连接int listenq = 1024;int  listenfd, connfd;struct sockaddr_in cliaddr, servaddr;socklen_t socklen = sizeof(struct sockaddr_in);int nready, nread;char buf[MAXLINE];//存放和客户端通信的socket描述符int clientSockFds[FD_SETSIZE];//描述符集合fd_set allset, rset;//最大的描述符int maxfd;//创建socketlistenfd = socket(AF_INET, SOCK_STREAM, 0);if (listenfd < 0) {perror("socket error");return -1;}int opt = 1;//监听的端口变成可以复用的if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {perror("setsockopt error");}bzero(&servaddr, sizeof(servaddr));servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = htonl(INADDR_ANY);servaddr.sin_port = htons(servPort);//绑定if (bind(listenfd, (struct sockaddr*)&servaddr, socklen) == -1) {perror("bind error");exit(-1);}//监听if (listen(listenfd, listenq) < 0) {perror("listen error");return -1;}//初始化数组int i = 0;for (i = 0; i< FD_SETSIZE; i++){clientSockFds[i] = -1;}//清空描述符集合FD_ZERO(&allset);//将描述符加入集合FD_SET(listenfd, &allset);maxfd = listenfd;printf("echo server use select startup, listen on port %d\n", servPort);printf("max connection: %d\n", FD_SETSIZE);for (;;)  {rset = allset;//等待事件发生nready = select(maxfd + 1, &rset, NULL, NULL, NULL);if (nready < 0) {perror("select error");continue;}//处理客户端的连接if (FD_ISSET(listenfd, &rset)) {connfd = accept(listenfd, (struct sockaddr*) &cliaddr, &socklen);if (connfd < 0) {perror("accept error");continue;}sprintf(buf, "accept form %s:%d\n", inet_ntoa(cliaddr.sin_addr), cliaddr.sin_port);printf(buf, "");//将客户端连接的描述符加入数组for (i = 0; i< FD_SETSIZE; i++) {if (clientSockFds[i] == -1) {clientSockFds[i] = connfd;break;}}if (i == FD_SETSIZE) {fprintf(stderr, "too many connection, more than %d\n", FD_SETSIZE);close(connfd);continue;}if (connfd > maxfd)maxfd = connfd;//将描述符加入集合FD_SET(connfd, &allset);if (--nready <= 0)continue;}//处理客户端的数据收发handle(clientSockFds, maxfd, &rset, &allset);}
}void handle(int * clientSockFds, int maxFds, fd_set* pRset, fd_set* pAllset)
{int nread;int i;char buf[MAXLINE];for (i = 0; i< maxFds; i++) {if (clientSockFds[i] != -1) {if (FD_ISSET(clientSockFds[i], pRset)) {nread = read(clientSockFds[i], buf, MAXLINE);//读取客户端socket流if (nread < 0) {perror("read error");close(clientSockFds[i]);FD_CLR(clientSockFds[i], pAllset);clientSockFds[i] = -1;continue;}if (nread == 0) {printf("client close the connection\n");close(clientSockFds[i]);FD_CLR(clientSockFds[i], pAllset);clientSockFds[i] = -1;continue;}write(clientSockFds[i], buf, nread);//响应客户端有可能失败暂不处理}}}}
client.c  客户端#include  <unistd.h>
#include  <sys/types.h>       /* basic system data types */
#include  <sys/socket.h>      /* basic socket definitions */
#include  <netinet/in.h>      /* sockaddr_in{} and other Internet defns */
#include  <arpa/inet.h>       /* inet(3) functions */
#include <sys/select.h>       /* select function*/#include <stdlib.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>#define MAXLINE 10240
#define max(a,b)    ((a) > (b) ? (a) : (b))
//typedef struct sockaddr  SA;void handle(int sockfd);int main(int argc, char **argv)
{char * servInetAddr = "127.0.0.1";int servPort = 6888;char buf[MAXLINE];int connfd;struct sockaddr_in servaddr;if (argc == 2) {servInetAddr = argv[1];}if (argc == 3) {servInetAddr = argv[1];servPort = atoi(argv[2]);}if (argc > 3) {printf("usage: selectechoclient <IPaddress> <Port>\n");return -1;}connfd = socket(AF_INET, SOCK_STREAM, 0);bzero(&servaddr, sizeof(servaddr));servaddr.sin_family = AF_INET;servaddr.sin_port = htons(servPort);inet_pton(AF_INET, servInetAddr, &servaddr.sin_addr);if (connect(connfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0) {perror("connect error");return -1;}printf("welcome to selectechoclient\n");handle(connfd);     /* do it all */close(connfd);printf("exit\n");exit(0);
}void handle(int connfd)
{FILE* fp = stdin;char sendline[MAXLINE], recvline[MAXLINE];fd_set rset;FD_ZERO(&rset);int maxfds = max(fileno(fp), connfd) + 1;int nread;for (;;) {FD_SET(fileno(fp), &rset);FD_SET(connfd, &rset);if (select(maxfds, &rset, NULL, NULL, NULL) == -1) {perror("select error");continue;}if (FD_ISSET(connfd, &rset)) {//接收到服务器响应nread = read(connfd, recvline, MAXLINE);if (nread == 0) {printf("server close the connection\n");break;}else if (nread == -1) {perror("read error");break;}else {//server responsewrite(STDOUT_FILENO, recvline, nread);}}if (FD_ISSET(fileno(fp), &rset)) {//标准输入可读if (fgets(sendline, MAXLINE, fp) == NULL) {//eof exitbreak;}else {write(connfd, sendline, strlen(sendline));}}}
}

2.poll模型
检测文件描述符上,是否有某些事件发生。poll本质上和select没有区别,只是描述fd集合的方式不同,poll使用pollfd结构而不是select的fd_set结构

2.1 函数原型
需要包含头文件#include<poll.h>

*int poll(struct pollfd fds,unsigned int nfds,int timeout);
参数:
fds:是一个poll函数监听的struct pollfd结构类型的数组

pollfd结构体定义如下:
struct pollfd{
int fd;//文件描述符(socket描述符)
short events;//等待的事件
short revents;//实际发生了的事件
};
events和revents的取值是一样的,常用的事件:
POLLIN 有数据可读
POLLOUT 写数据不会导致阻塞
POLLMSGSIGPOLL 消息可用
POLLERR 指定的文件描述符发生错误

nfds :是fds的元素个数
timeout :表示poll函数的超时时间,单位是毫秒

注意:
timeout == 0 代表立即返回
timeout > 0 代表等待指定的毫秒数后,返回
timeout < 0 代表永不过期,就是阻塞
函数返回值同select

2.2使用步骤
a:pollfd的数组 fdlist;
b:初始化fdlist;
c:将监听fdlist中,并设置要监测的事件(POLLIN)
d:调用poll函数去监测
e:遍历fdlist集合,处理客户端连接或收发数据

例如一个简单的poll模型
程序将会等待3秒去监测是否有读事件发生,若满足触发条件就会在屏幕上输出所打字节,若3s内无读事件发生会显示超时

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<poll.h>
#include<strings.h>int main()
{struct pollfd poll_fd;//设置要检测的描述符,0是标准输入poll_fd.fd = 0;//设置检测的事件,POLLIN是读事件poll_fd.events = POLLIN;while(1){//3000代表等待3000毫秒(3s),等待检测的最大时间值int ret = poll(&poll_fd,1,3000);switch(ret){case 0://超时printf("poll timeout\n");break;case -1://错误printf("poll error\n");break;default:{//事件判断if(poll_fd.revents == POLLIN){char buffer[1024];bzero(buffer,sizeof(buffer));int nread = read(poll_fd.fd,buffer,sizeof(buffer));if(nread > 0){printf("stdin input %s\n",buffer);}}}}}return 0;
}

3.epoll模型
为实现高并发,在2.6内核中提出了epoll,是select和poll的增强版本,没有描述符限制。epoll使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件放到内核的一个事件表中,如此用户空间和内核空间的copy只需一次

3.1相关函数
epoll函数基于select和poll接口比较简单,一共3个函数。
a:创建一个epoll实例
int epoll_create(int size);
创建一个epoll的句柄,size是用来告诉内核所要监听的数目有多大
要注意的是,在创建好句柄后,其会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。

b:注册socket对应的事件
**int epoll_ctl(int epfd,int op,int fd,struct epoll_event event);

epfd:epoll上下文的描述符,就是epoll_create的返回值;

op:操作命令
EPOLL_CTL_ADD 向epoll监听集合中添加socket描述符
EPOLL_CTL_DEL 从epoll监听集合中删除socket描述符
EPOLL_CTL_MOD 修改

fd :socket描述符,对TCP来说就是accept函数的返回值;

event :在向epoll监听集合当中添加socket描述符的同时,为这个描述符绑定一个触发事件。
可以是以下宏的集合:
1》EPOLLIN : 表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
2》 EPOLLOUT: 表示对应的文件描述符可以写;
3》EPOLLPRI: 表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
4》EPOLLERR: 表示对应的文件描述符发生错误;
5》EPOLLHUP: 表示对应的文件描述符被挂断;
6》EPOLLET: 将 EPOLL设为边缘触发(Edge Triggered)模式(默认为水平触发),这是相对于水平触发(Level Triggered)来说的。
7》EPOLLONESHOT: 只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里

c:等待epfd_代表的epoll实例中监听的事件发生,events指针返回已经准备好的事件,最多有maxevents个,参数maxevents必须大于0
int epoll_wait(int epfd,struct epoll_event *events,int maxevents,int timeout);//返回值是需要处理的事件数目

epfd epoll上下文描述符
events 用于保存监听集合当中所有的触发事件
maxevents 最大的监听数
timeout 监听等待超时(ms)
设置timeout_为-1则epoll_wait()会一直阻塞,设置为0则会立即返回。

3.2关于TCP服务器的思路
1》创建服务器的socket
2》创建epoll实例
3》使用epoll_ctl设置
4》监听服务器socket是否有新的客户端建立连接(即是否可读)

while(true)
{epoll_wait();for 每个事件{ if(服务器socket可读){accept()  把新的客户端socket加入epoll监听事件   }else {  read()send()                  }}
}

此刻发送数据的时候,调用了send函数,若用epoll_wait等待socket可写了后再发数据,就不会造成阻塞了

例如一个简单的epoll TCP通信实例
客户端:通过输入ip地址和端口去连接服务器;等待键盘输入;发送数据到服务器并且接收服务器回射的数据
服务器:等待客户端连接;接收来自客户端的数据并且回射

客户端
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>void Usage()
{printf("usage: ./client [ip] [port]\n"); //输入ip地址和端口
}int main(int argc, char* argv[]) {if (argc != 3) {Usage();return 1;}//服务器socket地址结构struct sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_addr.s_addr = inet_addr(argv[1]);addr.sin_port = htons(atoi(argv[2]));int fd = socket(AF_INET, SOCK_STREAM, 0);if (fd < 0) {perror("socket");return 1;}//连接服务器int ret = connect(fd, (struct sockaddr*)&addr, sizeof(addr));if (ret < 0) {perror("connect");return 1;}while(1){printf("> ");fflush(stdout);//刷新输出缓冲区,即将缓冲区内容输出到屏幕上char buf[1024] = {0};read(0, buf, sizeof(buf) - 1);ssize_t write_size = write(fd, buf, strlen(buf));//写函数if (write_size < 0) {perror("write");continue;}ssize_t read_size = read(fd, buf, sizeof(buf) - 1);//读函数if (read_size < 0) {perror("read");continue;}if (read_size == 0) {printf("server close\n");break;}printf("server say: %s", buf);}close(fd);return 0;
}
服务器
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <string.h>//处理客户端连接accept
void handleConnect(int fdListen,int epfd)
{struct sockaddr_in addr;socklen_t nLen = sizeof(addr);bzero(&addr,sizeof(addr));int fdClient = accept(fdListen,(struct sockaddr*)&addr,&nLen);if(fdClient < 0){perror("accept");return;}//显示登录信息printf("client %s:%d connected!\n",inet_ntoa(addr.sin_addr),ntohs(addr.sin_port));struct epoll_event event;event.data.fd = fdClient;event.events = EPOLLIN;//注册事件int ret = epoll_ctl(epfd,EPOLL_CTL_ADD,fdClient,&event);if(ret < 0){perror("epoll_ctl");return;}return;
}//处理客户端数据收发
void handleClientRW(int fdClient,int epfd)
{char buffer[1024] = {0};int nsize = read(fdClient,buffer,sizeof(buffer));if(nsize < 0){//errorperror("read");return;}if(nsize == 0){//对方closeclose(fdClient);//将事件删除epoll_ctl(epfd,EPOLL_CTL_DEL,fdClient,NULL);printf("client say good bye!\n");return;}printf("client say:%s",buffer);//将数据发送给客户端write(fdClient,buffer,strlen(buffer));
}int main()
{int fdListen;struct sockaddr_in  srvaddr;srvaddr.sin_family = AF_INET;srvaddr.sin_addr.s_addr = inet_addr("127.0.0.1");srvaddr.sin_port = htons(6888);//创建socketfdListen = socket(PF_INET,SOCK_STREAM,0);if(fdListen < 0){perror("socket error:");return -1;}//绑定int nRet = bind(fdListen,(struct sockadd*)&srvaddr,sizeof(srvaddr));if(nRet < 0){perror("bind error:");return -1;}//监听int nListen = listen(fdListen,5);if(nListen < 0){perror("listen error:");return -1;}printf("server listening at 6888 port!\n");//创建epoll实例int epfd = epoll_create(10);if(epfd < 0){close(fdListen);perror("epoll_create:");return -1;}//用于添加事件struct epoll_event event;event.data.fd = fdListen;event.events = EPOLLIN;//注册事件int ret = epoll_ctl(epfd,EPOLL_CTL_ADD,fdListen,&event);if(ret < 0){close(fdListen);perror("epoll_ctl:");return -1;}//处理客户端数据收发//存放检测到的事件struct epoll_event events[10];while(1){//等待检测的事件发生,返回值是发生事件的数量int nNums = epoll_wait(epfd,&events,10,-1);if(nNums < 0){perror("epoll_wait");continue;}if(nNums == 0){perror("epoll_wait timeout");continue;}int i;for(i=0;i<nNums;++i){if(events[i].data.fd == fdListen){//处理客户端连接accepthandleConnect(fdListen,epfd);}else{//处理客户端数据收发handleClientRW(events[i].data.fd,epfd);}}}//关闭句柄close(epfd);return 0;
}

总结:基于以上内容小结三者的主要区别
1.较于select,poll和epoll没有socket的FD_SETSIZE(1024)个数的限制
2.不同于select使用三个位图来表示三个fdset的方式,poll使用一个pollfd的指针实现
3.内核中select和poll的实现采用轮询来处理,轮询的fd越多耗时越长
4.epoll的实现基于回调的,如果fd有期望的事件发生就通过回调函数将其加入到epoll的就绪队列中去,即epoll只关心活跃的fd(因此效率高)
5.内核/用户空间拷贝问题,当内核把fd消息通知给用户空间时,select和poll采用内存拷贝的方法,而epoll采用的是内存共享的方式

小结下select模型,poll模型和epoll模型相关推荐

  1. 学习C++项目——select模型,poll模型和epoll模型

    学习计算机网络编程 一.思路和学习方法   本文学习于:C语言技术网(www.freecplus.net),在 b 站学习于 C 语言技术网,并加以自己的一些理解和复现,如有侵权会删除.   接下来应 ...

  2. 「软件项目管理」成本估算模型——Walston-Felix模型和COCOMO Ⅱ模型

    Walston-Felix模型和COCOMO Ⅱ模型 序言 一.Walston-Felix模型 1. 公式 2. 举例 二.COCOMO模型(Constructive Cost Model) 1. 模 ...

  3. Reactor模型和Proactor模型:同步IO与异步IO

    Table of Contents 服务端的线程模型 2种fd 3种事件 Reactor模型-同步I/O 1.单Reactor单线程模型 2.单Reactor多线程模型 3.主从Reactor多线程模 ...

  4. 彻底搞懂Reactor模型和Proactor模型

    在高性能的I/O设计中,有两个著名的模型:Reactor模型和Proactor模型,其中Reactor模型用于同步I/O,而Proactor模型运用于异步I/O操作. 服务端的线程模型 无论是Reac ...

  5. ologit模型与logit_Logit模型和Logistic模型有什么区别?

    之前在<Logit究竟是个啥?--离散选择模型之三>一文中提过,Logit应该理解成Log-it,这里的it指的是Odds("胜率",等于P/1-P).一个Logit变 ...

  6. 【word2vec】篇三:基于Negative Sampling 的 CBOW 模型和 Skip-gram 模型

    系列文章: [word2vec]篇一:理解词向量.CBOW与Skip-Gram等知识 [word2vec]篇二:基于Hierarchical Softmax的 CBOW 模型和 Skip-gram 模 ...

  7. 【word2vec】篇二:基于Hierarchical Softmax的 CBOW 模型和 Skip-gram 模型

    文章目录 CBOW 模型 基本结构 目标函数 梯度计算 Skip-gram 模型 基本结构 梯度计算 优缺点分析 系列文章: [word2vec]篇一:理解词向量.CBOW与Skip-Gram等知识 ...

  8. 基于 OData 模型和 JSON 模型的 SAP UI5 表格控件行项目的添加和删除实现

    这是 Jerry 2021 年的第 62 篇文章,也是汪子熙公众号总共第 339 篇原创文章. 龟虽寿曹操神龟虽寿,犹有竟时:腾蛇乘雾,终为土灰.老骥伏枥,志在千里:烈士暮年,壮心不已.盈缩之期,不但 ...

  9. lr模型和dnn模型_建立ML或DNN模型的技巧

    lr模型和dnn模型 机器学习 (Machine Learning) Everyone can fit data into any model machine learning or deep lea ...

最新文章

  1. 今日头条反爬措施形同虚设,论多平台协同在安全方面的重要性
  2. Swift Property Wrapper 属性包装器
  3. 数据列表DataList模板之实例
  4. s3c2440芯片累加汇编语言,s3c2440 --跑马灯 C+汇编代码
  5. iOS 手机摇一摇功能
  6. 现在工作和技术一般,想下班后充充电多学点东西。然而事实却相反,怎么让自己的学习更加有毅力?...
  7. 【Allennlp】: Allennlp中的test_data
  8. [转]overflow解决float浮动后高度自适应问题
  9. 工资计算系统设计实现
  10. 电脑屏幕网页字体大小怎么调整?
  11. WorldFirst万里汇推出港币和离岸人民币账户!
  12. 白杨SEO:QQ群SEO是什么?QQ群排名如何做引流与营销?【举例】
  13. linux dot命令,DOT语言使用笔记(1)
  14. 洗碗机,加速中国化才能更适合中国厨房
  15. 程序员如何避免面向监狱编程?避免踩雷!
  16. 达梦数据库查询模式名,表名,字段名
  17. uIP中国的协议文件:Ch01
  18. C语言获取MTK平台系统资源信息(CPU/GPU/fps/温度等),保存为表格形式输出
  19. 算法的数值稳定性实验报告用c语言,数值计算实验教案.doc
  20. Abaqus GUI程序开发之常用的Abaqus内核指令(一)

热门文章

  1. 关于C++使极域电子教室最小化的思想
  2. 解决使用vivadoHLS视频库ug1233教程49页编译失败
  3. 中国科学院大学21年计算机考研情况 信工所、成都计算所情况
  4. 学习SVG(七)图案和渐变填充
  5. HIVE 窗口函数和分析函数
  6. 做节能减排的企业怎么申请碳中和服务认证?
  7. Tsu,Tco,Th,Tpd的概念
  8. sql中的datetime在java,Dapper SqlDateTime在SQL Server中使用GETDATE()溢出
  9. Java job interview:百度高管持续“洗牌 ” ,市值仅为阿里腾讯的 1/10
  10. 【插件】浏览器广告拦截插件| 浏览器搜索广告横飞怎么办