Socket网络编程--epoll小结
以前使用的用于I/O多路复用为了方便就使用select函数,但select这个函数是有缺陷的。因为它所支持的并发连接数是有限的(一般小于1024),因为用户处理的数组是使用硬编码的。这个最大值为FD_SETSIZE,这是在<sys/select.h>中的一个常量,它说明了最大的描述符数。但是对于大多数应用程序而言,这个数是够用的,而且有可能还是太大的,多数应用程序只使用3~10个描述符。而如今的网络服务器小小的都有几万的连接,虽然可以使用多线程多进程(也就有N*1024个)。但是这样处理起来既不方面,性能又低。
同时期有I/O多路复用的还有一个poll函数,这个函数类似于select,但是其应用程序接口有所不用。原型如下
#include <poll.h>
int poll(struct pollfd fdarray[], nfds_t nfds, int timeout);//返回值: 准备就绪的描述符数,若超时则返回0,出错返回-1
如果只是考虑性能的话,poll()也是不合适的,尽管它可以支持较高的TCP并发连接数,但是由于其采用“轮询”机制(遍历数组而已),但并发数较高时,其运行效率相当低(如果有10k个连接,单用于轮询的时间就需要1~10ms了),同时还可能存在I/O事件分配不均,导致部分TCP连接上的I/O出现“饥饿”现象。基于种种原因在Linux 2.5.44版本后poll被epoll取代。
支持一个进程打开最大数目的 socket 描述符(FD)。select 最不能忍受的是一个进程所打开的FD 是有一定限制的,由 FD_SETSIZE 设置,默认值是 2048。对于那些需要支持的上万连接数目的 IM 服务器来说显然太少了。这时候你一是可以选择修改这个宏然后重新编译内核,不过资料也同时指出这样会带来网络效率的下降,二是可以选择多进程的解决方案(传统的 Apache 方案Process Per Connection,TPC方案 Thread Per Connection),不过虽然 linux 上面创建进程的代价比较小,但仍旧是不可忽视的,加上进程间数据同步远比不上线程间同步的高效,所以也不是一种完美的方案。不过 epoll 则没有这个限制,它所支持的 FD 上限是最大可以打开文件的数目,这个数字一般远大于 2048,举个例子,在 1GB 内存的机器上大约是 10 万左右,具体数目可以 cat /proc/sys/fs/file-max 察看,一般来说这个数目和系统内存关系很大。
由于epoll这个函数是后增加上的,造成现在很少有资料提及到,我看了APUE,UNPv1等书都没有找到相关的函数原型。所以我只能从网络上抄一些函数原型过来了。
epoll用到的所有函数都是在头文件sys/epoll.h中声明,有什么地方不明白或函数忘记了可以去看一下或者man epoll。epoll和select相比,最大不同在于:
epoll返回时已经明确的知道哪个sokcet fd发生了事件,不用再一个个比对(轮询)。这样就提高了效率。select的FD_SETSIZE是有限制的,而epoll是没有限制的只与系统资源有关。
epoll_create函数
/* Creates an epoll instance. Returns an fd for the new instance.
The "size" parameter is a hint specifying the number of file
descriptors to be associated with the new instance. The fd
returned by epoll_create() should be closed with close(). */
extern int epoll_create (int __size) __THROW;
该函数生成一个epoll专用的文件描述符。它其实是在内核申请空间,用来存放你想关注的socket fd上是否发生以及发生了什么事件。size就是你在这个epoll fd上能关注的最大socket fd数。这个数没有select的1024约束。
epoll_ctl函数
/* Manipulate an epoll instance "epfd". Returns 0 in case of success,
-1 in case of error ( the "errno" variable will contain the
specific error code ) The "op" parameter is one of the EPOLL_CTL_*
constants defined above. The "fd" parameter is the target of the
operation. The "event" parameter describes which events the caller
is interested in and any associated user data. */
extern int epoll_ctl (int __epfd, int __op, int __fd,
struct epoll_event *__event) __THROW;
/* Valid opcodes ( "op" parameter ) to issue to epoll_ctl(). */
#define EPOLL_CTL_ADD 1 /* Add a file descriptor to the interface. */
#define EPOLL_CTL_DEL 2 /* Remove a file descriptor from the interface. */
#define EPOLL_CTL_MOD 3 /* Change file descriptor epoll_event structure. */
该函数用于控制某个epoll文件描述符上的事件,可以注册事件,修改事件,删除事件。
参数: epfd:由 epoll_create 生成的epoll专用的文件描述符;
op:要进行的操作例如注册事件,可能的取值EPOLL_CTL_ADD 注册、EPOLL_CTL_MOD 修 改、EPOLL_CTL_DEL 删除
fd:关联的文件描述符;
event:指向epoll_event的指针;
返回值:如果调用成功返回0,不成功返回-1
用到的数据结构
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 */
};
设置实例
struct epoll_event ev;
//设置与要处理的事件相关的文件描述符
ev.data.fd=listenfd;
//设置要处理的事件类型
ev.events=EPOLLIN|EPOLLET;
//注册epoll事件
epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);
//常用的事件类型:
EPOLLIN :表示对应的文件描述符可以读;
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET:表示对应的文件描述符有事件发生;
//具体可以看<sys/epoll.h>
epoll_wait函数
/* Wait for events on an epoll instance "epfd". Returns the number of
triggered events returned in "events" buffer. Or -1 in case of
error with the "errno" variable set to the specific error code. The
"events" parameter is a buffer that will contain triggered
events. The "maxevents" is the maximum number of events to be
returned ( usually size of "events" ). The "timeout" parameter
specifies the maximum wait time in milliseconds (-1 == infinite).
This function is a cancellation point and therefore not marked with
__THROW. */
extern int epoll_wait (int __epfd, struct epoll_event *__events,
int __maxevents, int __timeout); /* Same as epoll_wait, but the thread's signal mask is temporarily
and atomically replaced with the one provided as parameter.
This function is a cancellation point and therefore not marked with
__THROW. */
extern int epoll_pwait (int __epfd, struct epoll_event *__events,
int __maxevents, int __timeout,
__const __sigset_t *__ss);
该函数用于轮询I/O事件的发生;
参数: epfd:由epoll_create 生成的epoll专用的文件描述符;
epoll_event:用于回传代处理事件的数组;
maxevents:每次能处理的事件数;
timeout:等待I/O事件发生的超时值(单位应该是ms);-1相当于阻塞,0相当于非阻塞。一般用-1即可
返回值:返回发生事件数。如出错则返回-1。
下面给一个man手册里面的例子
#define MAX_EVENTS 10
struct epoll_event ev, events[MAX_EVENTS];
int listen_sock, conn_sock, nfds, epollfd; /* Set up listening socket, 'listen_sock' (socket(),
bind(), listen()) */ epollfd = epoll_create();
if (epollfd == -) {
perror("epoll_create");
exit(EXIT_FAILURE);
} ev.events = EPOLLIN;
ev.data.fd = listen_sock;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listen_sock, &ev) == -) {
perror("epoll_ctl: listen_sock");
exit(EXIT_FAILURE);
} for (;;) {
nfds = epoll_wait(epollfd, events, MAX_EVENTS, -);
if (nfds == -) {
perror("epoll_pwait");
exit(EXIT_FAILURE);
} for (n = ; n < nfds; ++n) {
if (events[n].data.fd == listen_sock) {
conn_sock = accept(listen_sock,
(struct sockaddr *) &local, &addrlen);
if (conn_sock == -) {
perror("accept");
exit(EXIT_FAILURE);
}
setnonblocking(conn_sock);
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = conn_sock;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, conn_sock,
&ev) == -) {
perror("epoll_ctl: conn_sock");
exit(EXIT_FAILURE);
}
} else {
do_use_fd(events[n].data.fd);
}
}
}
下面这个是引用mmz_xiaokong
epoll函数
常用模型的缺点
如果不摆出来其他模型的缺点,怎么能对比出 Epoll 的优点呢。
PPC/TPC 模型
这两种模型思想类似,就是让每一个到来的连接一边自己做事去,别再来烦我 。只是 PPC 是为它开了一个进程,而 TPC 开了一个线程。可是别烦我是有代价的,它要时间和空间啊,连接多了之后,那么多的进程 / 线程切换,这开销就上来了;因此这类模型能接受的最大连接数都不会高,一般在几百个左右。
select 模型
1. 最大并发数限制,因为一个进程所打开的 FD (文件描述符)是有限制的,由 FD_SETSIZE 设置,默认值是 1024/2048 ,因此 Select 模型的最大并发数就被相应限制了。自己改改这个 FD_SETSIZE ?想法虽好,可是先看看下面吧 …
2. 效率问题, select 每次调用都会线性扫描全部的 FD 集合,这样效率就会呈现线性下降,把 FD_SETSIZE 改大的后果就是,大家都慢慢来,什么?都超时了??!!
3. 内核 / 用户空间 内存拷贝问题,如何让内核把 FD 消息通知给用户空间呢?在这个问题上 select 采取了内存拷贝方法。
poll 模型
基本上效率和 select 是相同的, select 缺点的 2 和 3 它都没有改掉。
Epoll 的提升
把其他模型逐个批判了一下,再来看看 Epoll 的改进之处吧,其实把 select 的缺点反过来那就是 Epoll 的优点了。
1. Epoll 没有最大并发连接的限制,上限是最大可以打开文件的数目,这个数字一般远大于 2048, 一般来说这个数目和系统内存关系很大 ,具体数目可以 cat /proc/sys/fs/file-max 察看。
2. 效率提升, Epoll 最大的优点就在于它只管你“活跃”的连接 ,而跟连接总数无关,因此在实际的网络环境中, Epoll 的效率就会远远高于 select 和 poll 。
3. 内存拷贝, Epoll 在这点上使用了“共享内存 ”,这个内存拷贝也省略了。
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 errorEpoll 不仅会告诉应用程序有I/0 事件到来,还会告诉应用程序相关的信息,这些信息是应用程序填充的,因此根据这些信息应用程序就能直接定位到事件,而不必遍历整个FD 集合。
int res = epoll_wait(epfd, events, , );
for (int i = ; i < res;i++)
{
handleEvent(events[n]);
}
下面用一个实例来说明
client.cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h> #define MAX_DATA_SIZE 4096
#define SERVER_PORT 12138 int main(int argc,char *argv[])
{
int sockfd;
struct hostent * host;
struct sockaddr_in servAddr;
int pid;
char sendBuf[MAX_DATA_SIZE],recvBuf[MAX_DATA_SIZE];
int sendSize,recvSize; host=gethostbyname(argv[]);
if(host==NULL)
{
perror("get host error");
exit(-);
} sockfd=socket(AF_INET,SOCK_STREAM,);
if(sockfd==-)
{
perror("创建socket失败");
exit(-);
} servAddr.sin_family=AF_INET;
servAddr.sin_port=htons(SERVER_PORT);
servAddr.sin_addr=*((struct in_addr *)host->h_addr);
bzero(&(servAddr.sin_zero),); if(connect(sockfd,(struct sockaddr *)&servAddr,sizeof(struct sockaddr_in))==-)
{
perror("connect 失败");
exit(-);
} if((pid=fork())<)
{
perror("fork error");
}
else if(pid>)
{
while()
{
fgets(sendBuf,MAX_DATA_SIZE,stdin);
sendSize=send(sockfd,sendBuf,MAX_DATA_SIZE,);
if(sendSize<)
perror("send error");
memset(sendBuf,,sizeof(sendBuf));
}
}
else
{
while()
{
recvSize=recv(sockfd,recvBuf,MAX_DATA_SIZE,);
if(recvSize<)
perror("recv error");
printf("接收到的信息:%s",recvBuf);
memset(recvBuf,,sizeof(recvBuf));
}
}
return ;
}
server.cpp
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <string.h>
#include <netinet/in.h>
#include <string.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <unistd.h> #define SERVER_PORT 12138
#define CON_QUEUE 20
#define MAX_DATA_SIZE 4096
#define MAX_EVENTS 500 void AcceptConn(int sockfd,int epollfd);
void Handle(int clientfd); int main(int argc,char *argv[])
{
struct sockaddr_in serverSockaddr;
int sockfd; //创建socket
if((sockfd=socket(AF_INET,SOCK_STREAM,))==-)
{
perror("创建socket失败");
exit(-);
}
serverSockaddr.sin_family=AF_INET;
serverSockaddr.sin_port=htons(SERVER_PORT);
serverSockaddr.sin_addr.s_addr=htonl(INADDR_ANY);
bzero(&(serverSockaddr.sin_zero),); int on=;
setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on)); if(bind(sockfd,(struct sockaddr *)&serverSockaddr,sizeof(struct sockaddr))==-)
{
perror("绑定失败");
exit(-);
} if(listen(sockfd,CON_QUEUE)==-)
{
perror("监听失败");
exit(-);
} //epoll初始化
int epollfd;//epoll描述符
struct epoll_event eventList[MAX_EVENTS];
epollfd=epoll_create(MAX_EVENTS);
struct epoll_event event;
event.events=EPOLLIN|EPOLLET;
event.data.fd=sockfd;//把server socket fd封装进events里面 //epoll_ctl设置属性,注册事件
if(epoll_ctl(epollfd,EPOLL_CTL_ADD,sockfd,&event)<)
{
printf("epoll 加入失败 fd:%d\n",sockfd);
exit(-);
} while()
{
int timeout=;//设置超时;在select中使用的是timeval结构体
//epoll_wait epoll处理
//ret会返回在规定的时间内获取到IO数据的个数,并把获取到的event保存在eventList中,注意在每次执行该函数时eventList都会清空,由epoll_wait函数填写。
//而不清除已经EPOLL_CTL_ADD到epollfd描述符的其他加入的文件描述符。这一点与select不同,select每次都要进行FD_SET,具体可看我的select讲解。
//epoll里面的文件描述符要手动通过EPOLL_CTL_DEL进行删除。
int ret=epoll_wait(epollfd,eventList,MAX_EVENTS,timeout); if(ret<)
{
perror("epoll error\n");
break;
}
else if(ret==)
{
//超时
continue;
} //直接获取了事件数量,给出了活动的流,这里就是跟selec,poll区别的关键 //select要用遍历整个数组才知道是那个文件描述符有事件。而epoll直接就把有事件的文件描述符按顺序保存在eventList中
for(int i=;i<ret;i++)
{
//错误输出
if((eventList[i].events & EPOLLERR) || (eventList[i].events & EPOLLHUP) || !(eventList[i].events & EPOLLIN))
{
printf("epoll error\n");
close(eventList[i].data.fd);
exit(-);
} if(eventList[i].data.fd==sockfd)
{
//这个是判断sockfd的,主要是用于接收客户端的连接accept
AcceptConn(sockfd,epollfd);
}
else //里面可以通过判断eventList[i].events&EPOLLIN 或者 eventList[i].events&EPOLLOUT 来区分当前描述符的连接是对应recv还是send
{
//其他所有与客户端连接的clientfd文件描述符
//获取数据等操作
//如需不接收客户端发来的数据,但是不关闭连接。
//epoll_ctl(epollfd, EPOLL_CTL_DEL,eventList[i].data.fd,eventList[i]);
//Handle对各个客户端发送的数据进行处理
Handle(eventList[i].data.fd);
}
}
} close(epollfd);
close(sockfd);
return ;
} void AcceptConn(int sockfd,int epollfd)
{
struct sockaddr_in sin;
socklen_t len=sizeof(struct sockaddr_in);
bzero(&sin,len); int confd=accept(sockfd,(struct sockaddr *)&sin,&len); if(confd<)
{
perror("connect error\n");
exit(-);
} //把客户端新建立的连接添加到EPOLL的监听中
struct epoll_event event;
event.data.fd=confd;
event.events=EPOLLIN|EPOLLET;
epoll_ctl(epollfd,EPOLL_CTL_ADD,confd,&event);
return ;
} void Handle(int clientfd)
{
int recvLen=;
char recvBuf[MAX_DATA_SIZE];
memset(recvBuf,,sizeof(recvBuf));
recvLen=recv(clientfd,(char *)recvBuf,MAX_DATA_SIZE,);
if(recvLen==)
return ;
else if(recvLen<)
{
perror("recv Error");
exit(-);
}
//各种处理
printf("接收到的数据:%s \n",recvBuf);
return ;
}
epoll参考资料
http://blog.csdn.net/mmz_xiaokong/article/details/8704988
http://blog.csdn.net/mmz_xiaokong/article/details/8704455
http://www.cppblog.com/converse/archive/2008/10/13/63928.html
http://blog.csdn.net/haoahua/article/details/2037704
https://banu.com/blog/2/how-to-use-epoll-a-complete-example-in-c/
epoll为什么这么快: http://www.cppblog.com/converse/archive/2008/10/12/63836.html
本文地址: http://www.cnblogs.com/wunaozai/p/3895860.html
Socket网络编程--epoll小结的更多相关文章
- Socket网络编程--聊天程序(9)
这一节应该是聊天程序的最后一节了,现在回顾我们的聊天程序,看起来还有很多功能没有实现,但是不管怎么说,都还是不错的.这一节我们将讲多服务器问题(高大上的说法就是负载问题了.)至于聊天程序的文件发送(也 ...
- Python Socket 网络编程
Socket 是进程间通信的一种方式,它与其他进程间通信的一个主要不同是:它能实现不同主机间的进程间通信,我们网络上各种各样的服务大多都是基于 Socket 来完成通信的,例如我们每天浏览网页.QQ ...
- windows socket 网络编程
样例代码就在我的博客中,包含六个UDP和TCP发送接受的cpp文件,一个基于MFC的局域网聊天小工具project,和此小工具的全部执行时库.资源和执行程序.代码的压缩包位置是http://www.b ...
- Socket网络编程详解
一,socket的起源 socket一词的起源 在组网领域的首次使用是在1970年2月12日发布的文献IETF RFC33中发现的, 撰写者为Stephen Carr.Steve Crocker和Vi ...
- UNIX网络编程——epoll 的accept , read, write(重要)
在一个非阻塞的socket上调用read/write函数,返回EAGAIN或者EWOULDBLOCK(注:EAGAIN就是EWOULDBLOCK). 从字面上看,意思是: EAGAIN: 再试一次 E ...
- Socket网络编程基本介绍
一,socket的起源 socket一词的起源 在组网领域的首次使用是在1970年2月12日发布的文献IETF RFC33中发现的, 撰写者为Stephen Carr.Steve Crocker和Vi ...
- Socket网络编程--Libev库学习(1)
这一节是安装篇. Socket网络编程不知不觉已经学了快两个月了.现在是时候找个网络库学学了.搜索了很多关于如何学网络编程的博客和问答.大致都是推荐学一个网络库,至于C++网络库有那么几个,各有各的好 ...
- day7 socket网络编程
Python Socket网络编程 Socket是进程间通信的一种方式,它与其他进程间通信的一个主要不同是:它能实现不同主机间的进程间通信,我们网络上各种各样的服务大多都是基于Socket来完成通信的 ...
- Python Socket 网络编程 (客户端的编程)
Socket 是进程间通信的一种方式,它与其他进程间通信的一个主要不同是:它能实现不同主机间的进程间通信,我们网络上各种各样的服务大多都是基于 Socket 来完成通信的,例如我们每天浏览网页.QQ ...
随机推荐
- 【Java】 剑指offer(65) 不用加减乘除做加法
本文参考自<剑指offer>一书,代码采用Java语言. 更多:<剑指Offer>Java实现合集 题目 写一个函数,求两个整数之和,要求在函数体内不得使用+.-.×. ...
- 006 Spark中的wordcount以及TopK的程序编写
1.启动 启动HDFS 启动spark的local模式./spark-shell 2.知识点 textFile: def textFile( path: String, minPartitions: ...
- Storm中关于Topology的设计
一:介绍Storm设计模型 1.Topology Storm对任务的抽象,其实 就是将实时数据分析任务 分解为 不同的阶段 点: 计算组件 Spout Bolt 边: 数据流向 数据从上 ...
- c++中关于用stringstream进行的类型转化
1.将int转化成string类型 #include <iostream> #include <sstream> using namespace std; int main() ...
- Cpu 主频与睿频
主频就是一颗CPU的运行频率.比如一颗CPU是2.3G,无论是单核还是多核,所有的核心都是工作在2.3G. 睿频是Intel的一项加速技术,指当启动一个运行程序后,处理器会自动加速到合适的频率,而原来 ...
- PyQt5初级教程(一)
python 版qt入门级使用说明 我使用的是python3.5安装PyQt5: pip3 install PyQt5 可以用如下代码测试环境是否安装成功,运行成功会弹出一个窗口: from PyQt ...
- PHP如何支持CURL字符串证书传输
背景 最近在对接微信支付的时候,需要在退款处用到证书,由于我们是SAAS平台,要支持多方多渠道支付,如果把所有证书文件保存在应用服务器会受到SLB的影响,会导致某台机器文件不同步而阻碍退款流程,但把文 ...
- v-on指令监听dom事件
一.无参 <div id="J_app"> <button v-on:click="eatWhat">吃啥</button> ...
- RadGridView添加序号列
public class RowNumberColumn : GridViewDataColumn { public override System.Windows.FrameworkElement ...
- U3D面试题四
1.配置Unity3D调试环境 在windows环境下,设置unity3d的编辑器调试环境方法: 点击“Edit‘---”Preferences“,弹出如下窗口 选择MonoDeveop即可. 在编辑 ...