设想一下如下场景:有100万个客户端同时与一个服务器进程保持着TCP连接。而每一时刻,通常只有几百上千个TCP连接是活跃的(事实上大部分场景都是这种情况)。如何实现这样的高并发?

在select/poll时代,服务器进程每次都把这100万个连接告诉操作系统(从用户态复制句柄数据结构到内核态),让操作系统内核去查询这些套接字上是否有事件发生,轮询完后,再将句柄数据复制到用户态,让服务器应用程序轮询处理已发生的网络事件,这一过程资源消耗较大,因此,select/poll一般只能处理几千的并发连接。

epoll的设计和实现与select完全不同。epoll通过在Linux内核中申请一个简易的文件系统(文件系统一般用什么数据结构实现?B+树)。把原先的select/poll调用分成了3个部分:

1)调用epoll_create()建立一个epoll对象(在epoll文件系统中为这个句柄对象分配资源)

2)调用epoll_ctl向epoll对象中添加这100万个连接的套接字

3)调用epoll_wait收集发生的事件的连接

如此一来,要实现上面说是的场景,只需要在进程启动时建立一个epoll对象,然后在需要的时候向这个epoll对象中添加或者删除连接。同时,epoll_wait的效率也非常高,因为调用epoll_wait时,并没有一股脑的向操作系统复制这100万个连接的句柄数据,内核也不需要去遍历全部的连接。

下面来看看Linux内核具体的epoll机制实现思路。

当某一进程调用epoll_create方法时,Linux内核会创建一个eventpoll结构体,这个结构体中有两个成员与epoll的使用方式密切相关。eventpoll结构体如下所示:

struct eventpoll{
....
/*红黑树的根节点,这颗树中存储着所有添加到epoll中的需要监控的事件*/
struct rb_root rbr;
/*双链表中则存放着将要通过epoll_wait返回给用户的满足条件的事件*/
struct list_head rdlist;
....
};

  

每一个epoll对象都有一个独立的eventpoll结构体,用于存放通过epoll_ctl方法向epoll对象中添加进来的事件。这些事件都会挂载在红黑树中,如此,重复添加的事件就可以通过红黑树而高效的识别出来(红黑树的插入时间效率是lgn,其中n为树的高度)。

而所有添加到epoll中的事件都会与设备(网卡)驱动程序建立回调关系,也就是说,当相应的事件发生时会调用这个回调方法。这个回调方法在内核中叫ep_poll_callback,它会将发生的事件添加到rdlist双链表中。

在epoll中,对于每一个事件,都会建立一个epitem结构体,如下所示:

struct epitem{
struct rb_node rbn;//红黑树节点
struct list_head rdllink;//双向链表节点
struct epoll_filefd ffd; //事件句柄信息
struct eventpoll *ep; //指向其所属的eventpoll对象
struct epoll_event event; //期待发生的事件类型
}

  当调用epoll_wait检查是否有事件发生时,只需要检查eventpoll对象中的rdlist双链表中是否有epitem元素即可。如果rdlist不为空,则把发生的事件复制到用户态,同时将事件数量返回给用户。

从上面的讲解可知:通过红黑树和双链表数据结构,并结合回调机制,造就了epoll的高效。

OK,讲解完了Epoll的机理,我们便能很容易掌握epoll的用法了。一句话描述就是:三步曲。

第一步:epoll_create()系统调用。此调用返回一个句柄,之后所有的使用都依靠这个句柄来标识。

第二步:epoll_ctl()系统调用。通过此调用向epoll对象中添加、删除、修改感兴趣的事件,返回0标识成功,返回-1表示失败。

第三部:epoll_wait()系统调用。通过此调用收集收集在epoll监控中已经发生的事件。

最后,附上一个epoll编程实例。

服务器端

/*************************************************************************
File Name: tcp_s.c
Author: AlexCthon
Mail: AlexCthon@163.com
Created Time: Tue 15 May 2018 10:25:10 PM CST
************************************************************************/ //边沿触发
#include"fun.h"
void setnoblock(int fd)
{
int status;
status=fcntl(fd,F_GETFL);
status = status|O_NONBLOCK;
fcntl(fd,F_SETFL,status);
}
int main(int argc,char**argv)
{
if(argc!=3)
{
printf("./server IPPROT\n");
return -1;
} int sfd;
sfd = socket(AF_INET,SOCK_STREAM,0);
if(sfd==-1)
{
perror("socket"); return -1;
} struct sockaddr_in ser;
bzero(&ser,sizeof(ser));
ser.sin_family = AF_INET;
ser.sin_port = htons(atoi(argv[2]));//将端口号转换为网络字节序
ser.sin_addr.s_addr = inet_addr(argv[1]);//将点分十进制转换为网络字节序 int ret;
ret = bind(sfd,(struct sockaddr*)&ser,sizeof(struct sockaddr));
if(-1==ret)
{
perror("bind");
close(sfd);
return -1;
} if(-1==listen(sfd,10))
{
perror("listen");
close(sfd);
return -1;
}
struct sockaddr_in client;
bzero(&client,sizeof(client)); int newfd;
int len; char buf[10]={0}; int epfd=epoll_create(1);
struct epoll_event event,evs[3];
event.events=EPOLLIN;
event.data.fd=0;
ret = epoll_ctl(epfd,EPOLL_CTL_ADD,0,&event);
if(-1==ret)
{
perror("epoll_ctl");
return -1;
}
event.events=EPOLLIN;
event.data.fd=sfd;
epoll_ctl(epfd,EPOLL_CTL_ADD,sfd,&event);
int ret1;
int i=0;
printf("before select\n");
while(1)
{
ret1 = epoll_wait(epfd,evs,3,-1); for(i=0;i<ret1;i++)
{
if(evs[i].data.fd==sfd)//sfd可读,代表有客户端连接
{
len = sizeof(struct sockaddr);
newfd = accept(sfd,(struct sockaddr*)&client , &len);
if(newfd==-1)
{
perror("accept");
return -1;
}
setnoblock(newfd);
event.events = EPOLLIN;
event.data.fd = newfd;
epoll_ctl(epfd,EPOLL_CTL_ADD,newfd,&event);
// printf("%d\n",ret);
printf("client ip = %s,port = %d\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port));
}
if(evs[i].data.fd==newfd)
{
bzero(buf,sizeof(buf));
ret = recv(newfd,buf,sizeof(buf)-1,0);
if(ret==0)
{
printf("bye!\n");
event.events = EPOLLIN;
event.data.fd = newfd;
epoll_ctl(epfd,EPOLL_CTL_DEL,newfd,&event);
close(newfd);
} printf("%s\n",buf);
// printf("%d\n",ret);
} if(evs[i].data.fd==0)
{
bzero(buf,sizeof(buf));
ret=read(STDIN_FILENO,buf,sizeof(buf)-1);
if(ret==0)
{
printf("bye!\n");
event.events = EPOLLIN|EPOLLET;
event.data.fd = newfd; epoll_ctl(epfd,EPOLL_CTL_DEL,newfd,&event);
close(newfd); }
send(newfd,buf,strlen(buf)-1,0);//不把\n传过去
}
}
} close(sfd); printf("祝您生活愉快");
return 0;
}

  

IO多路复用模型之epoll实现机制的更多相关文章

  1. 什么是IO多路复用?Nginx的处理机制

    先来说一下什么是IO复用? IO复用解决的就是并发行的问题,比如多个用户并发访问一个WEB网站,对于服务端后台而言就会产生多个请求,处理多个请求对于中间件就会产生多个IO流对于系统的读写.那么对于IO ...

  2. Linux网络通信编程(套接字模型TCP\UDP与IO多路复用模型select\poll\epoll)

    Linux下测试代码: http://www.linuxhowtos.org/C_C++/socket.htm TCP模型 //TCPClient.c #include<string.h> ...

  3. IO多路复用模型之select()函数详解

    IO复用 我们首先来看看服务器编程的模型,客户端发来的请求服务端会产生一个进程来对其进行服务,每当来一个客户请求就产生一个进程来服务,然而进程不可能无限制的产生,因此为了解决大量客户端访问的问题,引入 ...

  4. IO多路复用的几种实现机制的分析

    http://blog.csdn.net/zhang_shuai_2011/article/details/7675797 select,poll,epoll都是IO多路复用的机制.所谓I/O多路复用 ...

  5. IO多路复用select/poll/epoll详解以及在Python中的应用

    IO multiplexing(IO多路复用) IO多路复用,有些地方称之为event driven IO(事件驱动IO). 它的好处在于单个进程可以处理多个网络IO请求.select/epoll这两 ...

  6. Python异步非阻塞IO多路复用Select/Poll/Epoll使用,线程,进程,协程

    1.使用select模拟socketserver伪并发处理客户端请求,代码如下: import socket import select sk = socket.socket() sk.bind((' ...

  7. 转一贴,今天实在写累了,也看累了--【Python异步非阻塞IO多路复用Select/Poll/Epoll使用】

    下面这篇,原理理解了, 再结合 这一周来的心得体会,整个框架就差不多了... http://www.haiyun.me/archives/1056.html 有许多封装好的异步非阻塞IO多路复用框架, ...

  8. 阻塞IO,非阻塞IO,IO多路复用模型

    #服务端 import socket sk = socket.socket() sk.bind(('127.0.0.1',8080)) sk.listen() while True: conn, ad ...

  9. Linux IO多路复用 select/poll/epoll

    Select -- synchronius I/O multiplexing select, FS_SET,FD_CLR,FD_ISSET,FD_ZERO #include <sys/time. ...

随机推荐

  1. HDU 2236 无题II(二分图匹配+二分)

    HDU 2236 无题II 题目链接 思路:行列仅仅能一个,想到二分图,然后二分区间长度,枚举下限.就能求出哪些边是能用的,然后建图跑二分图,假设最大匹配等于n就是符合的 代码: #include & ...

  2. sh_Spring整合Hibernate

    分别介绍了Sping和Hibernate,以下是将它们整合到一块去了.   一.Hibernate内容 1.创建PO类. package cn.tgb.domain; //User实体 public ...

  3. pwm驱动原理和代码实现

    学这个pwm真是非常曲则,首先是看s3c2440的datasheet,全英文的,并且还有硬件的时序图(非常多是硬件的工作原理,和软件控制不相关). 看了非常久加上网上看了资料才把这个pwm弄通. 当然 ...

  4. Cocos2d-X中的粒子

    Cocos2d-x引擎提供了强大的type=cocos2d-x&url=/doc/cocos-docs-master/manual/framework/native/v3/particle-s ...

  5. 读《疯狂Java讲义》笔记总结三

    1.初始化块 实际上初始化块是一个假象,使用javac命令编译Java类后,该Java类中的初始化块会消失--初始化块中代码会被 "还原" 到每一个构造器中,且位于构造器全部代码的 ...

  6. 笔记03 wpf 在MVVM模式下怎样在Viewmodel里面获得view的控件对象

     转自http://blog.csdn.net/qing2005/article/details/6601199http://blog.csdn.net/qing2005/article/detail ...

  7. Android 设计模式之单例模式

    设计模式是前人在开发过程中总结的一些经验,我们在开发过程中依据实际的情况,套用合适的设计模式,能够使程序结构更加简单.利于程序的扩展和维护.但也不是没有使用设计模式的程序就不好.如简单的程序就不用了, ...

  8. hdu5318 The Goddess Of The Moon (矩阵高速幂优化dp)

    题目:pid=5318">http://acm.hdu.edu.cn/showproblem.php?pid=5318 题意:给定n个数字串和整数m,规定若数字串s1的后缀和数字串s2 ...

  9. C/C++,java开源数学计算库

    有限元分析.数值计算.三维建模.信号处理.性能分析.仿真分析...这些或多或少与我们常用的软件息息相关,假如有一天你只需要这些大型软件系统的某一个很有限的功能,你是不是也要因此再用一用那动辄几个g的软 ...

  10. ANDROID STUDIO 2.2.3 DOWNLOAD FROM DL.GOOGLE.COM

    立即开始使用 Android Studio Android Studio 包含用于构建 Android 应用所需的所有工具. 下载 ANDROID STUDIO2.2.3 FOR WINDOWS (1 ...