I/O Mutiplexing poll 和 epoll
上一篇介绍了select的基本用法,接着来学习一下poll和epoll的基本用法。首先来看poll:
#include <sys/poll.h> int poll (struct pollfd *fds, unsigned int nfds, int timeout);
poll() 采用了struct pollfd 结构数组来保存关心的文件描述符,而不是像select一样使用三个fd_set ,pollfd结构体定义如下:
struct pollfd { int fd; /* file descriptor */ short events; /* requested events to watch */ short revents; /* returned events witnessed */ };
每一个pollfd结构体指定了一个被监视的文件描述符,fds数组中可以存放多个pollfd结构,而且数量不会像select的FD_SETSIZE一样被限制在1024或者2048 。数组中每个pollfd结构体的events域是监视该文件描述符的事件掩码,由用户来设置这个域。revents域是文件描述符的操作结果事件掩码,系统调用返回时设置这个域。events域中请求的任何事件都可能在revents域中返回。我们可以设置如下事件:
POLLIN:有数据可读。
POLLRDNORM:有普通数据可读。
POLLRDBAND:有优先数据可读。
POLLPRI:有紧迫数据可读。
------------------------------------------------------------
POLLOUT:写数据不会导致阻塞。
POLLWRNORM:写普通数据不会导致阻塞。
POLLWRBAND:写优先数据不会导致阻塞。
此外,revents域中还可能返回下列事件:
POLLERR:指定的文件描述符发生错误。
POLLHUP:指定的文件描述符挂起事件。
POLLNVAL:指定的文件描述符非法。
注意:只能作为描述字的返回结果存储在revents中,而不能作为测试条件用于events中。
其中POLLIN | POLLPRI等价于select()的读事件,POLLOUT | POLLWRBAND等价于select()的写事件。POLLIN等价于POLLRDNORM | POLLRDBAND,而POLLOUT则等价于POLLWRNORM。假如,要同时监视一个文件描述符是否可读和可写,我们可以设置events为POLLIN | POLLOUT。在poll返回时,我们可以检查revents中的标志,对应于文件描述符请求的events结构体。如果POLLIN事件被设置,则文件描述符可以被读取而不阻塞。如果POLLOUT被设置,则文件描述符可以写入而不导致阻塞。这些标志并不是互斥的:它们可能被同时设置,表示这个文件描述符的读取和写入操作都会正常返回而不阻塞。
timeout参数指定等待的毫秒数,无论I/O是否准备好,超时时间一到poll都会返回。timeout指定为负数值表示无限超时,UNPv1 中使用的INFTIM 宏貌似现在已经废弃,因此如果要设置无限等待,直接将timeout赋值为-1;timeout为0指示poll调用立即返回并列出准备好I/O的文件描述符,但并不等待其它的事件。
成功时,poll()返回结构体中revents域不为0的文件描述符个数;如果在超时前没有任何事件发生,poll()返回0;失败时,poll()返回-1。
//pollEcho.cpp
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <vector>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <poll.h>
#include <stropts.h>
#include <netdb.h> #define PORT 1314
#define MAX_LINE_LEN 1024 int main()
{
struct sockaddr_in cli_addr, server_addr;
socklen_t addr_len;
int one,flags,nrcv,nwrite,nready; int listenfd,connfd;
char buf[MAX_LINE_LEN],addr_str[INET_ADDRSTRLEN]; std::vector<struct pollfd> pollfdArray;
struct pollfd pfd; bzero(&server_addr, sizeof server_addr);
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
server_addr.sin_addr.s_addr = htonl(INADDR_ANY); listenfd = socket(AF_INET, SOCK_STREAM, ); if( listenfd < )
{
printf("listen error: %s \n", strerror(errno));
exit();
} one = ;
setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR, &one, sizeof one); flags = fcntl(listenfd,F_GETFL,);
fcntl(listenfd, F_SETFL, flags | O_NONBLOCK); if(bind(listenfd,reinterpret_cast<struct sockaddr *>(&server_addr),sizeof(server_addr)) < )
{
printf("bind error: %s \n", strerror(errno));
exit();
} listen(listenfd, ); pfd.fd = listenfd;
pfd.events = POLLIN; pollfdArray.push_back(pfd); while()
{
nready = poll(&(*pollfdArray.begin()), pollfdArray.size(), -); if( nready < )
{
printf("poll error: %s \n", strerror(errno));
} if( pollfdArray[].revents & POLLIN)
{
addr_len = sizeof cli_addr;
connfd = accept(listenfd, reinterpret_cast<struct sockaddr *>(&cli_addr), &addr_len); if( connfd < )
{
if( errno != ECONNABORTED || errno != EWOULDBLOCK || errno != EINTR)
{
printf("accept error: %s \n", strerror(errno));
continue;
}
} printf("recieve from : %s at port %d\n", inet_ntop(AF_INET,&cli_addr.sin_addr,addr_str,INET_ADDRSTRLEN),cli_addr.sin_port); flags = fcntl(connfd, F_GETFL, );
fcntl(connfd,F_SETFL, flags | O_NONBLOCK); bzero(&pfd, sizeof pfd); pfd.fd = connfd;
pfd.events = POLLIN; pollfdArray.push_back(pfd); if(--nready < )
{
continue;
} } for( unsigned int i = ; i < pollfdArray.size(); i++) // i from 1 not 0
{
pfd = pollfdArray[i]; if(pfd.revents & (POLLIN | POLLERR))
{
memset(buf, , MAX_LINE_LEN);
if( (nrcv = read(pfd.fd, buf, MAX_LINE_LEN)) < )
{
if(errno != EWOULDBLOCK || errno != EAGAIN || errno != EINTR)
{
printf("read error: %s\n",strerror(errno));
}
}
else if( == nrcv)
{
close(pfd.fd);
pollfdArray.erase(pollfdArray.begin() + i);
}
else
{
printf("nrcv: %s\n",buf);
nwrite = write(pfd.fd, buf, nrcv);
if( nwrite < )
{
if(errno != EAGAIN || errno != EWOULDBLOCK)
printf("write error: %s\n",strerror(errno));
}
printf("nwrite = %d\n",nwrite);
} }
}
}
return ;
}
以上代码操作的文件描述符都设置成为了非阻塞的状态,这也是为了更好的配合I/O multiplexing 的执行,试想如果read 或者 write 阻塞在某个描述符上,I/O multiplexing 就失去了真正的意义了,因为此时select/poll 函数就无法处理其它描述符产生的事件了。但是只要设置为非阻塞就够了吗? 这显然是还不够的,后面会专门写一篇文章对非阻塞的I/O multiplexing 进行完善。
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
epoll是Linux下多路复用IO接口select/poll的增强版本,它能显著减少程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率,因为它不会复用文件描述符集合来传递结果而迫使开发者每次等待事件之前都必须重新准备要被侦听的文件描述符集合,另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。
epoll的使用与select/poll不同,它是由一组系统调用组成:
#include<sys/epoll.h>
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);
第一个函数 epoll_create() 创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大,其实size参数内核不会用到,只是开发者自己提醒自己的一个标记。epoll对监听的描述符数目没有限制,它所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于2048,举个例子,在1GB内存的机器上大约是10万左右,具体数目可以cat /proc/sys/fs/file-max察看,一般来说这个数目和系统内存关系很大,在我的机器上这个值为:149197.
第二个函数 epoll_ctl() 是epoll的事件注册函数,它不同于select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。第一个参数是epoll_create()的返回值,第二个参数表示动作,用三个宏来表示:
EPOLL_CTL_ADD:注册新的fd到epfd中;
EPOLL_CTL_MOD: 修改已经注册的fd监听事件;
EPOLL_CTL_DEL: 从epfd中删除一个fd;
第三个参数是需要监听的fd,第四个参数是告诉内核需要监听什么事,struct epoll_event结构如下:
struct epoll_event {
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
其中epoll_data_t 结构如下:
typedef union epoll_data {
void *ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
} epoll_data_t;
注意这是一个union 结构。
events可以是以下几个宏的集合:
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
这里介绍一下边沿触发和水平触发(epoll默认使用水平触发):
LT 电平触发(高电平触发):
EPOLLIN 事件
内核中的某个socket接收缓冲区 为空 低电平
内核中的某个socket接收缓冲区 不为空 高电平
EPOLLOUT 事件
内核中的某个socket发送缓冲区 不满 高电平
内核中的某个socket发送缓冲区 满 低电平
ET 边沿触发:
低电平 -> 高电平 触发
高电平 -> 低电平 触发
推荐使用默认的水平触发。
第三个函数epoll_wait() 等待事件的产生,类似于select()调用。参数events用来从内核得到事件的集合,返回的事件都保存在该events数组中,需要通过判断该数组中各个元素的状态来决定该如何处理,该maxevents告之内核这个events数组的大小。该函数返回需要处理的事件数目,如返回0表示已超时。
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <vector>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/epoll.h>
using namespace std;
#define PORT 1314
#define MAX_LINE_LEN 1024
#define EPOLL_EVENTS 1024 int main()
{
struct sockaddr_in cli_addr, server_addr;
socklen_t addr_len;
int one,flags,nrcv,nwrite,nready; int listenfd,epollfd,connfd;
char buf[MAX_LINE_LEN],addr_str[INET_ADDRSTRLEN]; struct epoll_event ev;
std::vector<struct epoll_event> eventsArray(); bzero(&server_addr, sizeof server_addr);
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
server_addr.sin_addr.s_addr = htonl(INADDR_ANY); listenfd = socket(AF_INET, SOCK_STREAM, ); if( listenfd < )
{
perror("socket open error! \n");
exit();
} one = ;
setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR, &one, sizeof one); flags = fcntl(listenfd,F_GETFL,);
fcntl(listenfd, F_SETFL, flags | O_NONBLOCK); if(bind(listenfd,reinterpret_cast<struct sockaddr *>(&server_addr),sizeof(server_addr)) < )
{
perror("Bind error! \n");
exit();
} listen(listenfd, ); epollfd = epoll_create(EPOLL_EVENTS); if(epollfd < )
{
printf("epoll_create error: %s \n",strerror(errno));
exit();
} ev.events = EPOLLIN;
ev.data.fd = listenfd; if(epoll_ctl(epollfd, EPOLL_CTL_ADD,listenfd,&ev) < )
{
printf("register listenfd failed: %s",strerror(errno));
exit();
} while()
{
nready = epoll_wait(epollfd,&(*eventsArray.begin()),static_cast<int>(eventsArray.size()),-); if(nready < )
{
printf("epoll_wait error: %s \n",strerror(errno));
} for( int i = ; i < nready; i++)
{
if(eventsArray[i].data.fd == listenfd)
{
addr_len = sizeof cli_addr;
connfd = accept(listenfd, reinterpret_cast<struct sockaddr *>(&cli_addr),&addr_len); if( connfd < )
{
if( errno != ECONNABORTED || errno != EWOULDBLOCK || errno != EINTR)
{
printf("accept socket aborted: %s \n",strerror(errno));
continue;
}
} flags = fcntl(connfd, F_GETFL, );
fcntl(connfd,F_SETFL, flags | O_NONBLOCK); ev.events = EPOLLIN;
ev.data.fd = connfd; if(epoll_ctl(epollfd,EPOLL_CTL_ADD,connfd,&ev) < )
{
printf("epoll add error: %s",strerror(errno));
} printf("recieve from : %s at port %d\n", inet_ntop(AF_INET,&cli_addr.sin_addr,addr_str,INET_ADDRSTRLEN),cli_addr.sin_port); if(--nready < )
{
continue;
} }
else
{
ev = eventsArray[i]; printf("fd = %d \n",ev.data.fd); memset(buf,,MAX_LINE_LEN); if( (nrcv = read(ev.data.fd, buf, MAX_LINE_LEN)) < )
{
if(errno != EWOULDBLOCK || errno != EAGAIN || errno != EINTR)
{
printf("read error: %s\n",strerror(errno));
}
}
else if( == nrcv)
{
close(ev.data.fd);
printf("close: %d fd \n",ev.data.fd);
eventsArray.erase(eventsArray.begin() + i);
}
else
{
printf("nrcv, content: %s\n",buf);
nwrite = write(ev.data.fd, buf, nrcv);
if( nwrite < )
{
if(errno != EAGAIN || errno != EWOULDBLOCK)
printf("write error: %s\n",strerror(errno));
}
printf("nwrite = %d\n",nwrite);
}
}
}
} return ;
}
客户端的测试代码还是可以用前一篇文章提到的:nc localhost 1314 的方式来测试。
I/O multiplexing 有三个方式可以完成,这三种方式的优劣和适用场合不同,后面会专门分析。
I/O Mutiplexing poll 和 epoll的更多相关文章
- select、poll、epoll之间的区别总结
select.poll.epoll之间的区别总结 05/05. 2014 select,poll,epoll都是IO多路复用的机制.I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪 ...
- (转载) Linux IO模式及 select、poll、epoll详解
注:本文是对众多博客的学习和总结,可能存在理解错误.请带着怀疑的眼光,同时如果有错误希望能指出. 同步IO和异步IO,阻塞IO和非阻塞IO分别是什么,到底有什么区别?不同的人在不同的上下文下给出的答案 ...
- select、poll、epoll之间的区别总结[整理]
select,poll,epoll都是IO多路复用的机制.I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作.但select ...
- select、poll、epoll区别总结
1 本质上都是同步I/O 三者都是I/O复用,本质上都属于同步I/O.因为三者只是负责通知应用程序什么时候数据准备好了,实际的I/O操作还是在由应用程序处理:如果是异步I/O的话,实际I/O由内核处理 ...
- socket阻塞与非阻塞,同步与异步、I/O模型,select与poll、epoll比较
1. 概念理解 在进行网络编程时,我们常常见到同步(Sync)/异步(Async),阻塞(Block)/非阻塞(Unblock)四种调用方式: 同步/异步主要针对C端: 同步: 所谓同步,就 ...
- 聊聊IO多路复用之select、poll、epoll详解
本文转载自: http://mp.weixin.qq.com/s?__biz=MzAxODI5ODMwOA==&mid=2666538922&idx=1&sn=e6b436ef ...
- select、poll、epoll之间的区别
select.poll.epoll之间的区别总结[整理] select,poll,epoll都是IO多路复用的机制.I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就 ...
- 【Unix环境编程】select、poll、epoll机制的联系与区别
在linux设计并发网络程序,主要有如下几种模型:Apache模型(Process Per Connection, PPC).TPC(Thread Per Connection)模型,select机制 ...
- [转载] select, poll和epoll的区别
源地址:http://sheepxxyz.blog.163.com/blog/static/61116213201022003513530/ 随着2.6内核对epoll的完全支持,网络上很多的文章和示 ...
随机推荐
- 移植Python3到TQ2440(一)
平台 硬件:TQ2440 64MB内存 256MB NandFlash bootloader:U-Boot 2015.04 kernel:linux-4.9 Python: Python-3.6.0 ...
- 使用RemObjects Pascal Script
摘自RemObjects Wiki 本文提供RemObjects Pascal Script的整体概要并演示如何创建一些简单的脚本. Pascal Script包括两个不同部分: 编译器 (uPSCo ...
- Excel部署配置DCOM
对 Excel进行编程,实际上就是通过 .Net Framework去调用 Excel的 COM组件,所有要在 Web环境下调用 COM组件的时候,都需要对其进行相应的配置. 很多朋友都反映在 Win ...
- tms web core介绍
tms web core介绍 TMS Web CORE是基于将Delphi UI代码编译为javascript并以此方式创建的 称为单页应用程序.TMS Web核心应用程序可以包含多个表单. 这些多个 ...
- java中Double类数字太大时页面正常显示而不要用科学计数法
/** * 当浮点型数据位数超过10位之后,数据变成科学计数法显示.用此方法可以使其正常显示. * @param value * @return Sting */ public static Stri ...
- Scala从零開始:使用Intellij IDEA写hello world
引言 在之前的文章中,我们介绍了怎样使用Scala IDE也就是eclipse中集成的Scala开发插件来进行Scala语言程序的开发,在使用了一段时间之后,发现eclipse对Scala的支持并非非 ...
- SharePoint Online 创建文档库
前言 本文介绍如何在Office 365中创建文档库,以及文档库的一些基本设置. 正文 通过登录地址登录到Office 365的SharePoint Online站点中,我们可以在右上角的设置菜单中, ...
- JavaScript:ECMAScript 引用类型
ylbtech-JavaScript:ECMAScript 引用类型 1. 返回顶部 2. ECMAScript 引用类型返回顶部 引用类型通常叫做类(class). 本教程会讨论大量的 ECMASc ...
- SEM(搜索引擎营销)
ylbtech-Miscellaneos:SEM(搜索引擎营销) 搜索引擎营销:英文Search Engine Marketing ,我们通常简称为“SEM”.就是根据用户使用搜索引擎的方式利用用户检 ...
- JS 父页面调子页面(2种情况),子掉父级(1种)(转)
A :父级调用子级页面 ,非IFRAME情况,类似平级: window.open("子页面.html", "", "width=1024,height ...