epoll的使用实例
在网络编程中通常需要处理很多个连接,可以用select和poll来处理多个连接。但是select都受进程能打开的最大文件描述符个数的限制。并且select和poll效率会随着监听fd的数目增多而下降。
解决方法就是用epoll
1.epoll
是Linux内核为处理大批量文件描述符而做了改进的poll,是Linux下多路复用IO接口select/poll的增强版本。
2.epoll、select、poll的区别:
1)相比于select和poll,epoll最大的好处在于不会随着监听fd数目的增加而降低效率
2)内核中的select与poll的实现是采用轮询来处理的,轮询数目越多耗时就越多
3)epoll的实现是基于回调的,如果fd有期望的事件发生就会通过回调函数将其加入epoll就绪队列中。也就是说它只关心“活跃”的fd,与fd数目无关。
4)内核空间用户空间数据拷贝问题,如何让内核把fd消息通知给用户空间?select和poll采取了内存拷贝的方法,而epoll采用的是共享内存的方式。速度快
5)epoll不仅会告诉应用程序有I/o事件的到来,还会告诉应用程序相关的信息,这写信息是应用程序填充的,因此根据这写信息应用程序就能直接定位到事件,而不必遍历整个fd集合。
6)poll和select受进程能打开的最大文件描述符的限制。select还受FD_SETSIZE的限制。但是epoll的限制的最大可以打开文件的数目(cat /proc/sys/fs/file-max进行查看)。
3.epoll的工作模式
有下面两种工作模式,默认是水平触发。
EPOLLLT:水平触发(Level Triggered),事件没有处理完也会触发。完全靠kernel epoll驱动,应用程序只需要处理从epoll_wait返回的fds。这些fds我们认为他们处于就绪状态。
LT模式支持阻塞fd和非阻塞fd。
EPOLLET:边沿触发(Edge Triggered)。只有空闲->就绪才会触发。应用程序需要维护一个就绪队列。
此模式下,系统仅仅通知应用程序哪些fds变成了就绪状态,一旦fd变成了就绪状态,epoll将不再关注这个fd的任何状态信息(从epoll队列移除)。
直到应用程序通过读写操作触发EAGAIN状态,epoll认为这个fd又变成空闲状态,那么epoll又重新关注这个fd的状态变化(重新加入epoll队列中)。
随着epoll_wait的返回,队列中的fds是减少的,所以在大并发的系统中,EPOLLET更有优势。但是对程序员的要求也更高。
ET模式只支持non-block socket,以避免由于一个文件句柄的阻塞读/阻塞写把处理多个文件描述符的任务饿死。
4.如何使用
主要是下面几个函数和结构体。
#include <sys/epoll.h>
int epoll_create(int size);
int epoll_create1(int flags);
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);
The struct epoll_event is defined as :
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
struct epoll_event {
uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
1)int epoll_create(int size);
创建一个epoll的句柄,
size:告诉内核这个句柄可以监听的数目一共多大。
注意这里返回一个句柄,也是一个文件描述符,后面还是要关闭的。
2)int epoll_create1(int flags);
上面那个的加强版,flags可以是EPOLL_CLOEXEC,表示具有执行后关闭的特性
EPOLL_CLOEXEC
Set the close-on-exec (FD_CLOEXEC) flag on the new file descrip‐
tor. See the description of the O_CLOEXEC flag in open(2) for
reasons why this may be useful
3) int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
注册函数,用来对创建的epollfd进行操作的。
epfd:create出来的fd
op:执行的动作,由3个宏来表示
EPOLL_CTL_ADD:注册新的fd到epfd中;
EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
EPOLL_CTL_DEL:从epfd中删除一个fd;
fd:要监听的fd
event:表示需要监听的事件
结构见上面。
events可以是以下几个宏的集合:
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
4) int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);
用来等待事件的产生
epfd:create出来的epollfd
events:从内核得到事件的集合。一般的一个数组
maxevents:用来告诉内核events有多大,不能大于epoll_create()时的size。
timeout是超时时间:单位的毫秒,为0表示立即返回,-1表示阻塞。
返回:0表示超时,>0表示需要处理的事件数目。<0表示出错
5.实例:
server端是一个回射服务器:
#include<sys/types.h>
#include<sys/socket.h>
#include<sys/select.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<poll.h>
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
#include<errno.h>
#include<unistd.h>
#include <fcntl.h>
#include<sys/epoll.h>
#include<vector>
#include<algorithm>
// #include"comm.h"
void setfdisblock(int fd, bool isblock)
{
int flags = fcntl(fd, F_GETFL);
if(flags < )
return;
if(isblock) // 阻塞
{
flags &= ~O_NONBLOCK;
}
else // 非阻塞
{
flags |= O_NONBLOCK;
} if(fcntl(fd, F_SETFL, flags)<)
perror("fcntl set");
}
typedef std::vector<struct epoll_event> EventList;
#define CLIENTCOUNT 2048
#define MAX_EVENTS 2048
int main(int argc, char **argv)
{
int listenfd = socket(AF_INET, SOCK_STREAM, );
if(listenfd < )
{
perror("socket");
return -;
} unsigned short sport = ;
if(argc == )
{
sport = atoi(argv[]);
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
printf("port = %d\n", sport);
addr.sin_port = htons(sport);
addr.sin_addr.s_addr = inet_addr("127.0.0.1"); if(bind(listenfd, (struct sockaddr*)&addr, sizeof(addr)) < )
{
perror("bind");
return -;
}
if(listen(listenfd, ) < )
{
perror("listen");
return -;
} struct sockaddr_in connaddr;
socklen_t len = sizeof(connaddr); int i = , ret = ;
std::vector<int> clients; // 客户端存储的迭代器
int epollfd = epoll_create1(EPOLL_CLOEXEC);
//int epollfd = epoll_create(MAX_EVENTS);// 设置连接数 struct epoll_event event;
event.events = EPOLLIN|EPOLLET;
event.data.fd = listenfd;
if(epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &event) < )
{
perror("epoll_ctl");
return -;
}
EventList events();
int count = ;
int nready = ;
char buf[] = {};
int conn = ;
while()
{ nready = epoll_wait(epollfd, &*events.begin(), static_cast<int>(events.size()), -);
if(nready == -)
{
perror("epoll_wait");
return -;
}
if(nready == ) // 肯定不会走到这里,因为上面没设置超时时间
{
continue;
}
if((size_t)nready == events.size()) // 对clients进行扩容
events.resize(events.size() * );
for(i = ; i < nready; i++)
{
if(events[i].data.fd == listenfd)
{
conn = accept(listenfd, (struct sockaddr*)&connaddr, &len);
if(conn < )
{
perror("accept");
return -;
}
char strip[] = {};
char *ip = inet_ntoa(connaddr.sin_addr);
strcpy(strip, ip);
printf("client connect, conn:%d,ip:%s, port:%d, count:%d\n", conn, strip,ntohs(connaddr.sin_port), ++count); clients.push_back(conn);
// 设为非阻塞
setfdisblock(conn, false);
// add fd in events
event.data.fd = conn;// 这样在epoll_wait返回时就可以直接用了
event.events = EPOLLIN|EPOLLET;
epoll_ctl(epollfd, EPOLL_CTL_ADD, conn, &event); }
else if(events[i].events & EPOLLIN)
{
conn = events[i].data.fd;
if(conn < )
continue;
ret = read(conn, buf, sizeof(buf));
if(ret == -)
{
perror("read");
return -;
}
else if(ret == )
{
printf("client close remove:%d, count:%d\n", conn, --count);
close(conn);
event = events[i];
epoll_ctl(epollfd, EPOLL_CTL_DEL, conn, &event);
clients.erase(std::remove(clients.begin(), clients.end(), conn), clients.end());
}
write(conn, buf, sizeof(buf));
memset(buf, , sizeof(buf));
}
}
}
close(listenfd);
return ;
}
client:连接服务器,通过终端发送数据给server,并接收server发来的数据。
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/select.h>
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
void select_test(int conn)
{
int ret = ;
fd_set rset;
FD_ZERO(&rset);
int nready;
int maxfd = conn;
int fd_stdin = fileno(stdin);
if(fd_stdin > maxfd)
{
maxfd = fd_stdin;
}
int len = ;
char readbuf[] = {};
char writebuf[] = {};
while()
{
FD_ZERO(&rset);
FD_SET(fd_stdin, &rset);
FD_SET(conn, &rset);
nready = select(maxfd+, &rset, NULL, NULL, NULL);
if(nready == -)
{
perror("select");
exit();
}
else if(nready == )
{
continue;
}
if(FD_ISSET(conn, &rset))
{
ret = read(conn, readbuf, sizeof(readbuf));
if(ret == )
{
printf("server close1\n");
break;
}
else if(- == ret)
{
perror("read1");
break;
}
fputs(readbuf, stdout);
memset(readbuf, , sizeof(readbuf));
} if(FD_ISSET(fd_stdin, &rset))
{
read(fd_stdin, writebuf, sizeof(writebuf));
len = strlen(writebuf);
ret = write(conn, writebuf, len);
if(ret == )
{
printf("server close3\n");
break;
}
else if(- == ret)
{
perror("write");
break;
}
memset(writebuf, , sizeof(writebuf));
}
}
}
int sockfd = ;
int main(int argc, char **argv)
{
sockfd = socket(AF_INET, SOCK_STREAM, );
if(sockfd < )
{
perror("socket");
return -;
} unsigned short sport = ;
if(argc == )
{
sport = atoi(argv[]);
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
printf("port = %d\n", sport);
addr.sin_port = htons(sport);
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
if(connect(sockfd, (struct sockaddr*)&addr, sizeof(addr)) < )
{
perror("connect");
return -;
}
struct sockaddr_in addr2;
socklen_t len = sizeof(addr2);
if(getpeername(sockfd, (struct sockaddr*)&addr2, &len) < )
{
perror("getsockname");
return -;
}
printf("Server: port:%d, ip:%s\n", ntohs(addr2.sin_port), inet_ntoa(addr2.sin_addr));
select_test(sockfd);
close(sockfd);
return ;
}
还可以进行暴力连接测试。可以参考: http://www.cnblogs.com/xcywt/p/8120166.html 这个例子的客户端就是暴力连接测试的。
测试结果可以和select实现的server进行比较,发现在虚拟机上epollserver好像没有快多少(我也暂时没找到原因)。
但是在服务器上,还是有很快的,效率提升了很多。
epoll的使用实例的更多相关文章
- Linux下的I/O复用与epoll详解
前言 I/O多路复用有很多种实现.在linux上,2.4内核前主要是select和poll,自Linux 2.6内核正式引入epoll以来,epoll已经成为了目前实现高性能网络服务器的必备技术.尽管 ...
- 什么是epoll
什么是epoll epoll是什么?按照man手册的说法:是为处理大批量句柄而作了改进的poll.当然,这不是2.6内核才有的,它是在2.5.44内核中被引进的(epoll(4) is a new A ...
- epoll详解
转自:http://blog.chinaunix.net/uid-24517549-id-4051156.html 什么是epoll epoll是什么?按照man手册的说法:是为处理大批量句柄而作了改 ...
- [转载] epoll详解
转载自http://blog.csdn.net/xiajun07061225/article/details/9250579 什么是epoll epoll是什么?按照man手册的说法:是为处理大批量句 ...
- 20分钟了解Epoll + 聊天室实战
我们知道,计算机的硬件资源由操作系统管理.调度,我们的应用程序运行在操作系统之上,我们的程序运行需要访问计算机上的资源(如读取文件,接收网络请求),操作系统有内核空间和用户空间之分,所以数据读取,先由 ...
- [转]epoll详解
什么是epollepoll是什么?按照man手册的说法:是为处理大批量句柄而作了改进的poll.当然,这不是2.6内核才有的,它是在2.5.44内核中被引进的(epoll(4) is a new AP ...
- epoll好文章
https://www.cnblogs.com/apprentice89/p/3234677.html https://www.jianshu.com/p/aa486512e989 https://c ...
- 关于一篇对epoll讲的比较好的一篇文章
原文地址http://www.cnblogs.com/lojunren/p/3856290.html 前言 I/O多路复用有很多种实现.在linux上,2.4内核前主要是select和poll,自Li ...
- Linux下的I/O复用与epoll详解(转载)
Linux下的I/O复用与epoll详解 转载自:https://www.cnblogs.com/lojunren/p/3856290.html 前言 I/O多路复用有很多种实现.在linux上,2 ...
随机推荐
- Python [习题] 字典排序
[习题] 对此字典分别按照value 和key 如何排序? dic1 = {'and':40, 'a':54, 'is':60, 'path':139, 'the':124, 'os':49} In ...
- mysql步骤详解
一.配置MySQL数据库 1.解压绿色版mysql,并改名为mysql5.7,如下图 对比一下下图5.6以前的版本,少data目录(存放数据)和my-default.ini文件(配置信息) 二.安装服 ...
- 如何使用MFC连接Access数据库
(1)新建一个Access数据库文件.将其命名为data.mdb,并创建好表.字段. (2)为系统添加数据源.打开“控制面板”—>“管理工具”—>“数据源”,选择“系统DSN”,点击右边的 ...
- JavaScript学习笔记(十五)——对象之Date,RegExp
在学习廖雪峰前辈的JavaScript教程中,遇到了一些需要注意的点,因此作为学习笔记列出来,提醒自己注意! 如果大家有需要,欢迎访问前辈的博客https://www.liaoxuefeng.com/ ...
- 学习笔记:UITabBarController使用详解
一.手动创建UITabBarController 最常见的创建UITabBarController的地方就是在application delegate中的 applicationDidFinishLa ...
- 网站图片挂马检测及PHP与python的图片文件恶意代码检测对比
前言 周一一早网管收到来自阿里云的一堆警告,发现我们维护的一个网站下有数十个被挂马的文件.网管直接关了vsftpd,然后把警告导出邮件给我们. 取出部分大致如下: 服务器IP/名称 木马文件路径 更新 ...
- 51Nod 1110 距离之和最小 V3 中位数 思维
基准时间限制:1 秒 空间限制:131072 KB 分值: 40 难度:4级算法题 X轴上有N个点,每个点除了包括一个位置数据X[i],还包括一个权值W[i].点P到点P[i]的带权距离 = 实际距离 ...
- PAT 1008. Elevator (20)
1008. Elevator (20) 时间限制 400 ms 内存限制 65536 kB 代码长度限制 16000 B 判题程序 Standard 作者 CHEN, Yue The highest ...
- 200行Java代码搞定计算器程序
发现了大学时候写的计算器小程序,还有个图形界面,能够图形化展示表达式语法树,哈哈;) 只有200行Java代码,不但能够计算加减乘除,还能够匹配小括号~ 代码点评: 从朴素的界面配色到简单易懂错误提示 ...
- salesforce零基础学习(八十四)配置篇: 自定义你的home page layout
当我们进入salesforce系统或者切换app后,默认第一个看到的就是home页面.home页面简单的来说可以包括左侧(narrow component)和右侧(wide component)两部分 ...