- 浏览: 271911 次
- 来自: 广州
最新评论
-
cloudmail:
http://blog.csdn.net/zhang_red/ ...
awk合并行、linux shell 字符串操作、sed字符替换 -
cloudmail:
逐行读取文件时支持读取以空格开头的内容:IFS="& ...
awk合并行、linux shell 字符串操作、sed字符替换 -
cloudmail:
sina返回的结果:[{"url_short&quo ...
PHP调用t.cn新浪短网址服务API(JSON版) -
cloudmail:
http://www.dwz.cn/http://help.b ...
PHP调用t.cn新浪短网址服务API(JSON版) -
cloudmail:
for i in {1..2000}; do wget -O ...
PHP调用t.cn新浪短网址服务API(JSON版)
1.简介
Linux I/O多路复用技术在比较多的TCP网络服务器中有使用,即比较多的用到select函数。Linux 2.6内核中有提高网络I/O性能的新方法,即epoll 。 epoll是什么?按照man手册的说法是为处理大批量句柄而作了改进的poll。要使用epoll只需要以下的三个系统函数调用: epoll_create(2),epoll_ctl(2),epoll_wait(2)。
2.select模型的缺陷
(1) 在Linux内核中,select所用到的FD_SET是有限的
内核中有个参数__FD_SETSIZE定义了每个FD_SET的句柄个数:#define __FD_SETSIZE 1024。也就是说,如果想要同时检测1025个句柄的可读状态是不可能用select实现的;或者同时检测1025个句柄的可写状态也是不可能的。
(2) 内核中实现select是使用轮询方法
每次检测都会遍历所有FD_SET中的句柄,显然select函数的执行时间与FD_SET中句柄的个数有一个比例关系,即select要检测的句柄数越多就会越费时
3.Windows IOCP模型的缺陷
windows完成端口实现的AIO,实际上也只是使用内部用线程池实现的,最后的结果是IO有个线程池,你的应用程序也需要一个线程池。很多文档其实已经指出了这引发的线程context-switch所带来的代价。
4.EPOLL模型的优点
(1) 支持一个进程打开大数目的socket描述符(FD)
epoll没有select模型中的限制,它所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于select 所支持的2048
(2) IO效率不随FD数目增加而线性下降
传统select/poll的另一个致命弱点就是当你拥有一个很大的socket集合,由于网络得延时,使得任一时间只有部分的socket是"活跃"的,而select/poll每次调用都会线性扫描全部的集合,导致效率呈现线性下降。但是epoll不存在这个问题,它只会对"活跃"的socket进行操作:这是因为在内核实现中epoll是根据每个fd上面的callback函数实现的。于是,只有"活跃"的socket才会主动去调用callback函数,其他idle状态的socket则不会。在这点上,epoll实现了一个"伪"AIO",因为这时候推动力在os内核。在一些 benchmark中,如果所有的socket基本上都是活跃的,比如一个高速LAN环境,epoll也不比select/poll低多少效率,但若过多使用的调用epoll_ctl,效率稍微有些下降。然而一旦使用idle connections模拟WAN环境,那么epoll的效率就远在select/poll之上了。
(3) 使用mmap加速内核与用户空间的消息传递
无论是select,poll还是epoll都需要内核把FD消息通知给用户空间,如何避免不必要的内存拷贝就显得很重要。在这点上,epoll是通过内核于用户空间mmap同一块内存实现。
5.EPOLL模型的工作模式
(1) LT模式
LT:level triggered,这是缺省的工作方式,同时支持block和no-block socket,在这种模式中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表。
(2) ET模式
LT:edge-triggered,这是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核就通过epoll告诉你,然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作而导致那个文件描述符不再是就绪状态(比如你在发送,接收或是接受请求,或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK 错误)。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核就不会发送更多的通知(only once)。不过在TCP协议中,ET模式的加速效用仍需要更多的benchmark确认。
6.EPOLL模型的使用方法
epoll用到的所有函数都是在头文件sys/epoll.h中声明的,下面简要说明所用到的数据结构和函数:
(1) epoll_data、epoll_data_t、epoll_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 */
};
结构体epoll_event 被用于注册所感兴趣的事件和回传所发生待处理的事件。epoll_event 结构体的events字段是表示感兴趣的事件和被触发的事件,可能的取值为:
EPOLLIN: 表示对应的文件描述符可以读;
EPOLLOUT: 表示对应的文件描述符可以写;
EPOLLPRI: 表示对应的文件描述符有紧急的数据可读;
EPOLLERR: 表示对应的文件描述符发生错误;
EPOLLHUP: 表示对应的文件描述符被挂断;
EPOLLET: 表示对应的文件描述符有事件发生;
联合体epoll_data用来保存触发事件的某个文件描述符相关的数据。例如一个client连接到服务器,服务器通过调用accept函数可以得到于这个client对应的socket文件描述符,可以把这文件描述符赋给epoll_data的fd字段,以便后面的读写操作在这个文件描述符上进行。
(2)epoll_create
函数声明:intepoll_create(intsize)
函数说明:该函数生成一个epoll专用的文件描述符,其中的参数是指定生成描述符的最大范围。
(3) epoll_ctl函数
函数声明:intepoll_ctl(int epfd,int op, int fd, struct epoll_event *event)
函数说明:该函数用于控制某个文件描述符上的事件,可以注册事件、修改事件、删除事件。
epfd:由 epoll_create 生成的epoll专用的文件描述符;
op:要进行的操作,可能的取值EPOLL_CTL_ADD 注册、EPOLL_CTL_MOD 修改、EPOLL_CTL_DEL 删除;
fd:关联的文件描述符;
event:指向epoll_event的指针;
如果调用成功则返回0,不成功则返回-1。
(4) epoll_wait函数
函数声明:int epoll_wait(int epfd, structepoll_event * events, int maxevents, int timeout)
函数说明:该函数用于轮询I/O事件的发生。
epfd:由epoll_create 生成的epoll专用的文件描述符;
epoll_event:用于回传代处理事件的数组;
maxevents:每次能处理的事件数;
timeout:等待I/O事件发生的超时值;
返回发生事件数。
设计思路:
首先通过create_epoll(int maxfds)来创建一个epoll的句柄,其中maxfds为你的epoll所支持的最大句柄数。这个函数会返回一个新的epoll句柄,之后的所有操作都将通过这个句柄来进行操作。在用完之后,记得用close()来关闭这个创建出来的epoll句柄。
然后在你的网络主循环里面,调用epoll_wait(int epfd, epoll_event events, int max_events,int timeout)来查询所有的网络接口,看哪一个可以读,哪一个可以写。基本的语法为:
nfds = epoll_wait(kdpfd, events, maxevents, -1);
其中kdpfd为用epoll_create创建之后的句柄,events是一个epoll_event*的指针,当epoll_wait函数操作成功之后,events里面将储存所有的读写事件。max_events是当前需要监听的所有socket句柄数。最后一个timeout参数指示 epoll_wait的超时条件,为0时表示马上返回;为-1时表示函数会一直等下去直到有事件返回;为任意正整数时表示等这么长的时间,如果一直没有事件,则会返回。一般情况下如果网络主循环是单线程的话,可以用-1来等待,这样可以保证一些效率,如果是和主循环在同一个线程的话,则可以用0来保证主循环的效率。epoll_wait返回之后,应该进入一个循环,以便遍历所有的事件。
对epoll 的操作就这么简单,总共不过4个API:epoll_create, epoll_ctl,epoll_wait和close。以下是man中的一个例子。
struct epoll_event ev, *events; for(;;) { nfds = epoll_wait(kdpfd, events, maxevents, -1); //等待IO事件 for(n = 0; n < nfds; ++n) { //如果是主socket的事件,则表示有新连接进入,需要进行新连接的处理。 if(events[n].data.fd == listener) { client = accept(listener, (struct sockaddr *) &local, &addrlen); if(client < 0) { perror("accept error"); continue; } // 将新连接置于非阻塞模式 setnonblocking(client); ev.events = EPOLLIN | EPOLLET; //注意这里的参数EPOLLIN | EPOLLET并没有设置对写socket的监听, //如果有写操作的话,这个时候epoll是不会返回事件的, //如果要对写操作也监听的话,应该是EPOLLIN | EPOLLOUT | EPOLLET。 // 并且将新连接也加入EPOLL的监听队列 ev.data.fd = client; // 设置好event之后,将这个新的event通过epoll_ctl if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, client, &ev) < 0) { //加入到epoll的监听队列里,这里用EPOLL_CTL_ADD //来加一个新的 epoll事件。可以通过EPOLL_CTL_DEL来减少 //一个epoll事件,通过EPOLL_CTL_MOD来改变一个事件的监听方式。 fprintf(stderr, "epoll set insertion error: fd=%d"0, client); return -1; } } else // 如果不是主socket的事件的话,则代表这是一个用户的socket的事件, // 则用来处理这个用户的socket的事情是,比如说read(fd,xxx)之类,或者一些其他的处理。 do_use_fd(events[n].data.fd); } } |
7.EPOLL模型的一个实例
#include <iostream> #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>
#define MAXLINE 10 #define OPEN_MAX 100 #define LISTENQ 20 #define SERV_PORT 5555 #define INFTIM 1000
void setnonblocking(int sock) { int opts; opts = fcntl(sock, F_GETFL); if(opts < 0) { perror("fcntl(sock, GETFL)"); exit(1); } opts = opts | O_NONBLOCK; if(fcntl(sock, F_SETFL, opts) < 0) { perror("fcntl(sock,SETFL,opts)"); exit(1); } } int main() { int i, maxi, listenfd, connfd, sockfd, epfd, nfds; ssize_t n; char line[MAXLINE]; socklen_t clilen; //声明epoll_event结构体的变量, ev用于注册事件, events数组用于回传要处理的事件 struct epoll_event ev,events[20]; //生成用于处理accept的epoll专用的文件描述符, 指定生成描述符的最大范围为256 epfd = epoll_create(256); struct sockaddr_in clientaddr; struct sockaddr_in serveraddr; listenfd = socket(AF_INET, SOCK_STREAM, 0);
setnonblocking(listenfd); //把用于监听的socket设置为非阻塞方式 ev.data.fd = listenfd; //设置与要处理的事件相关的文件描述符 ev.events = EPOLLIN | EPOLLET; //设置要处理的事件类型 epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev); //注册epoll事件 bzero(&serveraddr, sizeof(serveraddr)); serveraddr.sin_family = AF_INET; char *local_addr = "200.200.200.204"; inet_aton(local_addr, &(serveraddr.sin_addr)); serveraddr.sin_port = htons(SERV_PORT); //或者htons(SERV_PORT); bind(listenfd,(sockaddr *)&serveraddr, sizeof(serveraddr)); listen(listenfd, LISTENQ); maxi = 0; for( ; ; ) { nfds = epoll_wait(epfd, events, 20, 500); //等待epoll事件的发生 for(i = 0; i < nfds; ++i) //处理所发生的所有事件 { if(events[i].data.fd == listenfd) //监听事件 { connfd = accept(listenfd, (sockaddr *)&clientaddr, &clilen); if(connfd < 0) { perror("connfd<0"); exit(1); } setnonblocking(connfd); //把客户端的socket设置为非阻塞方式 char *str = inet_ntoa(clientaddr.sin_addr); std::cout << "connect from " << str <<std::endl; ev.data.fd=connfd; //设置用于读操作的文件描述符 ev.events=EPOLLIN | EPOLLET; //设置用于注测的读操作事件 epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev); //注册ev事件 } else if(events[i].events&EPOLLIN) //读事件 { if ( (sockfd = events[i].data.fd) < 0) { continue; } if ( (n = read(sockfd, line, MAXLINE)) < 0) // 这里和IOCP不同 { if (errno == ECONNRESET) { close(sockfd); events[i].data.fd = -1; } else { std::cout<<"readline error"<<std::endl; } } else if (n == 0) { close(sockfd); events[i].data.fd = -1; } ev.data.fd=sockfd; //设置用于写操作的文件描述符 ev.events=EPOLLOUT | EPOLLET; //设置用于注测的写操作事件 //修改sockfd上要处理的事件为EPOLLOUT epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, &ev); } else if(events[i].events&EPOLLOUT)//写事件 { sockfd = events[i].data.fd; write(sockfd, line, n); ev.data.fd = sockfd; //设置用于读操作的文件描述符 ev.events = EPOLLIN | EPOLLET; //设置用于注册的读操作事件 //修改sockfd上要处理的事件为EPOLIN epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, &ev); } } } } |
8.EPOLL进阶思考
8.1. 问题来源
最近学习EPOLL模型,介绍中说将EPOLL与Windows IOCP模型进行比较,说其的优势在于解决了IOCP模型大量线程上下文切换的开销,于是可以看出,EPOLL模型不需要多线程,即单线程中可以处理EPOLL逻辑。如果引入多线程反而会引起一些问题。但是EPOLL模型的服务器端到底可以不可以用多线程技术,如果可以,改怎么取舍,这成了困扰我的问题。上网查了一下,有这样几种声音:
(1) “要么事件驱动(如epoll),要么多线程,要么多进程,把这几个综合起来使用,感觉更加麻烦。”;
(2) “单线程使用epoll,但是不能发挥多核;多线程不用epoll。”;
(3) “主通信线程使用epoll所有需要监控的FD,有事件交给多线程去处理”;
(4) “既然用了epoll, 那么线程就不应该看到fd, 而只看到的是一个一个的业务请求/响应; epoll将网络数据组装成业务数据后, 转交给业务线程进行处理。这就是常说的半同步半异步”。
我比较赞同上述(3)、(4)中的观点
EPOLLOUT只有在缓冲区已经满了,不可以发送了,过了一会儿缓冲区中有空间了,就会触发EPOLLOUT,而且只触发一次。如果你编写的程序的网络IO不大,一次写入的数据不多的时候,通常都是epoll_wait立刻就会触发 EPOLLOUT;如果你不调用 epoll,直接写 socket,那么情况就取决于这个socket的缓冲区是不是足够了。如果缓冲区足够,那么写就成功。如果缓冲区不足,那么取决你的socket是不是阻塞的,要么阻塞到写完成,要么出错返回。所以EPOLLOUT事件具有较大的随机性,ET模式一般只用于EPOLLIN, 很少用于EPOLLOUT。
8.2. 具体做法
(1) 主通信线程使用epoll所有需要监控的FD,负责监控listenfd和connfd,这里只监听EPOLLIN事件,不监听EPOLLOUT事件;
(2) 一旦从Client收到了数据以后,将其构造成一个消息,放入消息队列中;
(3) 若干工作线程竞争,从消息队列中取出消息并进行处理,然后把处理结果发送给客户端。发送客户端的操作由工作线程完成。直接进行write。write到EAGAIN或EWOULDBLOCK后,线程循环continue等待缓冲区队列
发送函数代码如下:
bool send_data(int connfd, char *pbuffer, unsigned int &len,int flag) { if ((connfd < 0) || (0 == pbuffer)) { return false; }
int result = 0; int remain_size = (int) len; int send_size = 0; const char *p = pbuffer; time_t start_time = time(NULL); int time_out = 3; do { if (time(NULL) > start + time_out) { return false; } send_size = send(connfd, p, remain_size, flag); if (nSentSize < 0) { if ((errno == EAGAIN) || (errno == EWOULDBLOCK) || (errno == EINTR)) { continue; } else { len -= remain_size; return false; } }
p += send_size; remain_size -= send_size; }while(remain_size > 0); return true; } |
http://www.linuxidc.com/Linux/2012-04/57876.htm
发表评论
-
使用dig查询dns解析
2017-03-06 17:22 0dig命令使用大全(linux上域名查询) Dig HOWT ... -
awk用法小结
2016-09-20 14:23 3532原文网址: http://www.cnblogs.com/ ... -
nginx内置预定义变量
2015-01-25 21:25 1026nginx的配置文件中可以使用的内置变量以美元符$开始,也有 ... -
awk合并行、linux shell 字符串操作、sed字符替换
2015-01-18 16:13 4263四行合一行 cat filename 111111 ... -
PHP调用t.cn新浪短网址服务API(JSON版)
2014-11-08 14:25 7923没什么好讲了,重要的地方我都在代码中注释了,主要是APP_ ... -
【转】How To Use Dropbox As An SVN Repository
2014-01-18 18:53 865How To Use Dropbox As An SVN R ... -
【转】时区简写及时间格式解析
2013-11-11 15:33 19771今天在解析CST时区时发现时间不对,原来使用的代码将CS ... -
C 语言执行 whois 查询
2013-06-28 11:09 1127http://www.binarytides.com/c-c ... -
在Redhat Linux上安装和使用subversion
2012-10-17 18:28 24091,安装: wget http://m ... -
【转】 使用 Bash 编写的 TCP 端口扫描器
2012-09-03 12:50 1350我突然有一个用 Bash 来编写 TCP 端口扫描器的想 ... -
Linux Shell实现"多进程"编程
2012-09-02 20:14 2397单进程模式代码: #!/bin/sh pl ... -
Perl使用Net::SMTP_auth认证发送邮件
2012-08-29 17:33 3177#!/usr/bin/perl -w # http:/ ... -
Perl使用Net::SMTP_auth认证发邮件
2012-08-29 17:28 1#!/usr/bin/perl -w # http: ... -
通过awk在Linux下打乱文件内容的顺序
2012-06-26 18:17 4938假设一个文件是已经排好序的,现随机抽取里面的部分。为了达到随机 ... -
EDM邮箱数据库设计
2012-06-13 18:06 29一,业务需求: 1) 分页获取某企业下的邮箱列表,按最 ... -
一步一步学PHP(1): Windows下配置Nginx + PHP开发环境
2012-05-21 23:22 27041,下载并安装nginx 1.1 下载地址:http://n ... -
MySQL导出指定表中的数据
2012-05-21 12:44 0MySQL导出指定表中的数据 要求: 1. ... -
用shell脚本实现递归创建目录
2012-05-09 17:19 6020用shell脚本实现递归创建目录,相当于mkdir -p /t ... -
ALPHA、BETA、RC、GA版本的区别
2012-05-08 13:04 1115Alpha:是内部测试版, ... -
在RHEL5 源码编译安装MySQL 5.5.23
2012-05-04 14:43 15241.安装前装备 1.1已经安装gcc(gcc-c++) ...
相关推荐
本项目“Linux 网络库 C++ epoll”是针对这一需求而设计的一个实验性质的网络库,它充分利用了Linux内核提供的epoll机制,实现了高度优化的异步事件驱动模型。 Epoll(Event Poll)是Linux内核提供的一种I/O多路...
在Linux系统中,`epoll`是用于I/O多路复用的一种高效机制,尤其适合高并发、大连接数的网络编程场景。本资源提供的"Linux C++ epoll使用范例"包含了客户端、服务端以及一个测试程序,旨在帮助开发者更好地理解和运用...
网络编程中的`epoll`模型是Linux操作系统提供的一种高效、高...结合C++编程,可以构建出稳定、高效的网络服务应用程序。在实际项目中,理解并熟练掌握`epoll`的原理和使用方法,对于提升系统的可扩展性和性能至关重要。
在对王健伟老师的Linux C++网络编程课程进行辅助学习时,工程思维导图作为一种图形化思维工具,可以帮助学生更系统地理解和掌握复杂的知识点。这份资料中提到的内容涉及到网络编程的多个关键概念和实现细节,下面将...
在C++ Linux网络编程中,开发者利用C++语言与Linux操作系统强大的网络功能相结合,构建高效、可靠的网络应用程序。本资料包“C++ LINUX网络编程.zip”可能包含了关于这个主题的各种资源,如源代码示例、教程文档等。...
《C++网络编程 卷1和卷2 以及Linux多线程服务端编程》是由知名技术专家陈硕编著的一套全面深入的编程教程,涵盖了C++网络编程的基础与高级应用,以及在Linux环境下实现多线程服务端编程的技术精髓。这套教程对于深入...
本项目使用C++编程语言,在Ubuntu环境下开发,利用`g++`编译器进行编译,包含了`makefile`来自动化构建过程。下面我们将深入探讨`epoll`网络模型及其相关知识点。 **epoll的核心概念:** 1. **I/O多路复用**:这是...
在Linux系统中,TCP网络编程通常涉及到大量的I/O操作,对于高并发的场景,传统的轮询或阻塞I/O模型效率低下。此时,`epoll`接口就显得尤为重要,它是Linux提供的一种高效I/O事件通知机制,特别适用于多路复用的场景...
本示例以"网络编程模型示例"为主题,采用C++编程语言实现,并且已经在Linux环境下进行了测试并通过验证。我们将深入探讨这个主题,重点关注C++在实现事件驱动网络编程模型中的应用。 1. **事件驱动模型** 事件驱动...
3. **套接字编程模型**:学习如何利用socket API在Linux下进行网络通信,包括创建套接字、绑定地址、监听连接请求等关键步骤。 #### (二)实践篇 1. **TCP服务器与客户端编程**:通过具体实例展示如何使用C语言在...
在IT领域,Linux环境下的C++ Socket编程是网络通信的核心技术之一。Socket接口提供了一种在不同进程间通过网络进行通信的方法,无论这些进程是否在同一台机器上。本压缩包包含了一个简单的TCP服务器(TCPServer.zip...
在Linux操作系统中,C++编程语言常常用于构建高性能的网络服务器,特别是在处理大量并发连接时。本项目聚焦于使用C++封装的`epoll`事件驱动模型,采用边缘触发(Edge Triggered,ET)模式来设计一个服务器。`epoll`...
这个"C++ epoll client与server stocket连接通信例子"是一个很好的学习资源,它可以帮助开发者理解如何在实际项目中运用epoll进行高效的网络编程。通过阅读和运行代码,可以深入理解epoll的工作机制以及如何与C++...
1. **Linux网络编程基础**:首先,书中会介绍Linux下的网络编程基础知识,包括套接字API,TCP/IP协议栈的工作原理,以及I/O模型,如同步I/O、异步I/O和多路复用I/O(如select、poll、epoll)等。这些是构建服务端...
在C++编程领域,面向对象(Object-Oriented Programming,OOP)是一种广泛采用的编程范式,它强调数据和操作数据的方法结合在一起,形成一个独立的单元——对象。在这个"C++面向对象网络编程实例"中,我们看到作者...
### Linux Epoll编程实例解析 #### 一、Epoll简介 `epoll` 是 Linux 内核中的一个 I/O 复用技术,用于高效地管理大量并发连接。它通过使用事件驱动模型,允许应用程序监听多个文件描述符上的事件,如读写事件。与...
3. **异步I/O模型**:深入理解各种I/O模型,如阻塞I/O、非阻塞I/O、IO复用(select、poll、epoll)、信号驱动I/O和异步I/O,以及它们在服务器编程中的应用场景和优缺点。 4. **网络编程**:详细讲解TCP/IP协议栈,...
Linux网络编程(总共41集) 讲解Linux网络编程知识,分以下四个篇章。 Linux网络编程之TCP/IP基础篇 Linux网络编程之socket编程篇 Linux网络编程之进程间通信篇 Linux网络编程之线程篇 Linux网络编程之TCP/IP...
本文将详细介绍如何使用现代C++语言和muduo网络库在Linux平台上编写高性能的多线程TCP网络服务器,以及与此相关的各种编程模型、同步原语、进程间通信IPC机制、编程实践中的常见问题及其解决方案。 在Linux多线程...