其实在Linux下设计并发网络程序,向来不缺少方法,比如典型的Apache模型(Process Per Connection,简称PPC),TPC(Thread PerConnection)模型,以及select模型和poll模型,那为何还要再引入Epoll这个东东呢?那还是有得说说的…

1. 常用模型的缺点

  如果不摆出来其他模型的缺点,怎么能对比出Epoll的优点呢。

1.1 PPC/TPC模型

  这两种模型思想类似,就是让每一个到来的连接一边自己做事去,别再来烦我。只是PPC是为它开了一个进程,而TPC开了一个线程。可是别烦我是有代价的,它要时间和空间啊,连接多了之后,那么多的进程/线程切换,这开销就上来了;因此这类模型能接受的  最大连接数都不会高,一般在几百个左右。

1.2 select模型

  1. 最大并发数限制,因为一个进程所打开的FD(文件描述符)是有限制的,由FD_SETSIZE设置,默认值是1024/2048,因此Select模型的最大并发数就被相应限制了。自己改改这个FD_SETSIZE?想法虽好,可是先看看下面吧…

  2. 效率问题,select每次调用都会线性扫描全部的FD集合,这样效率就会呈现线性下降,把FD_SETSIZE改大的后果就是,大家都慢慢来,什么?都超时了??!!

  3. 内核/用户空间 内存拷贝问题,如何让内核把FD消息通知给用户空间呢?在这个问题上select采取了内存拷贝方法。

1.3 poll模型

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

2. Epoll的提升

  把其他模型逐个批判了一下,再来看看Epoll的改进之处吧,其实把select的缺点反过来那就是Epoll的优点了。

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

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

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

3. Epoll为什么高效

  Epoll的高效和其数据结构的设计是密不可分的,这个下面就会提到。

  首先回忆一下select模型,当有I/O事件到来时,select通知应用程序有事件到了快去处理,而应用程序必须轮询所有的FD集合,测试每个FD是否有事件发生,并处理事件;代码像下面这样:

int res = select(maxfd+, &readfds, NULL, NULL, );

if(res > )

{

    for(int i = ; 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, , );

for(int i = ; i < res;i++)

{

    handleEvent(events[n]);

}

4. Epoll关键数据结构

  前面提到Epoll速度快和其数据结构密不可分,其关键数据结构就是:

struct epoll_event {

    __uint32_t events;      // Epoll events

    epoll_data_t data;      // User datavariable

};

typedef union epoll_data {

    void *ptr;

   int fd;

    __uint32_t u32;

    __uint64_t u64;

} epoll_data_t;

  可见epoll_data是一个union结构体,借助于它应用程序可以保存很多类型的信息:fd、指针等等。有了它,应用程序就可以直接定位目标了。

5. 使用Epoll

  epoll的使用主要在于三个函数。

  1. epoll_create(int size);

  创建一个epoll的句柄,size用来告诉内核这个监听的数目最大值。 注意!是数量的最大值,不是fd的最大值,切勿搞混。 当创建好epoll句柄后,它就是会占用一个fd值,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。

  2. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

  epoll的事件注册函数。 epfd是epoll的句柄,即epoll_create的返回值; op表示动作:用三个宏表示: EPOLL_CTL_ADD:注册新的fd到epfd中; EPOLL_CTL_MOD:修改已经注册的fd的监听事件; EPOLL_CTL_DEL:从epfd中删除一个fd; fd是需要监听的套接字描述符; event是设定监听事件的结构体,数据结构如下:

  

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 */
};
events可以是以下几个宏的集合:
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误; EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,就会把这个fd从epoll的队列中删除。
如果还需要继续监听这个socket的话,需要再次把这个fd加入到EPOLL队列里

  3. int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

  等待事件的产生,返回需要处理的事件的数量,并将需处理事件的套接字集合于参数events内,可以遍历events来处理事件。

  参数epfd为epoll句柄 events为事件集合 参数timeout是超时时间(毫秒,0会立即返回,-1是永久阻塞)。

  该函数返回需要处理的事件数目,如返回0表示已超时。

  4.使用实例

 #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 <errno.h>
#include <stdlib.h>
#include <string.h> #define MAXLINE 10 //最大长度
#define OPEN_MAX 100
#define LISTENQ 20
#define SERV_PORT 8000
#define INFTIM 1000
#define IP_ADDR "10.73.219.151" int main()
{
struct epoll_event ev, events[];
struct sockaddr_in clientaddr, serveraddr;
int epfd;
int listenfd;//监听fd
int maxi;
int nfds;
int i;
int sock_fd, conn_fd;
char buf[MAXLINE]; epfd = epoll_create();//生成epoll句柄
listenfd = socket(AF_INET, SOCK_STREAM, );//创建套接字
ev.data.fd = listenfd;//设置与要处理事件相关的文件描述符
ev.events = EPOLLIN;//设置要处理的事件类型 epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev);//注册epoll事件 memset(&serveraddr, , sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
serveraddr.sin_port = htons(SERV_PORT);
bind(listenfd,(struct sockaddr*)&serveraddr, sizeof(serveraddr));//绑定套接口
socklen_t clilen;
listen(listenfd, LISTENQ);//转为监听套接字
int n;
while()
{
nfds = epoll_wait(epfd,events,,);//等待事件发生
//处理所发生的所有事件
for(i=;i<nfds;i++)
{
if(events[i].data.fd == listenfd)//有新的连接
{
clilen = sizeof(struct sockaddr_in);
conn_fd = accept(listenfd, (struct sockaddr*)&clientaddr, &clilen);
printf("accept a new client : %s\n",inet_ntoa(clientaddr.sin_addr));
ev.data.fd = conn_fd;
ev.events = EPOLLIN;//设置监听事件为可写
epoll_ctl(epfd, EPOLL_CTL_ADD, conn_fd, &ev);//新增套接字
}
else if(events[i].events & EPOLLIN)//可读事件
{
if((sock_fd = events[i].data.fd) < )
continue;
if((n = recv(sock_fd, buf, MAXLINE, )) < )
{
if(errno == ECONNRESET)
{
close(sock_fd);
events[i].data.fd = -;
}
else
{
printf("readline error\n");
}
}
else if(n == )
{
close(sock_fd);
printf("关闭\n");
events[i].data.fd = -;
} printf("%d -- > %s\n",sock_fd, buf);
ev.data.fd = sock_fd;
ev.events = EPOLLOUT;
epoll_ctl(epfd,EPOLL_CTL_MOD,sock_fd,&ev);//修改监听事件为可读
} else if(events[i].events & EPOLLOUT)//可写事件
{
sock_fd = events[i].data.fd;
printf("OUT\n");
scanf("%s",buf);
send(sock_fd, buf, MAXLINE, ); ev.data.fd = sock_fd;
ev.events = EPOLLIN;
epoll_ctl(epfd, EPOLL_CTL_MOD,sock_fd, &ev);
}
}
} return ;
}

Linux Epoll相关知识的更多相关文章

  1. select poll epoll相关知识速记

    缘起 面试的时候经常被问的一个很蛋疼的问题,经常被问,但是知识很零散,难记忆,看完就忘 select 作用 可以监视文件描述符是否可以读写,要求监视的文件描述符是非阻塞的 诞生背景 产生与上个世纪80 ...

  2. linux信号处理相关知识

      因为要处理最近项目中碰上的多个子进程退出信号同时到达,导致程序不当产生core的情况,今天我花了时间看了一些关于linux信号处理的博客. 总结一下:(知识未经实践) linux信号分两种,一种实 ...

  3. Linux中相关知识(atexit(),fork(),粘滞位)

    1.atexit()函数 函数名: atexit 头文件:#include<stdlib.h> 功 能: 注册终止函数(即main执行结束后调用的函数) 用 法: int atexit(v ...

  4. linux编译相关知识

    (1)用g++编译程序时,-l 与-L各是什么意思 http://bbs.chinaunix.net/thread-107364-1-1.html 感谢作者 -l 表示:编译程序到系统默认路进搜索,如 ...

  5. < 独立项目 - 文本挖掘 > - 2016/10/25 第一更 - <Linux相关知识准备>

    < 独立项目 -  文本挖掘 > 项目立项的相关背景介绍,TODO方向. 一.Ubuntu环境配置 主机系统:Windows 7 SP1  64位操作系统 | i5-4210 CPU | ...

  6. linux 创建守护进程的相关知识

    linux 创建守护进程的相关知识 http://www.114390.com/article/46410.htm linux 创建守护进程的相关知识,这篇文章主要介绍了linux 创建守护进程的相关 ...

  7. Linux入门之安装及相关知识。

    一.VMware虚拟机安装与使用 1.1.VMware 简介 VMware是一个虚拟PC的软件,可以在现有的操 作系统上虚拟出一个新的硬件环境,相当于模拟 出一台新的PC.以此来实现在一台机器上真正 ...

  8. LINUX涉及网络相关知识

    才接触到网络的老铁,是否比较晕呢? 简单记录一下网络相关知识吧(IPV4)! A0. 网络号.主机号 A1.网络地址分类: A2. 保留地址: A3. 子网掩码作用:(子网掩码.IPV4地址做“与”运 ...

  9. Linux相关知识笔记

    Quagga要在linux下编译并配置运行,所有,学习一点linux的基础知识. 安装的Ubuntu,用户名linux,密码1 使能Ubuntu的IP转发功能,需要修改etc/sysctl.conf和 ...

随机推荐

  1. NOIP 考前 高斯消元练习

    POJ 1830 列出n个方程右边为最后的情况 每一行代表第几个灯的情况,每一行代表是否按第几个按钮写出方程即可. #include <cstdio> #include <cstri ...

  2. 【Python】re正则表达式

    简单举几个常用的re正则表达式的例子: m = re.match("abc", "abcdef") print(m.group()) # 输出:abc m = ...

  3. Android ListView 多样式Item的一个注意点:(

    闲来无事,在写一个多样式Item的ListView的Demo时,遇到了一个以前没遇过的问题; ╮( ̄▽ ̄")╭ 我们知道,ListView里可以有多种样式的item, 实现只需要重写: @O ...

  4. commitizen-规范commit-message

    安装指南 安装commitizen sudo npm install -g commitizen 配置 cd到.git所在目录 commitizen init cz-conventional-chan ...

  5. WP7、WP8 格式化时间为距当前多少时间

    方法一: 使用 toolkit的 RelativeTimeConverter,使用方式 <phone:PhoneApplicationPage.Resources> <toolkit ...

  6. Bool 类型变量的使用

    定义一个bool类型的变量,默认为FALSE的 private bool BHaveBeenTip=false; private void label5_Click(object sender, Ev ...

  7. C#多线程线程

    “线程同步”的含义   当一个进程启动了多个线程时,如果需要控制这些线程的推进顺序(比如A线程必须等待B和C线程执行完毕之后才能继续执行),则称这些线程需要进行“线程同步(thread synchro ...

  8. css技巧收集

    1. 使用 :not() 为导航添加/取消边框 传统的方法为导航栏添加边框: /* add border */ .nav li { border-right: 1px solid #666; } /* ...

  9. Word Break

    Given a string s and a dictionary of words dict, determine if s can be segmented into a space-separa ...

  10. cocos多点触控实效注意事项

    一定要设置AppController.mm的 [eaglView setMultipleTouchEnabled:YES];