其实在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. tomcat中catalina是什么(转)

    转自http://blog.sina.com.cn/s/blog_700aa8830101kgbk.html 谢谢博主的总结 catalina 就是Tomcat服务器使用的 Apache实现的serv ...

  2. NOPI Excel插件导入导出 图片批注

    dateTable导出到excel的MemoryStream /// <summary> /// DataTable导出到Excel的MemoryStream Export() /// & ...

  3. 未在本地计算机上注册“microsoft.ACE.oledb.12.0”提供程序 解决方法

    最近在搞asp.net.今天在做数据库操作的时候,老发生错误,还以为是自己代码有问题,检查了好久都发现错误. 错误提示: 未在本地计算机上注册“microsoft.ACE.oledb.12.0”提供程 ...

  4. 【CSS】梯形、平行四边形导航条与毛玻璃效果【转】

    转载出处:http://www.cnblogs.com/Uncle-Keith/p/5943158.html 代码部分有小改动. 导航条对于每一个Web前端攻城狮来说并不陌生,但是毛玻璃可能会相对陌生 ...

  5. 当我们说线程安全时,到底在说什么——Java进阶系列(二)

    原创文章,同步发自作者个人博客,转载请以超链接形式在文章开头处注明出处http://www.jasongj.com/java/thread_safe/ 多线程编程中的三个核心概念 原子性 这一点,跟数 ...

  6. MVCC PostgreSQL实现事务和多版本并发控制的精华

    原创文章,同步发自作者个人博客,http://www.jasongj.com/sql/mvcc/ PostgreSQL针对ACID的实现机制 事务的实现原理可以解读为RDBMS采取何种技术确保事务的A ...

  7. oracle select into 的时候提示未找到数据

    ); begin '; --在select into 后面添加exception 错误处理机制 exception when no_data_found then version:= 'hhh '; ...

  8. 写给笨蛋徒弟的学习手册(1)——完整C#项目中各个文件含义

    Bin 目录用来存放编译的结果,bin是二进制binrary的英文缩写,因为最初C编译的程序文件都是二进制文件,它有Debug和Release两个版本,分别对应的文件夹为bin/Debug和bin/R ...

  9. SpaceSniffer 硬盘透视软件

    SpaceSniffer是一个可以让您硬盘中文件和文件夹的分布情况的应用程序. 可以用来查找一些不易发现的文件,因为它提供一些特殊的搜索方式,特别是对于清理磁盘,找出占磁盘空间较大文件的位置很有帮助.

  10. Codeforces Round #175 (Div. 2)

    A. Slightly Decreasing Permutations 后\(k\)个倒序放前面,前\(n-k\)个顺序放后面. B. Find Marble 模拟. C. Building Perm ...