muduo-net 源码分析(五)——Acceptor

先序

对服务器来说,一开始肯定会分配一个监听套接字用来接收客户端的连接;这一部分实际上就是由 Acceptor 实现的;muduo 库对 socket 进行了一系列的封装,这部分内容不是很难,但是很杂有兴趣可以自行查看;Endian.h 封装了字节序转换函数、SocketOps 封装了 socket 的系统调用函数、SocketRAII 方法封装了 socket 文件描述符、InetAddress 封装了网络地址

Acceptor 源码分析

重要的变量

EventLoop* loop_;           //所属的 EvenLoop
Socket acceptSocket_;       //listen socket
Channel acceptChannel_;     //绑定 listen socket 的通道

typedef std::function<void (int sockfd, const InetAddress&)> NewConnectionCallback;
//新连接到来时的回调函数
NewConnectionCallback newConnectionCallback_;    
bool listening_;
int idleFd_;				//一个文件描述符

重要的函数

Acceptor(EventLoop* loop, const InetAddress& listenAddr, bool reuseport);
~Acceptor();
void setNewConnectionCallback(const NewConnectionCallback& cb)
  { newConnectionCallback_ = cb; }
void listen();
void handleRead();

具体实现

Acceptor::Acceptor(EventLoop* loop, const InetAddress& listenAddr, bool reuseport)
  : loop_(loop),
    //根据传递进来的地址类型创建文件描述符
    acceptSocket_(sockets::createNonblockingOrDie(listenAddr.family())),
    //监听套接字
    acceptChannel_(loop, acceptSocket_.fd()),
    listening_(false),
    //?为什么这里要打开一个 idleFd_?
    idleFd_(::open("/dev/null", O_RDONLY | O_CLOEXEC))
{
  assert(idleFd_ >= 0);
  //设置地址、端口重复利用
  acceptSocket_.setReuseAddr(true);
  acceptSocket_.setReusePort(reuseport);
  //绑定地址
  acceptSocket_.bindAddress(listenAddr);
  //为 channel 设置一个 handlerRead 回调函数,即可读事件到来后触发的函数
  acceptChannel_.setReadCallback(
      std::bind(&Acceptor::handleRead, this));
}
  • Acceptor 构造函数主要的功能就是创建 listen socket,绑定地址和设置读回调函数;
  • 为什么要使用 idleFd_
  • 构造函数并没有打开通道的读事件、有没有调用 listen socket 的监听函数
//由于 listen socket 是用 RAII 封装好的,所以不需要我们手动关闭
Acceptor::~Acceptor()
{
  //关闭通道
  acceptChannel_.disableAll();
  acceptChannel_.remove();
 
  //关闭 idleFd_ 
  ::close(idleFd_);
}
//只能在 IO 线程中调用
void Acceptor::listen()
{
  loop_->assertInLoopThread();
  listening_ = true;
  //监听套接字
  acceptSocket_.listen();
  //关注套接字的可读事件
  acceptChannel_.enableReading();
}
  • 调用完 listen() 后,listen socket 的读事件才被监听,但客户端连接服务器时就会触发 listen socket 的可读事件,调用 handleRead();【这个函数是本节的重点
//只能在 IO 线程中调用
void Acceptor::handleRead()
{
  loop_->assertInLoopThread();
  
  //准备一个对等方地址,保存客户端地址
  InetAddress peerAddr;
  
  //接收客户端连接
  int connfd = acceptSocket_.accept(&peerAddr);
  if (connfd >= 0)
  {
    //对于新的客户端连接需要进行一些处理,如果没有处理,则关闭套接字
    if (newConnectionCallback_)
    {
      newConnectionCallback_(connfd, peerAddr);
    }
    else
    {
      sockets::close(connfd);
    }
  }
  //失败的处理
  else
  {
    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 libev.
    //如果客户端套接字太多了,我们需要对其进行处理
    //如果是电平触发,我们不去处理的话,缓冲区一直为高电平就会 busyloop
    //如果是边沿触发,我们不去处理的话,当文件描述符可写入时,由于缓冲区一直为高电平,不会触发事件
    if (errno == EMFILE)
    {
      //我们之前已经获得了一个文件描述符,将其关闭
      ::close(idleFd_);
      //此时就可以接收,相当于腾出来一个位置了,缓冲区的内容被读取了
      idleFd_ = ::accept(acceptSocket_.fd(), NULL, NULL);
      //再将其关闭,发送一个关闭命令
      ::close(idleFd_);
      //再次取得这个 fd
      idleFd_ = ::open("/dev/null", O_RDONLY | O_CLOEXEC);
    }
  }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值