epoll用法【整理】
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
epoll的API:
#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用法【整理】的更多相关文章
- Spring JdbcTemplate用法整理
Spring JdbcTemplate用法整理: xml: <?xml version="1.0" encoding="UTF-8"?> <b ...
- 网络通信 --> epoll用法
epoll用法 在linux的网络编程中,很长的时间都在使用select来做事件触发.在linux新的内核中,有了一种替换它的机制,就是epoll. epoll函数 1. 创建epoll的句柄 siz ...
- linq用法整理
linq用法整理 普通查询 var highScores = from student in students where student.ExamScores[exam] > score se ...
- linux学习:特殊符号,数学运算,图像与数组与部分终端命令用法整理
指令:let.expr.array.convert.tput.date.read.md5.ln.apt.系统信息 一:特殊符号用法整理 系统变量 $# 是传给脚本的参数个数 $0 是脚本本身的名字 $ ...
- #ifndef#define#endif的用法(整理)
[转] #ifndef#define#endif的用法(整理) 原作者:icwk 文件中的#ifndef 头件的中的#ifndef,这是一个很关键的东西.比如你有两个C文件,这两个C文件都in ...
- Google Guava 库用法整理<转>
参考: http://codemunchies.com/2009/10/beautiful-code-with-google-collections-guava-and-static-imports- ...
- MySQL中使用SHOW PROFILE命令分析性能的用法整理(配合explain效果更好,可以作为优化周期性检查)
这篇文章主要介绍了MySQL中使用show profile命令分析性能的用法整理,show profiles是数据库性能优化的常用命令,需要的朋友可以参考下 show profile是由Jerem ...
- select,poll,epoll用法
http://blog.csdn.net/sunboy_2050/article/details/6126712 select用法 #include <sys/time.h> ...
- Android spannableStringBuilder用法整理
Android spannableStringBuilder用法整理 分类: Android开发2013-11-29 10:58 5009人阅读 评论(0) 收藏 举报 Androidspannabl ...
- OBJECTPROPERTY用法整理
OBJECTPROPERTY用法整理 分类: 系统表与表结构 数据库管理维护2010-06-03 16:37 2783人阅读 评论(1) 收藏 举报 数据库sql serverinsertobject ...
随机推荐
- @Query 注解实现查询(二十四)
为了节约时间使得各位看官看起来更加简单舒适,这一节把测试方法和测试代码放在一起. 测试方法: // ------------------------------------ 使用 @Query 注解 ...
- AutoFac IoC DI 依赖注入
AutoFac IoC DI 依赖注入 记录点点滴滴知识,为了更好的服务后来者! 一.为什么使用AutoFac? 之前介绍了Unity和Ninject两个IOC容器,但是发现园子里用AutoFac的貌 ...
- Android sdk 更新后编译不过,【Could not find com.android.sdklib.build.ApkBuilderMain】
最近更新了Android sdk,发现编译不过了 解决方案: 进入 sdk/tool/lib/ 目录下,看看有没有 sdklib.jar 这个文件,如果没有看看有没有sdklib-25.*.*.jar ...
- ADS1.2使用
ADS编译错误Error : A1163E: Unknown opcode ARM汇编指令不支持顶格写,否则不能识别,指令前加上空格即可. 使用for(;;;)//死循环,编译报错如下,说是该语句有错 ...
- spring-security-4 (5)spring security Java配置实现自定义表单认证与授权
前面三篇讲解了spring security的搭建以及简单的表单认证与授权原理.本篇将实现我们自定义的表单登录与认证. 本篇不会再讲项目的搭建过程,因为跟第二节的搭建如出一辙.本篇也不会将项目中所有 ...
- java的static研究
(1)static关键字:可以用于修饰属性.方法和类. 1,属性:无论一个类生成了多少个对象,所有这些对象共同使用唯一的一份静态的成员变量(不能修饰临时变量 2,方法:static修饰的方法叫做静态, ...
- 小米开源监控open-falcon
小米开源监控系统Open-Falcon安装使用笔记 07net01.com 发布于 2016-10-25 18:42:03 分类:IT技术 阅读(88) 评论 前言 近期爆出Zabbix有严重bug, ...
- java单例模式等一些程序的写法....持续更新...
一.单例模式的写法: public class MyFactory { /** * 饿汉式 */ private static MyFactory instance = new MyFactory() ...
- C++ 构造函数_初始化列表
构造函数初始化列表以一个冒号开始,接着是以逗号分隔的数据成员列表,每个数据成员后面跟一个放在括号中的初始化式.例如: class Student { public: //构造函数初始化列表 Stude ...
- Bootstrap-Other:可视化布局
ylbtech-Bootstrap-Other:可视化布局 1.返回顶部 1. 2. 2.返回顶部 3.返回顶部 4.返回顶部 5.返回顶部 1. http://www.runoob.co ...