epoll用法整理
2016-01-10 20:53
357 查看
1、epoll是什么?
epoll是当前在Linux下开发大规模并发网络程序的热门人选,epoll 在Linux2.6内核中正式引入,和select相似,都是I/O多路复用(IO
multiplexing)技术。
Linux下设计并发网络程序,常用的模型有:
Apache模型(Process Per Connection,简称PPC)
TPC(Thread PerConnection)模型
select模型和poll模型。
epoll模型
2、常用模型的缺点
PPC/TPC模型
这两种模型思想类似,就是让每一个到来的连接都有一个进程/线程来服务。这种模型的代价是它要时间和空间。连接较多时,进程/线程切换的开销比较大。因此这类模型能接受的最大连接数都不会高,一般在几百个左右。
select模型
最大并发数限制:因为一个进程所打开的fd(文件描述符)是有限制的,由FD_SETSIZE设置,默认值是1024/2048,因此select模型的最大并发数就被相应限制了。
效率问题:select每次调用都会线性扫描全部的fd集合,这样效率就会呈现线性下降,把FD_SETSIZE改大可能造成这些fd都超时了。
内核/用户空间内存拷贝问题:如何让内核把fd消息通知给用户空间呢?在这个问题上select采取了内存拷贝方法。
poll模型
基本上效率和select是相同的,select缺点的2和3它都没有改掉。
epoll的改进
对比其他模型的问题,epoll的改进如下:
epoll没有最大并发连接的限制,上限是最大可以打开文件的数目,这个数字一般远大于2048, 一般来说这个数目和系统内存关系很大,具体数目可以cat
/proc/sys/fs/file-max察看。
效率提升,Epoll最大的优点就在于它只管你“活跃”的连接,而跟连接总数无关,因此在实际的网络环境中,Epoll的效率就会远远高于select和poll。
内存拷贝,Epoll在这点上使用了“共享内存”,这个内存拷贝也省略了。
3、 epoll为什么高效
epoll的高效和其数据结构的设计是密不可分的。
首先回忆一下select模型,当有I/O事件到来时,select通知应用程序有事件到了,应用程序必须轮询所有的fd集合,测试每个fd是否有事件发生,并处理事件;代码像下面这样:
int res = select(maxfd+1, &readfds, NULL, NULL, 120);
if (res > 0)
{
for (int i = 0; i < MAX_CONNECTION; i++)
{
if (FD_ISSET(allConnection[i],&readfds))
{
handleEvent(allConnection[i]);
}
}
}
// if(res == 0) handle timeout, res < 0 handle error
epoll不仅会告诉应用程序有I/0事件到来,还会告诉应用程序相关的信息,这些信息是应用程序填充的,因此根据这些信息应用程序就能直接定位到事件,而不必遍历整个fd集合。
int res = epoll_wait(epfd, events, 20, 120);
for (int i = 0; i < res;i++)
{
handleEvent(events
);
}
epoll关键数据结构
前面提到epoll速度快和其数据结构密不可分,其关键数据结构就是:
struct epoll_event {
__uint32_t events; //epoll events
epoll_data_t data; //user data variable
};
typedef union epoll_data {
void* ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
}epoll_data_t;
可见epoll_data是一个union结构体,借助于它应用程序可以保存很多类型的信息:fd、指针等等。有了它,应用程序就可以直接定位目标了。
使用epoll
epoll的API:
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, structepoll_event *event);
int epoll_wait(int epfd, struct epoll_event* events, int maxevents. int timeout);
int epoll_create(int size);
创建一个epoll的文件描述符,参数size告诉内核这个监听的数目共有多大。
int epoll_ctl(int epfd, int op, int fd, structepoll_event *event);
epoll的事件注册函数。
参数epfd是epoll_create返回值。
参数op为
EPOLL_CTL_ADD 注册新的fd到epfd中
EPOLL_CTL_MOD 修改已经注册的fd的监听事件
EPOLL_CTL_DEL 从epfd中删除一个fd
参数fd是需要监听文件描述符。
参数event是告诉内核需要监听什么事件。event->events的不同的值表示对应的文件描述符的不同事件:
EPOLLIN 可以读(包括对端Socket正常关闭)
EPOLLOUT 可以写
EPOLLPRI有紧急的数据可读(有带外数据OOB到来,TCP中的URG包)
EPOLLERR该文件描述符发生错误
EPOLLHUP该文件描述符被挂断
EPOLLET 将epoll设置为边缘触发(Edge Triggered)模式。
EPOLLONESHOT只监听一次事件,监听完之后,如果还想监听需要再次把该文件描述符加入到epoll队列中
int epoll_wait(int epfd, struct epoll_event* events, int maxevents. int timeout);
等待事件的产生。
参数events用来从内核得到事件的集合
参数maxevents告之内核这个events有多大(maxevents不能大于size)
参数timeout是超时时间(毫秒)
epoll的模式:
LT模式:Level Triggered水平触发
这个是缺省的工作模式。同时支持block socket和non-block socket。内核会告诉程序员一个文件描述符是否就绪了。如果程序员不作任何操作,内核仍会通知。
ET模式:Edge Triggered 边缘触发
是一种高速模式。仅当状态发生变化的时候才获得通知。这种模式假定程序员在收到一次通知后能够完整地处理事件,于是内核不再通知这一事件。注意:缓冲区中还 有未处理的数据不算状态变化,所以ET模式下程序员只读取了一部分数据就再也得不到通知了,正确的用法是程序员自己确认读完了所有的字节(一直调用read/write直到 出错EAGAIN为止)。
一个例子:
主要参考链接:http://blog.csdn.net/ljx0305/article/details/4065058
epoll是当前在Linux下开发大规模并发网络程序的热门人选,epoll 在Linux2.6内核中正式引入,和select相似,都是I/O多路复用(IO
multiplexing)技术。
Linux下设计并发网络程序,常用的模型有:
Apache模型(Process Per Connection,简称PPC)
TPC(Thread PerConnection)模型
select模型和poll模型。
epoll模型
2、常用模型的缺点
PPC/TPC模型
这两种模型思想类似,就是让每一个到来的连接都有一个进程/线程来服务。这种模型的代价是它要时间和空间。连接较多时,进程/线程切换的开销比较大。因此这类模型能接受的最大连接数都不会高,一般在几百个左右。
select模型
最大并发数限制:因为一个进程所打开的fd(文件描述符)是有限制的,由FD_SETSIZE设置,默认值是1024/2048,因此select模型的最大并发数就被相应限制了。
效率问题:select每次调用都会线性扫描全部的fd集合,这样效率就会呈现线性下降,把FD_SETSIZE改大可能造成这些fd都超时了。
内核/用户空间内存拷贝问题:如何让内核把fd消息通知给用户空间呢?在这个问题上select采取了内存拷贝方法。
poll模型
基本上效率和select是相同的,select缺点的2和3它都没有改掉。
epoll的改进
对比其他模型的问题,epoll的改进如下:
epoll没有最大并发连接的限制,上限是最大可以打开文件的数目,这个数字一般远大于2048, 一般来说这个数目和系统内存关系很大,具体数目可以cat
/proc/sys/fs/file-max察看。
效率提升,Epoll最大的优点就在于它只管你“活跃”的连接,而跟连接总数无关,因此在实际的网络环境中,Epoll的效率就会远远高于select和poll。
内存拷贝,Epoll在这点上使用了“共享内存”,这个内存拷贝也省略了。
3、 epoll为什么高效
epoll的高效和其数据结构的设计是密不可分的。
首先回忆一下select模型,当有I/O事件到来时,select通知应用程序有事件到了,应用程序必须轮询所有的fd集合,测试每个fd是否有事件发生,并处理事件;代码像下面这样:
int res = select(maxfd+1, &readfds, NULL, NULL, 120);
if (res > 0)
{
for (int i = 0; i < MAX_CONNECTION; i++)
{
if (FD_ISSET(allConnection[i],&readfds))
{
handleEvent(allConnection[i]);
}
}
}
// if(res == 0) handle timeout, res < 0 handle error
epoll不仅会告诉应用程序有I/0事件到来,还会告诉应用程序相关的信息,这些信息是应用程序填充的,因此根据这些信息应用程序就能直接定位到事件,而不必遍历整个fd集合。
int res = epoll_wait(epfd, events, 20, 120);
for (int i = 0; i < res;i++)
{
handleEvent(events
);
}
epoll关键数据结构
前面提到epoll速度快和其数据结构密不可分,其关键数据结构就是:
struct epoll_event {
__uint32_t events; //epoll events
epoll_data_t data; //user data variable
};
typedef union epoll_data {
void* ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
}epoll_data_t;
可见epoll_data是一个union结构体,借助于它应用程序可以保存很多类型的信息:fd、指针等等。有了它,应用程序就可以直接定位目标了。
使用epoll
epoll的API:
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, structepoll_event *event);
int epoll_wait(int epfd, struct epoll_event* events, int maxevents. int timeout);
int epoll_create(int size);
创建一个epoll的文件描述符,参数size告诉内核这个监听的数目共有多大。
int epoll_ctl(int epfd, int op, int fd, structepoll_event *event);
epoll的事件注册函数。
参数epfd是epoll_create返回值。
参数op为
EPOLL_CTL_ADD 注册新的fd到epfd中
EPOLL_CTL_MOD 修改已经注册的fd的监听事件
EPOLL_CTL_DEL 从epfd中删除一个fd
参数fd是需要监听文件描述符。
参数event是告诉内核需要监听什么事件。event->events的不同的值表示对应的文件描述符的不同事件:
EPOLLIN 可以读(包括对端Socket正常关闭)
EPOLLOUT 可以写
EPOLLPRI有紧急的数据可读(有带外数据OOB到来,TCP中的URG包)
EPOLLERR该文件描述符发生错误
EPOLLHUP该文件描述符被挂断
EPOLLET 将epoll设置为边缘触发(Edge Triggered)模式。
EPOLLONESHOT只监听一次事件,监听完之后,如果还想监听需要再次把该文件描述符加入到epoll队列中
int epoll_wait(int epfd, struct epoll_event* events, int maxevents. int timeout);
等待事件的产生。
参数events用来从内核得到事件的集合
参数maxevents告之内核这个events有多大(maxevents不能大于size)
参数timeout是超时时间(毫秒)
epoll的模式:
LT模式:Level Triggered水平触发
这个是缺省的工作模式。同时支持block socket和non-block socket。内核会告诉程序员一个文件描述符是否就绪了。如果程序员不作任何操作,内核仍会通知。
ET模式:Edge Triggered 边缘触发
是一种高速模式。仅当状态发生变化的时候才获得通知。这种模式假定程序员在收到一次通知后能够完整地处理事件,于是内核不再通知这一事件。注意:缓冲区中还 有未处理的数据不算状态变化,所以ET模式下程序员只读取了一部分数据就再也得不到通知了,正确的用法是程序员自己确认读完了所有的字节(一直调用read/write直到 出错EAGAIN为止)。
一个例子:
#include <netdb.h> #include <sys/socket.h> #include <sys/epoll.h> #include <netinet/in.h> #include <arpa/inet.h> #include <fcntl.h> #include <unistd.h> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <errno.h> /*创建并绑定一个socket作为服务器。 */ static int create_and_bind (char *port){ struct addrinfo hints; struct addrinfo *result, *rp; int s, sfd; memset (&hints, 0, sizeof (struct addrinfo)); hints.ai_family = AF_UNSPEC; /* Return IPv4 and IPv6 choices */ hints.ai_socktype = SOCK_STREAM; /* 设置为STREAM模式,即TCP链接 */ hints.ai_flags = AI_PASSIVE; /* All interfaces */ s = getaddrinfo (NULL, port, &hints, &result);//获得本地主机的地址 if (s != 0){ fprintf (stderr, "getaddrinfo: %s\n", gai_strerror (s)); return -1; } for (rp = result; rp != NULL; rp = rp->ai_next){//本地主机地址可能有多个,任意绑定一个即可 sfd = socket (rp->ai_family, rp->ai_socktype, rp->ai_protocol); //创建socket if (sfd == -1) continue; s = bind (sfd, rp->ai_addr, rp->ai_addrlen); //并绑定socket if (s == 0) { /* 绑定成功 */ break; } close (sfd); } if (rp == NULL){ fprintf (stderr, "Could not bind\n"); return -1; } freeaddrinfo (result); return sfd; } /* 设置socket为非阻塞模式。 先get flag,或上O_NONBLOCK 再set flag。 */ static int make_socket_non_blocking (int sfd) { int flags, s; flags = fcntl (sfd, F_GETFL, 0); if (flags == -1){ perror ("fcntl"); return -1; } flags |= O_NONBLOCK; s = fcntl (sfd, F_SETFL, flags); if (s == -1){ perror ("fcntl"); return -1; } return 0; } #define MAXEVENTS 64 /* 用法: ./epoll_test 8080 */ int main (int argc, char *argv[]) { int sfd, s; int efd; struct epoll_event event; struct epoll_event *events; if (argc != 2) { fprintf (stderr, "Usage: %s [port]\n", argv[0]); exit (EXIT_FAILURE); } sfd = create_and_bind (argv[1]); //sfd为绑定后等待连接接入的文件描述符 s = make_socket_non_blocking (sfd); s = listen (sfd, SOMAXCONN); efd = epoll_create1 (0); event.data.fd = sfd; event.events = EPOLLIN | EPOLLET; s = epoll_ctl (efd, EPOLL_CTL_ADD, sfd, &event); /* Buffer where events are returned,为events数组分配内存 */ events = (struct epoll_event*)calloc (MAXEVENTS, sizeof event); /* The event loop 事件循环*/ while (1) { int n, i; n = epoll_wait (efd, events, MAXEVENTS, -1); for (i = 0; i < n; i++) { if ((events[i].events & EPOLLERR) || (events[i].events & EPOLLHUP) || (!(events[i].events & EPOLLIN))) { /* 发生了错误或者被挂断,或者没有数据可读 An error has occured on this fd, or the socket is not ready for reading (why were we notified then?) */ fprintf (stderr, "epoll error\n"); close (events[i].data.fd); continue; }else if (sfd == events[i].data.fd) {//新连接 /* sfd上有数据可读,则表示有新连接 * We have a notification on the listening socket, * which means one or more incoming connections. */ printf("Incoming connection !\n"); while (1) { struct sockaddr in_addr; socklen_t in_len; int infd; char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV]; in_len = sizeof in_addr; infd = accept (sfd, &in_addr, &in_len); //读取到来的连接socket fd。 if (infd == -1) { if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) { /* 已经读完了sfd上的所有数据(所有连接)。最后一次读(非阻塞读)会返回EAGAIN(=EWOULDBLOCK) * We have processed all incoming connections. */ break; } else { perror ("accept"); break; } } s = getnameinfo (&in_addr, in_len, hbuf, sizeof hbuf, sbuf, sizeof sbuf, NI_NUMERICHOST | NI_NUMERICSERV); if (s == 0) { printf("Accepted connection on descriptor %d (host=%s, port=%s)\n", infd, hbuf, sbuf); } s = make_socket_non_blocking (infd); //设置socket为非阻塞模式 event.data.fd = infd; //将data部分设置为fd event.events = EPOLLIN | EPOLLET; //监听EPOLLIN事件,使用边缘触发模式 s = epoll_ctl (efd, EPOLL_CTL_ADD, infd, &event); } continue; } else {//有客户端发来数据 /* 有客户端发来数据,因为处于ET模式,所以必须完全读取所有数据(要不然,剩下一部分数据后,就无法再收到内核通知了)。*/ int conn = events[i].data.fd; if (conn < 0) continue; char recvbuf[1024] = {0}; int ret = read(conn, recvbuf, 1024); if (ret == -1) //ERR_EXIT("readline"); if (ret == 0) { printf("client close\n"); close(conn); event = events[i]; epoll_ctl(efd, EPOLL_CTL_DEL, conn, &event); //clients.erase(std::remove(clients.begin(), clients.end(), conn), clients.end()); } fputs(recvbuf, stdout); write(conn, recvbuf, strlen(recvbuf)); } } } free (events);//释放内存 close (sfd); //关闭sfd return EXIT_SUCCESS; }
主要参考链接:http://blog.csdn.net/ljx0305/article/details/4065058
相关文章推荐
- 春风醉人,暖意微醺
- jQuery中的事件
- SAWV Eclipse Plugin
- 原始XML资源
- 飘逸的python - __new__、__init__、__call__傻傻分不清
- 清除图片周围的空白区域
- 浅谈 举家搬迁静态文件到CDN
- GIT - 一些基本概念
- C语言 typedef的使用
- 【Java】使用Json-lib序列化关联对象的异常解决
- [DEV]How to compile TWRP touch recovery
- 【SQL server】数据的插入
- 去掉IIS_schema.xml的只读属性
- 语文文法
- 安卓进程通讯之messenger
- 深入理解Java虚拟机 类加载子系统1
- 机器学习之常用算法总结
- CodeForces 611C New Year and Domino【预处理】
- 1223 递归下降语法分析程序设计
- 2016/1/10 实例 1,控制台 输入人数 2,控制台 输入对应人数的成绩 3,求成绩总和以及最大值 最小值 平均值