muduo源码剖析之Acceptor监听类

简介

Acceptor类用于创建套接字,设置套接字选项,调用socket()->bind()->listen()->accept()函数,接受连接,然后调用TcpServer设置的connect事件的回调。

listen()//在TcpServer::start中调用

封装了一个listen fd相关的操作,用于mainLoop

成员及属性解析

Acceptor - 逻辑上的内部类

接受器封装,实质上就是对Channel的多一层封装

主要接口

listen

监听连接

当新连接进入时,调用Socket::accept创建套接字,触发TcpServer的回调

setNewConnectionCallback

TcpServer通过该接口设置回调,当新连接套接字创建后,创建TcpConnection对象

核心实现:

通过socket::accept接受新连接,获得套接字fd

这个fd作为参数调用TcpServer注册的回调

主要成员

  • loop
  • channel
  • idlefd
    非常巧妙的设计,在服务器压力过大,无法新建文件描述符时,通过这个idlefd拒绝连接
    来自libevent的设计

源码剖析

Acceptor.h

cpp 复制代码
#ifndef MUDUO_NET_ACCEPTOR_H
#define MUDUO_NET_ACCEPTOR_H

#include <functional>

#include "muduo/net/Channel.h"
#include "muduo/net/Socket.h"

namespace muduo
{
namespace net
{
class EventLoop;
class InetAddress;

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

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

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

  void listen();

  bool listening() const { return listening_; }

  // Deprecated, use the correct spelling one above.
  // Leave the wrong spelling here in case one needs to grep it for error messages.
  // bool listenning() const { return listening(); }

 private:
  void handleRead();

  EventLoop* loop_;
  Socket acceptSocket_;
  Channel acceptChannel_;
  NewConnectionCallback newConnectionCallback_;
  bool listening_;
  int idleFd_;
};

}  // namespace net
}  // namespace muduo

#endif  // MUDUO_NET_ACCEPTOR_H

Acceptor.cc

cpp 复制代码
// Copyright 2010, Shuo Chen.  All rights reserved.
// http://code.google.com/p/muduo/
//
// Use of this source code is governed by a BSD-style license
// that can be found in the License file.

// Author: Shuo Chen (chenshuo at chenshuo dot com)

#include "muduo/net/Acceptor.h"

#include "muduo/base/Logging.h"
#include "muduo/net/EventLoop.h"
#include "muduo/net/InetAddress.h"
#include "muduo/net/SocketsOps.h"

#include <errno.h>
#include <fcntl.h>
//#include <sys/types.h>
//#include <sys/stat.h>
#include <unistd.h>

using namespace muduo;
using namespace muduo::net;

Acceptor::Acceptor(EventLoop* loop, const InetAddress& listenAddr, bool reuseport)
  : loop_(loop),
    acceptSocket_(sockets::createNonblockingOrDie(listenAddr.family())),//创建一个非阻塞的socket fd
    acceptChannel_(loop, acceptSocket_.fd()),//创建socket fd的channel
    listening_(false),//是否处于监听状态
    idleFd_(::open("/dev/null", O_RDONLY | O_CLOEXEC))
{
  assert(idleFd_ >= 0);
  acceptSocket_.setReuseAddr(true);			//设置ip地址复用
  acceptSocket_.setReusePort(reuseport);	//设置端口复用
  acceptSocket_.bindAddress(listenAddr);	//bind()函数封装,绑定ip和端口
  acceptChannel_.setReadCallback(
      std::bind(&Acceptor::handleRead, this));//设置accept的回调函数
}

Acceptor::~Acceptor()
{
  acceptChannel_.disableAll();//将mainloop poller监听集合中移除,取消所有事件的监听
  acceptChannel_.remove();//在events_删除channel
  ::close(idleFd_);//关闭文件
}

void Acceptor::listen()//开始监听fd
{
  loop_->assertInLoopThread();//判断是不是和创建时的io线程处于同一个线程
  listening_ = true;//是否监听
  acceptSocket_.listen();//真正的监听函数
  acceptChannel_.enableReading();//设置监听读事件
}

void Acceptor::handleRead()//当有client connnect时,则会调用
{
  loop_->assertInLoopThread();
  InetAddress peerAddr;
  //FIXME loop until no more
  int connfd = acceptSocket_.accept(&peerAddr);//接收client connect,返回值==accept()返回值
  if (connfd >= 0)
  {
    // string hostport = peerAddr.toIpPort();
    // LOG_TRACE << "Accepts of " << hostport;
    if (newConnectionCallback_)//如果设置了connect cb,则调用,否则则关闭这个连接
    {
      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.
    if (errno == EMFILE)
    {
      ::close(idleFd_);
      idleFd_ = ::accept(acceptSocket_.fd(), NULL, NULL);
      ::close(idleFd_);
      idleFd_ = ::open("/dev/null", O_RDONLY | O_CLOEXEC);
    }
  }
}
相关推荐
勤奋的凯尔森同学1 小时前
webmin配置终端显示样式,模仿UbuntuDesktop终端
linux·运维·服务器·ubuntu·webmin
闲猫1 小时前
go orm GORM
开发语言·后端·golang
丁卯4042 小时前
Go语言中使用viper绑定结构体和yaml文件信息时,标签的使用
服务器·后端·golang
chengooooooo2 小时前
苍穹外卖day8 地址上传 用户下单 订单支付
java·服务器·数据库
李白同学3 小时前
【C语言】结构体内存对齐问题
c语言·开发语言
人间打气筒(Ada)3 小时前
MySQL主从架构
服务器·数据库·mysql
楼台的春风4 小时前
【MCU驱动开发概述】
c语言·驱动开发·单片机·嵌入式硬件·mcu·自动驾驶·嵌入式
落笔画忧愁e4 小时前
FastGPT快速将消息发送至飞书
服务器·数据库·飞书
小冷爱学习!4 小时前
华为动态路由-OSPF-完全末梢区域
服务器·网络·华为
技术小齐5 小时前
网络运维学习笔记 016网工初级(HCIA-Datacom与CCNA-EI)PPP点对点协议和PPPoE以太网上的点对点协议(此处只讲华为)
运维·网络·学习