l  epoll是什么?

epoll是当前在Linux下开发大规模并发网络程序的热门人选,epoll 在Linux2.6内核中正式引入,和select相似,都是I/O多路复用(IO multiplexing)技术

Linux下设计并发网络程序,常用的模型有:

  • Apache模型(Process Per Connection,简称PPC)
  • TPC(Thread PerConnection)模型
  • select模型和poll模型。
  • epoll模型

l  常用模型的缺点

n  PPC/TPC模型

这两种模型思想类似,就是让每一个到来的连接都有一个进程/线程来服务。这种模型的代价是它要时间和空间。连接较多时,进程/线程切换的开销比较大。因此这类模型能接受的最大连接数都不会高,一般在几百个左右。

n  select模型

  • 最大并发数限制:因为一个进程所打开的fd(文件描述符)是有限制的,由FD_SETSIZE设置,默认值是1024/2048,因此select模型的最大并发数就被相应限制了。
  • 效率问题:select每次调用都会线性扫描全部的fd集合,这样效率就会呈现线性下降,把FD_SETSIZE改大可能造成这些fd都超时了。
  • 内核/用户空间内存拷贝问题:如何让内核把fd消息通知给用户空间呢?在这个问题上select采取了内存拷贝方法。

n  poll模型

  • 基本上效率和select是相同的,select缺点的2和3它都没有改掉。

l  epoll的改进

对比其他模型的问题,epoll的改进如下:

n  epoll没有最大并发连接的限制,上限是最大可以打开文件的数目,这个数字一般远大于2048, 一般来说这个数目和系统内存关系很大,具体数目可以cat /proc/sys/fs/file-max察看。

n  效率提升,Epoll最大的优点就在于它只管你活跃的连接,而跟连接总数无关,因此在实际的网络环境中,Epoll的效率就会远远高于select和poll。

n  内存拷贝,Epoll在这点上使用了“共享内存”,这个内存拷贝也省略了。

l  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[n]);

}

l  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、指针等等。有了它,应用程序就可以直接定位目标了。

l  使用epoll

epollAPI:

#include  <sys/epoll.h>

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作为服务器。 */

staticint  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;

}elseif (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 done = 0;

while (1) {

ssize_t count;

char buf[512];

count = read (events[i].data.fd, buf, sizeof buf);

if (count == -1) {

if (errno != EAGAIN) { //如果errno=EAGAIN,表示我们已经读取了所有的数据

perror ("read");

done = 1;

}

break;

elseif (count == 0) {  //读到文件尾(对端被关闭了)

/* End of file. The remote has closed the connection. */

done = 1;

break;

}

s = write (1, buf, count); /* 打印到屏幕 */

}

if (done) { //读完关闭(假设应用对每个连接只读取一次数据)

printf ("Closed connection on descriptor %d\n", events[i].data.fd);

/* Closing the descriptor will make epoll remove it from the set of descriptors which are monitored. */

close (events[i].data.fd);

}

}

}

}

free (events);//释放内存

close (sfd);   //关闭sfd

return EXIT_SUCCESS;

}

主要参考链接:http://blog.csdn.net/ljx0305/article/details/4065058

epoll用法【整理】的更多相关文章

  1. Spring JdbcTemplate用法整理

    Spring JdbcTemplate用法整理: xml: <?xml version="1.0" encoding="UTF-8"?> <b ...

  2. 网络通信 --> epoll用法

    epoll用法 在linux的网络编程中,很长的时间都在使用select来做事件触发.在linux新的内核中,有了一种替换它的机制,就是epoll. epoll函数 1. 创建epoll的句柄 siz ...

  3. linq用法整理

    linq用法整理 普通查询 var highScores = from student in students where student.ExamScores[exam] > score se ...

  4. linux学习:特殊符号,数学运算,图像与数组与部分终端命令用法整理

    指令:let.expr.array.convert.tput.date.read.md5.ln.apt.系统信息 一:特殊符号用法整理 系统变量 $# 是传给脚本的参数个数 $0 是脚本本身的名字 $ ...

  5. #ifndef#define#endif的用法(整理)

    [转] #ifndef#define#endif的用法(整理)    原作者:icwk  文件中的#ifndef 头件的中的#ifndef,这是一个很关键的东西.比如你有两个C文件,这两个C文件都in ...

  6. Google Guava 库用法整理<转>

    参考: http://codemunchies.com/2009/10/beautiful-code-with-google-collections-guava-and-static-imports- ...

  7. MySQL中使用SHOW PROFILE命令分析性能的用法整理(配合explain效果更好,可以作为优化周期性检查)

    这篇文章主要介绍了MySQL中使用show profile命令分析性能的用法整理,show profiles是数据库性能优化的常用命令,需要的朋友可以参考下   show profile是由Jerem ...

  8. select,poll,epoll用法

    http://blog.csdn.net/sunboy_2050/article/details/6126712 select用法 #include <sys/time.h>       ...

  9. Android spannableStringBuilder用法整理

    Android spannableStringBuilder用法整理 分类: Android开发2013-11-29 10:58 5009人阅读 评论(0) 收藏 举报 Androidspannabl ...

  10. OBJECTPROPERTY用法整理

    OBJECTPROPERTY用法整理 分类: 系统表与表结构 数据库管理维护2010-06-03 16:37 2783人阅读 评论(1) 收藏 举报 数据库sql serverinsertobject ...

随机推荐

  1. poj 2029 Get Many Persimmon Trees 各种解法都有,其实就是瞎搞不算吧是dp

    连接:http://poj.org/problem?id=2029 题意:给你一个map,然后在上面种树,问你h*w的矩形上最多有几棵树~这题直接搜就可以.不能算是DP 用树状数组也可作. #incl ...

  2. 古董留念 - Microsoft Office 4.2中文版

    Office 4.2是Office 95的前一个版本,最适合运行在Windows 3.x上,但即使是最新的Windows 7 32位版也是可以安装它的(不信你可以试试)! 原版以软盘为载体,安装一次需 ...

  3. 【转载】你真的会浮点数与整型数的"互转"吗?

    看了标题,你是不是觉得这TM是哪个iOS彩笔写的入门文章.好的,那咱们先来看看几个例题,看看你有没有白白点进来! int main() { float a = -6.0; int *b = & ...

  4. GPU Memory Usage占满而GPU-Util却为0的调试

    最近使用github上的一个开源项目训练基于CNN的翻译模型,使用THEANO_FLAGS='floatX=float32,device=gpu2,lib.cnmem=1' python run_nn ...

  5. html页面中event的常见应用

    一:获取键盘上某个按键的unicode值 <html> <head> <script type="text/javascript"> funct ...

  6. Linux内核 runtime_PM 框架

    runtime PM (runtime power management) 简介: 怎样动态地打开关闭设备的电源 ? 最简单的方法:在驱动程序中,open时打开电源,在close时关闭电源.但是有一个 ...

  7. phpstorm搜索匹配正则表达式

    data-position=".................................................................." 点是匹配任意一 ...

  8. Androoid studio 2.3 AAPT err(Facade for 596378712): \\?\C:\Users\中文文件夹\.android\build-cache

    错误如下: Error:Some file crunching failed, see logs for details Error:Execution failed for task ':app:m ...

  9. c# 爬虫(二) 模拟登录

    有了上一篇的介绍,这次我们来说说模拟登录,上一篇见 :c# 爬虫(一) HELLO WORLD 原理 我们知道,一般需要登录的网站,服务器和客户端都会有一段时间的会话保持,而这个会话保持是在登录时候建 ...

  10. 大数据框架hadoop的序列化机制

    Java内建序列化机制 在Windows系统上序列化的Java对象,可以在UNIX系统上被重建出来,不需要担心不同机器上的数据表示方法,也不需要担心字节排列次序. 在Java中,使一个类的实例可被序列 ...