muduo库的Acceptor类剖析

tcp 端口复用与惊群效应(REUSEADDR、REUSEPORT) 我在之前的一篇文章中,介绍了我在之前的项目中遇到的端口复用,windows 的udp里端口复用导致了一个bug,具体的链接参考如下: 关于Socket中端口复用_zhc的博客-CSDN博客_socket端口复用 而我现在是想总结一下,linux下的情况,想从头再梳理下,REUSEADDR 和 REUSEPORT的作用。 1.SO_REUSEADDR 是为了解决time_wait问题而产生的 SO_REUSEADDR 它的作用是当服务器端主动断开链接的时候,处在timewait阶段大约60秒钟,服 阅读详情


一:主要功能

>>>Acceptor用于accept(2)接受TCP连接。

>>>Acceptor的数据成员包括Socket、Channel。

>>>Acceptor的socket是listening socket(即server socket)。

>>>Channel用于观察此socket的readable事件,并Acceptor::handleRead(),后者调用accept(2)来接受连接,并回调用户callback。

不过,Acceptor类在上层应用程序中我们不直接使用,而是把它封装作为TcpServer的成员。


二:流程示例

Acceptor的主要成员有:

private:
  void handleRead();   //可读回调函数

  EventLoop* loop_;    //loop指针
  Socket acceptSocket_;   //监听套接字
  Channel acceptChannel_; //和监听套接字绑定的通道
  NewConnectionCallback newConnectionCallback_;  //一旦有新连接发生执行的回调函数
  bool listenning_;   //acceptChannel所处的EventLoop是否处于监听状态
  int idleFd_;     //用来解决文件描述符过多引起电平触发不断触发的问题,后文会有解释

然后看它的构造函数:

Acceptor::Acceptor(EventLoop* loop, const InetAddress& listenAddr, bool reuseport)
  : loop_(loop),
    acceptSocket_(sockets::createNonblockingOrDie(listenAddr.family())), //创建监听套接字
    acceptChannel_(loop, acceptSocket_.fd()),  //绑定Channel和socketfd
    listenning_(false),  
    idleFd_(::open("/dev/null", O_RDONLY | O_CLOEXEC))  //预先准备一个空闲文件描述符
{
  assert(idleFd_ >= 0);  //>=0
  acceptSocket_.setReuseAddr(true);
  acceptSocket_.setReusePort(reuseport);
  acceptSocket_.bindAddress(listenAddr);
  acceptChannel_.setReadCallback(                        
      boost::bind(&Acceptor::handleRead, this));   //设置Channel的fd的读回调函数
}

Acceptor通过一个EventLoop指针,一个InetAddress对象,以及一个是否复用端口的标志构造,构造函数中会创建监听套接字并绑定响应的Channel,但是并不会启动关注Channel的可读事件,这是listen()中应该做的。并且还创建了一个指向空的文件描述符,这待会会说。


创建完套接字我们可以设置新连接来到时的回调函数:

void setNewConnectionCallback(const NewConnectionCallback& cb)
  { newConnectionCallback_ = cb; }


那它是什么时候执行呢,是触发监听套接字可读事件的时候:

void Acceptor::handleRead()    //读回调函数
{
  loop_->assertInLoopThread();
  InetAddress peerAddr;
  //FIXME loop until no more
  int connfd = acceptSocket_.accept(&peerAddr);   //获得已连接套接字
  if (connfd >= 0)
  {
    // string hostport = peerAddr.toIpPort();
    // LOG_TRACE << "Accepts of " << hostport;
    if (newConnectionCallback_)   //如果设置了新连接回调函数
    {
      newConnectionCallback_(connfd, peerAddr);   //那么就执行它
    }
    else
    {
      sockets::close(connfd);  //否则就关闭,sockets是全局函数
    }
  }
  else   //如果<0失败了?
  {
    LOG_SYSERR << "in Acceptor::handleRead";
    // Read the section named "The special problem of
    // accept()ing when you can't" in libev's doc.
    // By Marc Lehmann, author of livev.
    if (errno == EMFILE)   //太多的文件描述符
    {
      ::close(idleFd_);   //先关闭空闲文件描述符,让它能够接收。否则由于采用电平触发,不接收会一直触发。
      idleFd_ = ::accept(acceptSocket_.fd(), NULL, NULL);  //那就腾出一个文件描述符,用来accept
      ::close(idleFd_);  //accept之后再关闭
      idleFd_ = ::open("/dev/null", O_RDONLY | O_CLOEXEC);  //然后再打开成默认方式
    }
  }
}
在监听套接字可读事件触发时,我们会调用accept接受连接。如果此时注册过回调函数,就执行它。如果没有就直接关闭!

另一方面,如果已用文件描述符过多,accept会返回-1,我们构造函数中注册的idleFd_就派上用场了。当前文件描述符过多,无法接收新的连接。但是由于我们采用LT模式,如果无法接收,可读事件会一直触发。那么在这个地方的处理机制就是,关掉之前创建的空心啊idleFd_,然后去accept让这个事件不会一直触发,然后再关掉该文件描述符,重新将它设置为空文件描述符。

这种机制可以让网络库在处理连接过多,文件描述符不够用时,不至于因为LT模式一直触发而产生坏的影响。


设置完回调函数,就可以listen()了,这也是Acceptor类的最终任务:

//Acceptor是在listen中开始关注可读事件
void Acceptor::listen()
{
  loop_->assertInLoopThread();
  listenning_ = true;
  acceptSocket_.listen();
  acceptChannel_.enableReading();   //关注可读事件
}

enableReading()不必多说,会在Poller中开始关注Channel对应fd的可读时间,所以当有连接到来时,可读事件发生,我们就可以愉快的调用上面的handleRead()函数,进而调用newConnectionCallback()函数了。而newConnectionCallback()函数是客端负责自定义的,所以客端就可以在一个新连接到来时,随心所欲的做自己想做的事情了。


下面是所有源码分析,后面会给出示例 :

头文件:

/// Acceptor of incoming TCP connections.
///
class Acceptor : boost::noncopyable
{
 public:
  typedef boost::function<void (int sockfd,
                                const InetAddress&)> NewConnectionCallback;

  Acceptor(EventLoop* loop, const InetAddress& listenAddr, bool reuseport);
  ~Acceptor();

  void setNewConnectionCallback(const NewConnectionCallback& cb)
  { newConnectionCallback_ = cb; }

  bool listenning() const { return listenning_; }
  void listen();

 private:
  void handleRead();   //可读回调函数

  EventLoop* loop_;
  Socket acceptSocket_;
  Channel acceptChannel_;    
  NewConnectionCallback newConnectionCallback_;
  bool listenning_;   //acceptChannel所处的EventLoop是否处于监听状态
  int idleFd_;
};

实现文件:

Acceptor::Acceptor(EventLoop* loop, const InetAddress& listenAddr, bool reuseport)
  : loop_(loop),
    acceptSocket_(sockets::createNonblockingOrDie(listenAddr.family())), //创建监听套接字
    acceptChannel_(loop, acceptSocket_.fd()),  //绑定Channel和socketfd
    listenning_(false),  
    idleFd_(::open("/dev/null", O_RDONLY | O_CLOEXEC))  //预先准备一个空闲文件描述符
{
  assert(idleFd_ >= 0);  //>=0
  acceptSocket_.setReuseAddr(true);
  acceptSocket_.setReusePort(reuseport);
  acceptSocket_.bindAddress(listenAddr);
  acceptChannel_.setReadCallback(                        
      boost::bind(&Acceptor::handleRead, this));   //设置Channel的fd的读回调函数
}

Acceptor::~Acceptor()
{
  acceptChannel_.disableAll();   //需要把所有事件都disable掉,才能调用remove函数
  acceptChannel_.remove();
  ::close(idleFd_);    //关闭文件描述符
}

//Acceptor是在listen中开始关注可读事件
void Acceptor::listen()
{
  loop_->assertInLoopThread();
  listenning_ = true;
  acceptSocket_.listen();
  acceptChannel_.enableReading();   //关注可读事件
}

void Acceptor::handleRead()    //读回调函数
{
  loop_->assertInLoopThread();
  InetAddress peerAddr;
  //FIXME loop until no more
  int connfd = acceptSocket_.accept(&peerAddr);   //获得已连接套接字
  if (connfd >= 0)
  {
    // string hostport = peerAddr.toIpPort();
    // LOG_TRACE << "Accepts of " << hostport;
    if (newConnectionCallback_)   //如果设置了新临街回调函数
    {
      newConnectionCallback_(connfd, peerAddr);   //那么就执行它
    }
    else
    {
      sockets::close(connfd);  //否则就关闭
    }
  }
  else   //如果<0失败了?
  {
    LOG_SYSERR << "in Acceptor::handleRead";
    // Read the section named "The special problem of
    // accept()ing when you can't" in libev's doc.
    // By Marc Lehmann, author of livev.
    if (errno == EMFILE)   //太多的文件描述符
    {
      ::close(idleFd_);   //先关闭空闲文件描述符,让它能够接收。否则由于采用电平触发,不接收会一直触发。
      idleFd_ = ::accept(acceptSocket_.fd(), NULL, NULL);  //那就腾出一个文件描述符,用来accept
      ::close(idleFd_);  //accept之后再关闭
      idleFd_ = ::open("/dev/null", O_RDONLY | O_CLOEXEC);  //然后再打开成默认方式
    }
  }
}


一个典型的例子:

#include <muduo/net/Acceptor.h>
#include <muduo/net/EventLoop.h>
#include <muduo/net/InetAddress.h>
#include <muduo/net/SocketsOps.h>

#include <stdio.h>

using namespace muduo;
using namespace muduo::net;

void newConnection(int sockfd, const InetAddress& peerAddr)
{
        printf("newConnection() : accepted a new connection from %s\n",
                                        peerAddr.toIpPort().c_str());
        ::write(sockfd, "How are you?\n", 13);
        sockets::close(sockfd);
}

int main()
{
        printf("main(): pid = %d\n", getpid());

        InetAddress listenAddr(8888);
        EventLoop loop;

        Acceptor acceptor(&loop, listenAddr, true);
        acceptor.setNewConnectionCallback(newConnection);
        acceptor.listen();

        loop.loop();
}

输出:








muduo网络(1):muduo整体框架 〇、背景 0.1、本文背景 在学习陈硕《Linux多线程服务端编程:使用muduo C++网络muduo源代码的过程中,感觉代码架构较复杂: 一是和boost相关的内容比较多,对现代C++编程理念不了解的人,可能感觉莫名其妙。(关于“现代C++”点击此处) 二是muduo的源代码中,虽然不考虑可移植性,但还是划分了很多小的(Channel、Socket、TcpConnection、Acce... 阅读详情

相关推荐

muduo---C++网络编程

陈硕 muduo 参考资料 muduo源码剖析 muduo基于Reactor模式实现 Reactor模式也是目前大多数Linux端高性能网络编程框架和网络应用所选择的主要架构,例如内存数据Redis和Java的Netty等。 muduo 架构 整个架构依照reactor模式,如如下图: 所谓的reactor模型,是有一个循环的过程,监听对应事件是否触发,触发时调用对应的callback函数进行处理; 这里的事件在muduo中包括socket可读写事件、定时器事件。在其他网络中如libevent也包

weixin_48433164的博客 3934

socket(7):详解 setsockopt (SO_REUSEPORT )

通俗易懂说socket(5)详解 setsockopt()

哈尼的博客 3084

STM32裸机开发基础:寄存器直接操作详解

寄存器是微控制器内部的特殊存储单元,直接与硬件电路连接,用于控制和监视硬件功能。与普通内存不同,寄存器的每一位(bit)通常都有特定的硬件含义。寄存器就是机器的控制面板上的各种开关和指示灯每个开关控制一个具体功能(如打开LED、启动通信)每个指示灯显示一个具体状态(如数据接收完成、错误发生)// GPIO引脚定义1。

2401_88760782的博客 2135

[muduo网络]——muduoAcceptor剖析muduo网络核心部分、设计思想)

接着之前我们剖析muduo三大核心组件Channel,Poller/EpollPoller,EventLoop,我们接下来继续看muduo中的其他,这一篇,我们先来介绍AcceptorAcceptor用于创建套接字,设置相关处理方法,调用listen函数,接受(accept)客户端连接, 接受新用户连接并分发连接给SubReactor(SubEventLoop)。注意: 它只在muduo网络内部的TcpServer使用,由TcpServer控制它的生命期。 监听套接字的fd由哪个Ev

m0_73537205的博客 1616

muduoAcceptor解析

首先Acceptor class,用于accept(2)新TCP连接,并通过回调通知使用者,它是内部class,供TcpServer使用,生命期由后者控制。 首先我们看Acceptor的数据成员: EventLoop* loop_; Socket acceptSocket_; Channel acceptChannel_; NewConnectionCallback newC...

大洋深处 613

SO_REUSEADDR和SO_REUSEPORT在不同OS上的表现

TCP套接字的SO_REUSEADDR和SO_REUSEPORT选项,以及他们在不同操作系统中的表现。

赶路人儿 1894

Muduo源码剖析(六)——SocketAcceptor

主要作用就是封装socketfd,从muduo源代码简化提取后的代码如下。中进行事件监视,这个工作在。,主要工作是 负责管理。这个对象在muduo的。调整当前进程的fd上限。进行集群或分布式部署了。

qq_42120843的博客 229

Muduo源码剖析笔记】 网络之TcpServer

Muduo源码阅读笔记

LeeEdgar的博客 156

[muduo网络]——muduoSocketAcceptor,Connector

Socket实际上就是封装套接字socket fd,请参考游双《Linux高性能服务器编程》第五章用于接受TCP连接, 它是TcpServer的成员,生命期由后者控制。Acceptor用于创建套接字socket,接受新客户端连接并分发连接给SubReactor(SubEventLoop)用于发起TCP连接, 它是TcpClient的成员,生命期由后者控制。它负责主动发起连接,不负责创建socket,只负责连接的建立,和Acceptor恰好相反。Connector。

weixin_45807759的博客 1523

长文梳理Muduo核心代码及优秀编程细节剖析

长文梳理Muduo核心代码(Multi-Reactor架构代码)及优秀编程细节思想剖析

T_Solotov的博客 1万+

muduo网络剖析——接受新连接Acceptor

基于c++的muduo网络开源项目深度剖析!超级加分的简历项目,期待您的讨论~

weixin_62021811的博客 1273

国庆弯道超车(手写Muduo剖析核心代码及编程思想)

Muduo是陈硕老师个人编写的TCP网络编程。博主出于学习的目的使用了C++11将陈硕老师的muduo重构了一下,当然只是重构了其核心的思想。muduo当中还有很多牛逼的实现个人现在能力有限只能看懂其主要的实现,后面能力提升了再写博客进行剖析。同时这篇博客的目的主要是为了剖析muduo的核心代码梳理其代码的执行逻辑。这个了博主能力有限,可能写着写着就写出来了一些不知所云的东西,还请各位担待。如果有写错的地方请在下方留言指正。

qq_56999918的博客 2531

muduo源码剖析Acceptor监听

Acceptor用于创建套接字,设置套接字选项,调用socket()->bind()->listen()->accept()函数,接受连接,然后调用TcpServer设置的connect事件的回调。listen()//在TcpServer::start中调用封装了一个listen fd相关的操作,用于mainLoop接受器封装,实质上就是对Channel的多一层封装监听连接 当新连接进入时,调用Socket::accept创建套接字,触发TcpServer的回调TcpServer通过该接口设置回调,当新连

新法的博客 2406

手写muduo网络(七):深入剖析 Acceptor

在网络编程中,服务器端程序需要能够监听客户端的连接请求并进行处理。Acceptor在这个过程中扮演着至关重要的角色,它负责创建监听套接字、绑定地址、开始监听以及处理新的连接请求。在本文中,我们将详细剖析手写 muduo 网络中的Acceptor,探讨其实现原理和工作流程。

2301_76564925的博客 1159

muduo的EventLoop剖析

一:采用的线程通信机制 首先来看一下一个进程(线程)如何通知另一个个等待中的(线程),有三种方法: 1.pipe,使用fd[0] 为读端,fd[1]为写端,半双工。等待线程关注fd[0]的可读事件。 2.socketpair,也有一对文件描述符,可用于双向通信,全双工。 3.eventfd。eventfd是一个比pipe更搞笑的线程间事件通知机制,一方面它比pipe少用一个pipe des

FreeeLinux's blog 3469

解析muduo源码之 Acceptor.h & Acceptor.cc

本文详细解析了Muduo网络中的Acceptor实现。Acceptor作为TCP连接接收器,主要负责监听端口、接受新连接,运行在主事件循环(mainReactor)中。其核心功能包括:创建非阻塞监听socket,绑定地址和端口,设置socket复用选项;通过Channel管理监听socket事件,当有新连接时触发handleRead回调;处理文件描述符耗尽(EMFILE)等边界情况,采用预置idleFd的经典解决方案。Acceptor将新连接的sockfd和对端地址通过回调交给TcpServer处理,实

m0_62807361的博客 379

Muduo源码剖析笔记】 网络Acceptor、Connector

Muduo源码阅读笔记

LeeEdgar的博客 249

以基于Reactor模式的高并发EchoServer为例剖析muduo框架

前言 在讲解基于muduo的高并发echo服务器之前,我们先来回顾一下我们一般编写基于Reactor模式的高并发服务器的基本流程。 muduo的EchoServer其实也是基于上述流程,只不过进行了一定程度地封装,本质上仍是Reactor模式。muduo的example中的echo服务器代码展示的是只有一个reactor的服务器,muduo其实支持one loop per t...

一步一步往上爬 947

[muduo网络]——EchoServer之消息读取(剖析muduo网络核心部分、设计思想)

muduo 是由陈硕大佬个人开发的 TCP 网络,本专栏使用c++11重构了Muduo中核心的Multi-Reactor架构,剖析大佬核心的代码设计思想,希望能够理清自己的思路,也希望能够帮助到大家。

m0_73537205的博客 1386

2ED2410-EM:12v / 24v智能模拟高侧MOSFET栅极驱动器

12v / 24v智能模拟高侧MOSFET栅极驱动器。2ED2410-EM具有输入CPP和CPN的电压比较器。该比较器可用于基于任何模拟电压触发SAFESTATE模式,甚至是外部电压2ED2410-EM。CPP为比较器的正输入,CPN为负输入。VcPREF可以通过微控制器进行调节,也可以通过板载电源芯片(如英飞凌SBC)或简单的分压器在限制VcP(REF)内进行固定,以保持稳定性。使用此比较器允许2ED2410-EM进入SAFESTATE并独立于微控制器关闭MOSFET。

让学习成为一种习惯 1946

matlab 仿真6自由度机械臂,模型使用.rar

编写六自由度机械臂控制程序可以遵循以下步骤:定义机械臂的运动学模型,包括DH参数、正运动学、逆运动学等。设计机械臂的控制器,可以采用位置控制或力控制等方式,根据具体需求进行选择。编写matlab程序,将机械臂的控制器与运动学模型相结合,实现机械臂的控制。在程序中设置输入输出接口,实现与外部设备的数据交互。进行仿真测试,检验程序的正确性和稳定性,可使用matlab自带的仿真工具箱或第三方仿真软件等。最后,将程序上传至机械臂控制器中,实现真实物理机械臂的控制。

上一篇: muduo库的Socket封装
下一篇: 计算机的构成概述 --操作系统设计精髓与设计
FreeeLinux
博客等级 码龄10年 421粉丝 356原创
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值