首页 > 其他分享 >epoll服务器

epoll服务器

时间:2023-06-01 18:02:43浏览次数:34  
标签:socket epoll 描述符 fd 内核 服务器 poll


epoll同上篇博客中的select一样,都是用于多路转接,但epoll被公认为Linux2.6下性能最好的多路I/O就绪通知方法。

 

一、epoll相关系统调用

epoll只有三个系统调用函数:

epoll_create:创建epoll模型

epoll_ctl:管理epoll模型

epoll_wait:等待I/O时间就绪

epoll服务器_数据

 

events可以是以下几个宏的集合:

EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);

EPOLLOUT:表示对应的文件描述符可以写;

EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);

EPOLLERR:表示对应的文件描述符发生错误;

EPOLLHUP:表示对应的文件描述符被挂断;

EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。

EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里

 

 

二、epoll工作原理

 

内存映射(mmap)技术,这样便彻底省掉了这些文件描述符在系统调用时复制的开销。

另一个本质的改进在于epoll采用基于事件的就绪通知方式,在select/poll中,进程只有在调用一定的方法后,内核才对所有监视的文件描述符进行扫描,而epoll事先通过epoll_ctl()来注册一个文件描述符,一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait()时便得到通知。

 

关于内存映射技术mmap:

 

mmap()系统调用使得进程之间通过映射同一个普通文件实现共享内存。普通文件被映射到进程地址空间后,进程可以向访问普通内存一样对文件进行访问,不必再调用read(),write()等操作。

注:实际上,mmap()系统调用并不是完全为了用于共享内存而设计的。它本身提供了不同于一般对普通文件的访问方式,进程可以像读写内存一样对普通文件的操作。而Posix或系统V的共享内存IPC则纯粹用于共享目的,当然mmap()实现共享内存也是其主要应用之一。

void* mmap ( void * addr , size_t len , int prot , int flags , int fd , off_t offset );
参数fd为即将映射到进程空间的文件描述字,一般由open()返回,同时,fd可以指定为-1,此时须指定flags参数中的MAP_ANON,表明进行的是匿名映射(不涉及具体的文件名,避免了文件的创建及打开,很显然只能用于具有亲缘关系的进程间通信)。len是映射到调用进程地址空间的字节数,它 从被映射文件开头offset个字节开始算起。prot 参数指定共享内存的访问权限。可取如下几个值的或:PROT_READ(可读) , PROT_WRITE (可写), PROT_EXEC (可执行), PROT_NONE(不可访问)。flags由以下几个常值指定:MAP_SHARED , MAP_PRIVATE , MAP_FIXED,其中,MAP_SHARED , MAP_PRIVATE必选其一,而MAP_FIXED则不推荐使用。offset参数一般设为0,表示从文件头开始映射。参数addr指定文件应被映射 到进程空间的起始地址,一般被指定一个空指针,此时选择起始地址的任务留给内核来完成。函数的返回值为最后文件映射到进程空间的地址,进程可直接操作起始地址为该值的有效地址。

 

 

三、epoll模型

 

(1)调用epoll_create创建epoll模型的时候,实际上是在内核区创建了一棵空的红黑树和一个空的队列;

(2)调用epoll_ctl的时候,实际上是在往红黑树中添加结点,结点描述的是文件描述符及其上的对应事件;

(3)当某文件描述符上的某事件就绪的时候操作系统会创造一个结点放在队列中(此结点表示此文件描述符上的此事件就绪),这个队列通过内存映射机制让用户看到。

 

 

四、epoll的优点

 

1.支持一个进程打开无限数目的fd

       select 最不能忍受的是一个进程所打开的FD是有一定限制的,由FD_SETSIZE设置,默认值是1024。对于那些需要支持的上万连接数目的IM服务器来说显然太少了。这时候你一是可以选择修改这个宏然后重新编译内核,不过资料也同时指出这样会带来网络效率的下降,二是可以选择多进程的解决方案(传统的 Apache方案),不过虽然linux上面创建进程的代价比较小,但仍旧是不可忽视的,加上进程间数据同步远比不上线程间同步的高效,所以也不是一种完美的方案。不过epoll没有这个限制,它所支持的fd上限是最大可以打来文件的数目,这个数字一般远大于1024,举个例子,在1GB内存的机器上大约是10万左右,具体数目可以cat /proc/sys/fs/file-max察看,一般来说这个数目和系统内存关系很大。

 

2.I/O效率不随fd数目增加而线性下降

      传统的select/poll另一个致命弱点就是当你拥有一个很大的socket集合,不过由于网络延时,任一时间只有部分的socket是"活跃"的,但是select/poll每次调用都会线性扫描全部的集合,导致效率呈现线性下降。但是epoll不存在这个问题,它只会对“活跃”的socket进行操作---这是因为在内核实现中epoll是根据每个fd上面的callback函数实现的。那么,只有"活跃"的socket才会主动的去调用 callback函数,其他idle状态socket则不会,因为这时候推动力在os内核。在一些 benchmark中,如果所有的socket基本上都是活跃的---比如一个高速LAN环境,epoll并不比select/poll有什么效率,相反,如果过多使用epoll_ctl,效率相比还有稍微的下降。但是一旦使用idle connections模拟WAN环境,epoll的效率就远在select/poll之上了。

 

3.使用mmap加速内核与用户控件的消息传递

 

    这点实际上涉及到epoll的具体实现了。无论是select,poll还是epoll都需要内核把fd消息通知给用户空间都需要内核把fd信息传递给用户空间,如何避免不必要的内存拷贝就很重要,在这点上,epoll是通过内核于用户空间mmap同一块内存实现的。

 

 

 五、epoll服务器

 

epoll_server.c:

 

1. #include <stdio.h>
2. #include <stdlib.h>
3. #include <unistd.h>
4. #include <sys/types.h>
5. #include <sys/socket.h>
6. #include <arpa/inet.h>
7. #include <netinet/in.h>
8. #include <sys/epoll.h>
9. #include <string.h>
10.   
11. static void usage(const char* proc)  
12. {  
13. "Usage: [local_ip] [local_port] %s\n", proc);  
14. }  
15.   
16. int startup(const char* _ip, int _port)  
17. {  
18.     int sock = socket(AF_INET, SOCK_STREAM, 0);  
19.     if(sock < 0)  
20.     {  
21. "socket");  
22.         exit(2);  
23.     }  
24.     struct sockaddr_in local;  
25.     local.sin_family = AF_INET;  
26.     local.sin_port = htons(_port);  
27.     local.sin_addr.s_addr = inet_addr(_ip);  
28.     if(bind(sock, (struct sockaddr*)&local, sizeof(local)) < 0)  
29.     {  
30. "bind");  
31.         exit(3);  
32.     }  
33.     if(listen(sock, 10) < 0)  
34.     {  
35. "listen");  
36.         exit(4);  
37.     }  
38.     return sock;  
39. }  
40.   
41. int main(int argc, char* argv[])  
42. {  
43.     if(argc != 3)  
44.     {  
45.         usage(argv[0]);  
46.         return 1;  
47.     }  
48.   
49.     int listen_sock = startup(argv[1], atoi(argv[2]));  
50.       
51.     int epfd = epoll_create(256);  
52.     struct epoll_event ev;  
53.     ev.events = EPOLLIN;  
54.     ev.data.fd = listen_sock;  
55.     epoll_ctl(epfd, EPOLL_CTL_ADD, listen_sock, &ev);  
56.     int nums = -1;  
57.     struct epoll_event revs[64];  
58.     int timeout = 1000;  
59.   
60.     while(1)  
61.     {  
62.         switch(nums = epoll_wait(epfd, revs, 64, timeout))  
63.         {  
64.             case -1:  
65. "epoll_wait");  
66.                 break;  
67.             case 0:  
68. "timeout...\n");  
69.             default:  
70.                 {  
71.                     int i = 0;  
72.                     for(; i<nums; ++i)  
73.                     {  
74.                         int sock = revs[i].data.fd;  
75.                         if(sock==listen_sock && (revs[i].events&EPOLLIN))  
76.                         {  
77. //listen_sock ready!!!
78.                             struct sockaddr_in client;  
79.                             socklen_t len = sizeof(client);  
80.                             int new_sock = accept(listen_sock,   
81.                                     (struct sockaddr*)&client, &len);  
82.                             if(new_sock < 0)  
83.                             {  
84. "accept");  
85.                                 continue;  
86.                             }  
87.                             ev.events = EPOLLIN;  
88.                             ev.data.fd = new_sock;  
89.                             epoll_ctl(epfd, EPOLL_CTL_ADD, new_sock, &ev);  
90.                         }  
91.                         else if(sock != listen_sock)  
92.                         {  
93.                             if(revs[i].events & EPOLLIN)  
94.                             {  
95. //read event ready!!!
96.                                 char buf[1024];  
97.                                 ssize_t s = read(sock, buf, sizeof(buf)-1);  
98.                                 if(s >0)  
99.                                 {  
100.                                     buf[s] = 0;  
101. "client# %s\n", buf);  
102.                                     ev.events = EPOLLOUT;  
103.                                     epoll_ctl(epfd, EPOLL_CTL_MOD, sock, &ev);  
104.                                 }  
105.                                 else if(s == 0)  
106.                                 {  
107. "client is quit...\n");  
108.                                     close(sock);  
109.                                     epoll_ctl(epfd, EPOLL_CTL_DEL, sock, NULL);  
110.                                 }  
111.                                 else  
112.                                 {  
113. "read");  
114.                                     continue;  
115.                                 }  
116.                             }  
117.                             else if(revs[i].events & EPOLLOUT)  
118.                             {  
119.                                 const char* msg = "HTTP/1.0 OK 200\r\n\r\n   
120.                                     <html><h1>hello epoll!</h1></html>";  
121.                                 write(sock, msg, strlen(msg));  
122.                                 close(sock);  
123.                                 epoll_ctl(epfd, EPOLL_CTL_DEL, sock, NULL);  
124.                             }  
125.                         }  
126.                     }  
127.                 }  
128.         }  
129.     }  
130. }

运行结果:

 

epoll服务器_文件描述符_02

 

浏览器运行结果:

epoll服务器_数据_03

 

EPOLLLT——水平触发
EPOLLET——边缘触发
 

epoll有EPOLLLT和EPOLLET两种触发模式,LT是默认的模式,ET是“高速”模式。LT模式下,只要这个fd还有数据可读,每次 epoll_wait都会返回它的事件,提醒用户程序去操作,而在ET(边缘触发)模式中,它只会提示一次,直到下次再有数据流入之前都不会再提示了,无 论fd中是否还有数据可读。所以在ET模式下,read一个fd的时候一定要把它的buffer读光,也就是说一直读到read的返回值小于请求值,或者 遇到EAGAIN错误。

 

首先介绍一下LT工作模式:

LT(level triggered)是缺省的工作方式,并且同时支持block和no-block socket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表.

优点:当进行socket通信的时候,保证了数据的完整输出,进行IO操作的时候,如果还有数据,就会一直的通知你。

缺点:由于只要还有数据,内核就会不停的从内核空间转到用户空间,所有占用了大量内核资源,试想一下当有大量数据到来的时候,每次读取一个字节,这样就会不停的进行切换。内核资源的浪费严重。效率来讲也是很低的。

ET:

ET(edge-triggered)是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知。请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once).

优点:每次内核只会通知一次,大大减少了内核资源的浪费,提高效率。

缺点:不能保证数据的完整。不能及时的取出所有的数据。

应用场景: 处理大数据。使用non-block模式的socket。

 

epoll_create

从slab缓存中创建一个eventpoll对象,并且创建一个匿名的fd跟fd对应的file对象,

而eventpoll对象保存在struct file结构的private指针中,并且返回,

该fd对应的file operations只是实现了poll跟release操作

 

创建eventpoll对象的初始化操作

获取当前用户信息,是不是root,最大监听fd数目等并且保存到eventpoll对象中

初始化等待队列,初始化就绪链表,初始化红黑树的头结点

 

epoll_ctl操作

将epoll_event结构拷贝到内核空间中

并且判断加入的fd是否支持poll结构(epoll,poll,selectI/O多路复用必须支持poll操作).

并且从epfd->file->privatedata获取event_poll对象,根据op区分是添加删除还是修改,

首先在eventpoll结构中的红黑树查找是否已经存在了相对应的fd,没找到就支持插入操作,否则报重复的错误.

相对应的修改,删除比较简单就不啰嗦了

 

插入操作时,会创建一个与fd对应的epitem结构,并且初始化相关成员,比如保存监听的fd跟file结构之类的

重要的是指定了调用poll_wait时的回调函数用于数据就绪时唤醒进程,(其内部,初始化设备的等待队列,将该进程注册到等待队列)完成这一步, 我们的epitem就跟这个socket关联起来了, 当它有状态变化时,

会通过ep_poll_callback()来通知.

最后调用加入的fd的file operation->poll函数(最后会调用poll_wait操作)用于完成注册操作.

最后将epitem结构添加到红黑树中

 

epoll_wait操作

计算睡眠时间(如果有),判断eventpoll对象的链表是否为空,不为空那就干活不睡明.并且初始化一个等待队列,把自己挂上去,设置自己的进程状态

为可睡眠状态.判断是否有信号到来(有的话直接被中断醒来,),如果啥事都没有那就调用schedule_timeout进行睡眠,如果超时或者被唤醒,首先从自己初始化的等待队列删除

,然后开始拷贝资源给用户空间了

拷贝资源则是先把就绪事件链表转移到中间链表,然后挨个遍历拷贝到用户空间,

并且挨个判断其是否为水平触发,是的话再次插入到就绪链表

标签:socket,epoll,描述符,fd,内核,服务器,poll
From: https://blog.51cto.com/u_16147764/6397247

相关文章

  • Java中将网上的png,jpg等存储在图片服务器中并且转成pdf,并且返回相应的url地址。
    通常在开发的时候,我们会遇到图片上传的功能,特别是有很多是提供url地址的方式。所以需要提供一个将url的图片等存储起来,然后提供一个我们自己的地址给用户使用。第一步:提供pdfbox的jar包。准备相应的maven    <dependency><groupId>org.apache.pdfbox</groupId......
  • Linux - 配置服务器之间SSH免密登录
     如果集群中服务器之间没有配置SSH免密,那么SSH访问其他服务器时[root@node01bin]#sshnode02Theauthenticityofhost'node02(192.168.56.122)'can'tbeestablished.ECDSAkeyfingerprintisSHA256:iuntlxKiV34RaCDGi7UsV/Ng2oVwWgob9yX3wL+3zzo.ECDSAkeyfingerp......
  • 【终极计算平台】上海道宁为您提供​Wolfram技术,支持跨桌面、云、服务器和移动设备的
     Wolfram帮助世界加快研究、教育、技术发展和革新的步伐无论您所在任何领域无论您需要任何应用Wolfram技术都是您的终极计算平台Mathematica具有涵盖所有技术计算领域的将近6,000个内置函数——所有这些都经过精心制作使其完美地整合在Mathematica系统中 ......
  • 传奇服务器架设教程,传奇GM权限命令设置教程
    作为一个传奇GM,除了需要会架设传奇,还需要了解一些日常GM的操作技能,刚好这两天有朋友提到设置gm权限,今天就给大家分享传奇GM权限命令设置方法1、如何设置GM名单?首先咱们在版本文件夹找到Mir200文件夹,找到M2网关(M2server),打开点击查看,列表信息,可以看见“管理员列表”,将你的游戏角色名......
  • SaltStack介绍——SaltStack是一种新的基础设施管理方法开发软件,简单易部署,可伸缩的
    SaltStack介绍和架构解析简介SaltStack是一种新的基础设施管理方法开发软件,简单易部署,可伸缩的足以管理成千上万的服务器,和足够快的速度控制,与他们交流,以毫秒为单位。SaltStack提供了一个动态基础设施通信总线用于编排,远程执行、配置管理等等。SaltStack项目于2011年启动,年......
  • 玩转服务器之数据传输篇:如何快速搭建FTP文件共享服务器
    FTP文件共享服务器介绍FTP服务(FileTransferProtocol,FTP)是最早应用于主机之间数据传输的基本服务之一,是目前使用最广泛的文件传送协议。FTP文件共享服务器在日常办公中可以实现多人之间文件的传递和共享,极大提高协同办公的效率。Vsftpd是一款在Linux发行版中最受推崇的FTP服务......
  • 在linux服务器上使用命令行下载百度网盘中的文件
    转载自https://blog.csdn.net/qq_37428140/article/details/124219739 1、安装bypy工具pipinstallbypy2、认证自己的网盘账号bypyinfo按照提示,在控制台和浏览器中完成认证3、授权成功后,我们可以在网盘中的“我的应用数据”目录下看到如下文件夹: 将需要下载的......
  • 什么样的服务器适合做APP?45.125.46.x
    现如今,智能手机已经越来越普及,随着智能手机的普及,同时催生了各类APP,很多企业除了建立官方网站,也会创建自家的APP,增加传播渠道,也是竞争途径之一。那么好的APP就非常重要了,在开发APP之前,我们还需要选择一款合适的服务器作为支撑,应该怎么选APP服务器的配置呢?选择APP服务器要从哪些配置......
  • JavaWeb——Tomcat服务器的安装与使用
    今天阿Q带大家了解服务器的概念以及tomcat服务器的安装和使用方法,废话不多说直接上干货。Web开发中的常见概念(1)B/S系统和C/S系统Brower/Server:浏览器、服务器系统-----网站Client/Server:客户端、服务器系统-----QQ、大型游戏(2)web应用服务器供向外部发布web资源的服务器软件......
  • 2、一个TOMCAT服务器搭建两个网站,并在主网站下搭建子业务
    在一个服务器上搭建多个网站如何实现三种方案:IP来区分、端口号来区分、host来区分如nginx中IP来区分:server{listen1.1.1.1:80;}server{listen2.2.2.2:80;}端口号来区分:server{listen1.1.1.1:80;}server{listen1.1.1.1:81;}host(主机头)来区分:ser......