使用Epoll 在 Linux 上开发高性能应用服务器

epoll是Linux提供一种多路复用的技术,类似各个平台都支持的select,只是epoll在内核的实现做了更多地优化,可以支持比select更多的文件描述符,当然也支持 socket这种网络的文件描述符。Linux上的大并发的接入服务器,目前的实现方式肯定都通过epoll实现。


epoll和线程

有很多开发人员用epoll的时候,会开多个线程来进行数据通信,比如一个线程专门accept(我个人早些年在FreeBSD用kqueue的时候,由于对内部机制没有基本了解也这样搞),一个线程收发,或者接收和发送都用各自独立的线程。

通常情况下,accept独立线程是没必要的,accept对于内核而言,就应用程序从内核的未完成的SYN队列读取一点数据而已。具体参见 accept部分:


TCP三次握手过程与对应的Berkeley Socket APIs的介绍

收发独立成两个线程也没有必要,因为大部分的应用服务器,通常情况下,启动一个线程收发数据,最大数据的收发量瓶颈在于网卡,而不是CPU;像网游接入服务器配置一个KM的网卡,很少有游戏会申请1G的带宽,那一台机器得有多少数据输入和输出。所以我们通信线程为epoll服务器就够了。

 


epoll的基本原理

为了让某些朋友能读得更连惯,我还是说一下epoll基本原理。

epoll外部表现和select是一样的。他提供READ, WRITE和ERROR等事件。

大致流程像下面这样:


1. 应用注册感兴趣的事件到内核;

2. 内核在某种条件下,将事件通知应用程序;

3. 应用程序收到事件后,根据事件类型做对应的逻辑处理。

 


原理部分我再说一下,不容易理解的地方,包括水平触发和边缘触发,WRITE事件的事件利用(这个可以结合参考文献1的kqueue的WRITE事件,原理一致的)和修改事件的细节。

水平触发

READ事件,socket recv buff有数据,将一直向应用程序通知,直到buff为空。

WRITE事件,socket send buff从满的状态到可发送数据,将一直通知应用程序,直到buff满。

 


边缘触发

READ事件,socket recv buff有数据了,只通知应用一次,不管应用程序有没有调用read api,接下来都不通知了。

WRITE事件,socket send buff从满的状态到可以发送数据,只通知一次。

上面这个解释不知道大家能否理解,也只能这样说了。有疑问的做一下试验。另外,这些细节的东西,前几年固定下来后,这几年做的项目,是直接用的,也就很少在涉及细节,是凭理解和记忆写下的文字,万一有错请指正^-^。

 


WRITE事件的利用

这个还一下不好描述。大概描述一下,详细看参考文献1。大致这样:

1. 逻辑层写数据到应用层的发送buff,向epoll注册一下WRITE事件;

2. 这时epoll会通知应用程序一个WRITE事件;

3. 在WRITE事件响应函数里,从应用层的发送buff读数据,然后用socket send api发送。

因为我在很多实际项目中,看到大家没有利用epoll的WRITE的事件来发数据,特意地说一下。大部分的项目,是直接轮询应用程序的发送队列,我早期项目也是这么干的。

 


epoll的修改事件

对于这个我的映像比较深刻。epoll的修改事件比较坑爹,不能单独修改某个事件!怎么说呢?比如epoll里已经注册了READ&WRITE事件,你如果想单单重注册一下WRITE事件而且READ事件不变,epoll的epoll_ctl API是做不到的,你必须同时注册READ&WRITE,这个在下面的代码中可以看到。FreeBSD的kqueue在这一点完全满足我们程序员的要求。

 


抽象epoll API

我把herm socket epoll封装部分贴出来,让朋友们参考一下epoll的用法。大部分错误抛异常代码被我去掉了。

class Multiplexor
{
public:
 Multiplexor(int size, int timeout = -1, bool lt = true);
 ~Multiplexor();

 void Run();
 void Register(ISockHandler* eh, MultiplexorMask mask);
 void Remove(ISockHandler* eh);
 void EnableMask(ISockHandler* eh, MultiplexorMask mask);
 void DisableMask(ISockHandler* eh, MultiplexorMask mask);
private:
 inline bool OperateHandler(int op, ISockHandler* eh, MultiplexorMask mask)
 {
  struct epoll_event evt;
  evt.data.ptr = eh;
  evt.events = mask;
  return epoll_ctl(m_epfd, op, eh->GetHandle(), &evt) != -1;
 }
private:
 int m_epfd;
 struct epoll_event* m_evts;
 int m_size;
 int m_timeout;
 __uint32_t m_otherMasks;
};

Multiplexor::Multiplexor(int size, int timeout, bool lt) 
{
 m_epfd = epoll_create(size);
 if (m_epfd == -1)
  throw HERM_SOCKET_EXCEPTION(ST_OTHER);
 
 m_size = size;
 m_evts = new struct epoll_event[size];

 m_timeout = timeout;

 // sys/epoll.h is no EPOLLRDHUP(0X2000), don't add EPOLLRDHUP
 m_otherMasks = EPOLLERR | EPOLLHUP;
 if (!lt)
  m_otherMasks |= EPOLLET;
}

Multiplexor::~Multiplexor()
{
 close(m_epfd);
 delete[] m_evts;
}

void Multiplexor::Run()
{
 int fds = epoll_wait(m_epfd, m_evts, m_size, m_timeout); 
 if (fds == -1)
 {
  if (errno == EINTR)
   return;
 }
 
 for (int i = 0; i < fds; ++i)
 {
  __uint32_t evts = m_evts[i].events;
  ISockHandler* eh = reinterpret_cast<ISockHandler*>(m_evts[i].data.ptr);
  int stateType = ST_SUCCESS;
  if (evts & EPOLLIN)
   stateType = eh->OnReceive();

  if (evts & EPOLLOUT)
   stateType = eh->OnSend();

  if (evts & EPOLLERR || evts & EPOLLHUP)
   stateType = ST_EXCEPT_FAILED;

  if (stateType != ST_SUCCESS)
   eh->OnError(stateType, errno);
 }
}

void Multiplexor::Register(ISockHandler* eh, MultiplexorMask mask)
{
 MultiplexorMask masks = mask | m_otherMasks;
 OperateHandler(EPOLL_CTL_ADD, eh, masks);
}

void Multiplexor::Remove(ISockHandler* eh)
{
 // Delete fd from epoll, don't need masks
 OperateHandler(EPOLL_CTL_DEL, eh, ALL_EVENTS_MASK);
}

void Multiplexor::EnableMask(ISockHandler* eh, MultiplexorMask mask)
{
 MultiplexorMask masks = mask | Herm::READ_MASK | Herm::WRITE_MASK;
 OperateHandler(EPOLL_CTL_MOD, eh, masks | m_otherMasks);
}

void Multiplexor::DisableMask(ISockHandler* eh, MultiplexorMask mask)
{
 MultiplexorMask masks = (Herm::READ_MASK | Herm::WRITE_MASK) & (~mask);
 if (!OperateHandler(EPOLL_CTL_MOD, eh, masks | m_otherMasks))
  throw HERM_SOCKET_EXCEPTION(ST_OTHER);
}

上面类就用到epoll_create(), epoll_ctl()和epoll_wait(),以及几种事件。epoll用起来比select清爽一些。

大致用法类似下面这样:

先定义一个Handler

class StreamHandler : public Herm::ISockHandler
{  
public: 
 virtual Herm::Handle GetHandle() const;
    virtual int OnReceive(int); 
 virtual int OnSend(int);
};

在OnReceive()处理收到数据的动作,在OnSend()。。。。

在通信线程中,大概类似这样的代码,实际看情况。

Multiplexor multiplexor;
StreamHandler sh;
multiplexor.Register(&sh, READ_EVT);
multiplexor.Run(...);


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/464131.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

昨晚两点睡

深圳下雨两天&#xff0c;我们居家办公两天&#xff0c;不过奇怪的事情是&#xff0c;这两天我都到公司上班&#xff0c;昨天早上没有下雨&#xff0c;我想着应该要去公司&#xff0c;结果到了公司才知道原来今天可以居家办公。不过&#xff0c;在公司才有上班的感觉&#xff0…

hashmap详解

一.hashmap的数据结构 HashMap采取数组加链表的存储方式(哈希表)来实现。亦即数组&#xff08;散列桶&#xff09;中的每一个元素都是链表 二.hashmap的构造函数 HashMap()&#xff1a;构造一个具有默认初始容量 (16) 和默认加载因子 (0.75) 的空 HashMap。 HashMap(int initia…

最近忙,三个字

最近忙&#xff0c;三个字 转载于:https://www.cnblogs.com/Liunsh/archive/2007/06/13/782352.html

书籍推荐-记这几年看的书

这几年看了不少书&#xff0c;大部分是自掏腰包&#xff0c;看一本好书是享受&#xff0c;我很喜欢这种感觉。 这些是我这几年看书的一些心得&#xff0c;对于一些新手来说&#xff0c;可能有点帮助。 这几年一直在走技术路线&#xff0c;所以看的大部分都是技术方面的书籍&…

不复位MCU直接调试运行程序,让bug闻风丧胆

大家周末好呀&#xff0c;文章转自bug菌的公众号&#xff0c;文章介绍步复位情况下调试bug&#xff0c;希望对大家有用。1调试窘境经常有朋友在开发中遇到这样的窘境&#xff0c;当单片机程序运行异常以后&#xff0c;由于调试信息做得并不是很全面&#xff0c;导致相应的问题场…

数据库设计中的14个技巧

数据库设计中的14个技巧 1. 原始单据与实体之间的关系   可以是一对一、一对多、多对多的关系。在一般情况下&#xff0c;它们是一对一的关系&#xff1a;即一张原始单据对 应且只对应一个实体。在特殊情况下&#xff0c;它们可能是一对多或多对一的关系&#xff0c;即一张原…

游三圣乡山中湖岛有感

游三圣乡山中湖岛有感——代腾飞 2007年6月10日 于成都池塘水绿萍飘荡柳絮随风四飞扬独坐湖亭把歌唱夕阳西下断愁肠 转载于:https://www.cnblogs.com/daitengfei/archive/2007/06/26/795914.html

这次比opencv快⑥倍!!!

打败opencv ,哦&#xff0c;是快了3倍上回书说道&#xff0c;我用汇编neon实现去畸变算法比opencv快3倍&#xff0c;这都不算啥&#xff0c;这次新增了透视变换算法&#xff0c;二者加起来比opencv快6倍&#xff01;拭目以待吧。啥玩意是透视变换&#xff1f;相信你们都开过高级…

数据和数据类型

一、什么是数据&#xff1a; 数据(date)是事实或观察的结果&#xff0c;是对客观事物的逻辑归纳&#xff0c;是用于表示客观事物的未加工的原始素材。 1&#xff09;数据是信息的表现形式和载体&#xff0c;可以是符号、文字、数字、语音、图像、视频等。数据和信息是不可分离…

TCP/UDP优化设置总结以及MTU的相关介绍

TCP/IP协议涉及到四层&#xff0c;从底层到上层异常为&#xff1a;链路层&#xff0c;网络层&#xff0c;传输层&#xff0c;应用层。    其中以太网&#xff08;Ethernet&#xff09;的数据帧在链路层    IP包在网络层    TCP或UDP包在传输层    TCP或UDP中的数据&…

Redis的七种武器及其适合的应用场景

长生剑、孔雀翎、碧玉刀、多情环、离别钩、霸王枪、拳头是古龙笔下的七种武器&#xff0c;而本文打算将Redis的几种使用方式 Strings、Hashs、Lists、Sets、Sorted Sets、Pub/Sub、Transactions 也比作七种武器&#xff0c;为大家讲解Redis的七种特性&#xff0c;并列举其适合的…

涂鸦的这套宠物SDK设计,真香

我应该在之前的文章里面说过&#xff0c;我之前创业的时候做过宠物方面的产品&#xff0c;而且我们当时用的是乐鑫的芯片。最近知道在涂鸦工作的朋友也在研究这方面&#xff0c;他给我寄了几个小板子&#xff0c;还有涂鸦的IOT SDK&#xff0c;我玩了几天&#xff0c;觉得真的很…

三个周年纪念日

六月二十七日&#xff0c;二十八日&#xff0c;二十九日毕业一年&#xff0c;抵京一年&#xff0c;工作一年。此刻我身处远离北京的偏僻小县……不是被发配——自己的选择。 此一年&#xff0c;改变了许多&#xff0c;坚持了许多……此一年&#xff0c;收获了一些&#xff0c;付…

sql server常用函数积累

1.LEFT(character,integer) 参数1&#xff1a;要截取的字符串&#xff0c;参数2&#xff1a;截取字符个数 返回从字符串左边开始指定个数的字符 2.RIGHT(character,integer) 参数1&#xff1a;要截取的字符串&#xff0c;参数2&#xff1a;截取字符个数 返回从字符串右边开始指…

入主 51cto

12年1月11日&#xff0c;入主 51CTO&#xff0c;记录自己的学习历程与感悟。转载于:https://blog.51cto.com/sugarlin/762038

准备 KVM 实验环境 - 每天5分钟玩转 OpenStack(3)

转载&#xff1a;http://cloudman.blog.51cto.com/10425448/1747415 KVM 是 OpenStack 使用最广泛的 Hypervisor&#xff0c;本节介绍如何搭建 KVM 实验环境 安装 KVM 上一节说了&#xff0c;KVM 是 2 型虚拟化&#xff0c;是运行在操作系统之上的&#xff0c;所以我们先要装一…

电子美图高清系列漫画分享给大家欣赏!

电子漫画搞电子的大家或多或少都会收集了电子漫画和表情到&#xff0c;小编找了一份比较全高清无码的电子美图漫画&#xff0c;供大家欣赏&#xff0c;提供下面三种下载方式&#xff01;1、GitHub&#xff1a;https://github.com/chiphome/Electronic-Comics2、Gitee&#xff1…

如何提高网页中图片显示的用户体验(附源码下载)

文章中加入适量的图片不仅可以更好的说明和补充文章的内容&#xff0c;而且还可以极大的减缓阅读者在阅读较长篇幅文章时的疲劳和不安。所以图文混排较好的文章能给阅读者更好的用户体验和享受。但是令人遗憾的是很多的网页图片的显示并不十分理想&#xff0c;非但没有给阅读者…

plone进行 用户和权限管理

用户和权限管理 .. Contents:: .. sectnum:: :prefix: 6. 对于任何一个系统&#xff0c;我们都会关心它的安全性问题。我们需要控制不同的用户&#xff0c;在网站的不同地方&#xff0c;在不同的阶段&#xff0c;能够拥有不同的权限。Plone具有一个强大的、柔性、精细粒度的…

全开源最小电压表:24位ADC,测量0~2V,五位半

1、项目背景2015年5月份评估完十几种24位ADC后就从第一份工作岗位上离职了&#xff0c;做的24位AD都没有达到实际的效果&#xff0c;一直耽搁困扰了好久。是硬件设计的问题&#xff1f;还是软件开发的问题&#xff1f;还是24位ADC真的不咋地&#xff1f;还是要离职了干活就不负…