【网络】(十一)更高效的epoll
来源:互联网 发布:yum 安装snmp 编辑:程序博客网 时间:2024/05/16 09:47
第十篇文章中将select函数换做了poll,解决了客户端最大并发数量限制的问题,但通过压力测试会发现连接建立的速度是比较慢的,不管时select还是poll,都会存在这个问题。
select会受fd_set集合大小的限制,该大小不易更改,需要重新编译内核;同时也受系统所能打开的最大文件描述符的限制,该限制易更改。
poll只受系统所能打开的最大文件描述符的限制,前面也专门提到了该如何更改这个限制;该值的极限值受物理内存大小的限制,查看/proc/sys/fs/file-max文件可查看当前系统所能打开的最大文件描述符,4G内存的电脑上这一数值差不多有378675。该两者在使用时,都需要内核遍历所有的文件描述符,直到找到所有发生事件的文件描述符,并通知应用程序,所以当文件描述符非常多时,该两者的效率就会明显下降,这在上一篇中用压力测试客户端可看出来。
1、select、poll和epoll的区别
相比于select与poll,epoll最大的好处在于它不会随着监听文件描述符数目的增长而降低效率。
内核中的select与poll的实现是采用轮询来处理的,轮询的fd数目越多,自然耗时越多
epoll的实现是基于回调的,如果fd有期望的事件发生,就通过回调函数将其加入epoll就绪队列中,也就是说它只关心“活跃”的fd,与fd数目无关;
内核/用户空间内存拷贝问题,如何让内核把fd消息通知给用户空间呢?在这个问题上,select/poll采用内存拷贝方法,而epoll采用共享内存的方式;
epoll不仅会告诉应用程序有I/O事件到来,还会告诉应用程序相关的信息,这些信息是应用程序填充的,因此根据这些信息,应用程序就能直接定位到事件,而不必遍历整个fd集合
2、改进poll服务端
将上一篇文章中的服务端程序用epoll重新实现,提高效率!
handle.h/handle.c 与上一篇文章相同
客户端程序暂时不做更改,client.c/connclient.c 与上一篇文章相同
服务端源码
因为要使用容器,所以采用C++改写,编译命令
g++ -Wall -g server.cpp handle.c -o server
server.cpp
#include <string.h>#include <signal.h>#include <sys/epoll.h>#include "handle.h"#include <vector>#include <iostream>#include <algorithm>typedef std::vector<struct epoll_event> Eventlist;#define FD_MAXSIZE 2048 //最大客户端数量//为了防止SIGPIPE信号产生而终止了进程,所以捕获此信号void handle_sigpipe(void){ struct sigaction act; act.sa_handler = SIG_IGN; //忽略SIGPIPE信号 act.sa_flags = 0; sigemptyset(&act.sa_mask); if(sigaction(SIGPIPE, &act, NULL) == -1) handle_error("sigaction");}/** activate_noblock -- 设置IO为非阻塞模式* 参数 fd:套接字*/void activate_nonblock(int fd){ int iret = 0; int flags = fcntl(fd,F_GETFL); if(flags == -1) handle_error("fcntl"); flags |= O_NONBLOCK; iret = fcntl(fd,F_SETFL,flags); if(iret == -1) handle_error("fcntl");}int main(void){ init(FD_MAXSIZE); handle_sigpipe(); //捕获SIGPIPE信号 int sk_fd = socket(AF_INET, SOCK_STREAM , IPPROTO_TCP); if(sk_fd < 0) handle_error("socket"); //使用REUSEADDR,不必等待TIME_WAIT 状态消失,就可以重新使用端口 int on = 1; if(setsockopt(sk_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) { close(sk_fd); handle_error("setsockopt"); } struct sockaddr_in sr_addr; memset(&sr_addr,0,sizeof(sr_addr)); sr_addr.sin_family = AF_INET; sr_addr.sin_port = htons(5188); sr_addr.sin_addr.s_addr = htonl(INADDR_ANY); if(bind(sk_fd, (struct sockaddr*)&sr_addr, sizeof(sr_addr)) < 0) { close(sk_fd); handle_error("bind"); } //被动套接字 if(listen(sk_fd, SOMAXCONN) < 0) //内核为此套接字排队的最大连接数由SOMAXCONN宏指定 { close(sk_fd); handle_error("listen"); } std::vector<int> clients; //客户端连接套接字集合 int epfd; //epoll实例描述符 //创建一个EPOLL实例,EPOLL_CLOEXEC指定当进程被替换时,自动关闭epfd epfd = epoll_create1(EPOLL_CLOEXEC); struct epoll_event event; event.data.fd = sk_fd; //监听套接字 event.events = EPOLLIN | EPOLLET; //监听的事件 epoll_ctl(epfd, EPOLL_CTL_ADD, sk_fd, &event); //监听套接字添加事件管理 Eventlist events(16); //记录哪些IO产生了事件 struct sockaddr_in peeraddr; socklen_t peerlen; int conn_sk; int nready; int count = 0; //客户端数目 while(true) { //检测哪些IO发生了事件 nready = epoll_wait(epfd, &*events.begin(), static_cast<int>(events.size()), -1); if(nready == 0) { printf("epoll_wait time out!\n"); continue; } else if(nready == -1) { if(errno == EINTR) continue; handle_error("epoll_wait"); } if(nready == static_cast<int>(events.size())) //events集合空间不够用了 events.resize(events.size()*2); //遍历事件 for(int i = 0; i < nready; i++) { if(events[i].data.fd == sk_fd) //监听套接字发生了事件 { peerlen = sizeof(struct sockaddr_in); conn_sk = accept(sk_fd, (struct sockaddr*)&peeraddr, &peerlen); if(conn_sk < 0) { if(errno == EINTR) continue; handle_error("accept"); } clients.push_back(conn_sk); //已连接套接字添加到客户端套接字集合中 activate_nonblock(conn_sk); //设置套接字为非阻塞模式 event.data.fd = conn_sk; event.events = EPOLLIN | EPOLLET; //监听的事件 epoll_ctl(epfd, EPOLL_CTL_ADD, conn_sk, &event); //添加事件管理 printf("Count = %d: Connect ip = %s\tport = %d\n",count++,inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port)); } else if(events[i].events & EPOLLIN) { conn_sk = events[i].data.fd; if(conn_sk < 0) continue; char recvbuf[1024] = {0}; int iret = recvline(conn_sk,recvbuf,sizeof(recvbuf)); if(iret == -1) handle_error("recvline"); if(iret == 0) { printf("Client was closed!\n"); event = events[i]; //从监听集合中删除它 epoll_ctl(epfd, EPOLL_CTL_DEL, conn_sk,&event); //删除该客户端的已连接套接字 clients.erase(std::remove(clients.begin(), clients.end(),conn_sk), clients.end()); close(conn_sk); } fputs(recvbuf, stdout); writen(conn_sk, recvbuf, strlen(recvbuf)); } } } for(unsigned int i = 0; i < clients.size(); i++) { close(clients[i]); } close(sk_fd); return 0;}
3、压力测试
编译上一篇中的压力测试客户端:
gcc -Wall -g -std=gnu99 connclient.c -o client
通过连接服务端,可明显发现速度快多了!
- 【网络】(十一)更高效的epoll
- 为什么epoll比select更高效?
- 简单高效epoll网络模型
- 网络编程中的一个高效的epoll模型
- 高效的epoll
- Android---优化下载让网络访问更高效(一)
- Android---优化下载让网络访问更高效(二)
- Android---优化下载让网络访问更高效(三)
- Android---优化下载让网络访问更高效(四)
- Android---优化下载让网络访问更高效(一)
- Android---优化下载让网络访问更高效(二)
- Android---优化下载让网络访问更高效(三)
- Android---优化下载让网络访问更高效(四)
- epoll的高效实现原理
- epoll的高效实现原理
- epoll的高效实现原理
- epoll的高效实现原理
- epoll的高效实现原理
- 使用GCD
- IE7浏览器下CSS属性选择器二三事
- xcode 新建工程详解
- Java中的栈变量,堆变量
- 积跬步,至千里
- 【网络】(十一)更高效的epoll
- nand flash driver
- Android项目工作区结构
- Delphi的TDatetime转换成C#的DateTime
- 深入理解JVM内幕
- thinkphp3.2版本 分页类 page.class.php url错误
- 使用GCD(二)
- 虚拟机四种网络连接模式比较
- Firefox OS启动过程分析-b2g进程启动(序)