Acceptor类是属于内部类。既然这个类是管理监听套接字的,那么这个监听套接字的生命周期就是由Acceptor类来管理。

Acceptor类主要负责监听连接请求,调用listen()接口时,通过Channel::enableReading()把socket的描述符加到poller(I/O复用器)中。当有新连接到达时,先调用系统函数accept(),再回调函数newConnectionCallback_()让TcpServer去创建连接。

#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 <unistd.h>

using namespace muduo;
using namespace muduo::net;

/******************************************************************** 
Modify : Eric Lee
Date : 2018-01-17
Description : 
Accpetor(连接处理器)的作用如下:
1、创建监听(接收者)套接字
2、设置套接字选项
3、创建监听套接字的事件处理器,主要用于处理监听套接字的读事件(新连接)
4、绑定地址
5、开始监听
6、等待事件到来
*********************************************************************/
Acceptor::Acceptor(EventLoop* loop, const InetAddress& listenAddr, bool reuseport)
  : loop_(loop),
    acceptSocket_(sockets::createNonblockingOrDie(listenAddr.family())),
    acceptChannel_(loop, acceptSocket_.fd()),
    listenning_(false),
    idleFd_(::open("/dev/null", O_RDONLY | O_CLOEXEC))
{
  assert(idleFd_ >= 0);
  // 地址复用
  acceptSocket_.setReuseAddr(true);
  // 端口复用
  acceptSocket_.setReusePort(reuseport);
  // 绑定地址
  acceptSocket_.bindAddress(listenAddr);
  // 设置读事件的回调函数
  acceptChannel_.setReadCallback(std::bind(&Acceptor::handleRead, this));
}

Acceptor::~Acceptor()
{
  acceptChannel_.disableAll();
  acceptChannel_.remove();
  ::close(idleFd_);
}

void Acceptor::listen()
{
  loop_->assertInLoopThread();
  listenning_ = true;
  // 底层调用了listen()函数
  acceptSocket_.listen();
  // 让监听字的channel关注可读事件
  acceptChannel_.enableReading();
}

/******************************************************************** 
Modify : Eric Lee
Date : 2018-01-17
Description : 
写服务器应用程序必须要考虑到服务器资源不足的情况,
其中常见的一个是打开的文件数量(文件描述符数量)不能超过系统限制,
当接受的连接太多时就会到达系统的限制,即表示打开的套接字文件描述符
太多,从而导致accpet失败,返回EMFILE错误,但此时连接已经在系统内核
建立好了,所以占用了系统的资源,我们不能让接受不了的连接继续占用系统
资源,如果不处理这种错误就会有越来越多的内核连接建立,系统资源被占用
也会越来越多,直到系统崩溃。一个常见的处理方式就是,先打开一个文件,
预留一个文件描述符,出现EMFILE错误的时候,把打开的文件关闭,此时就会
空出一个可用的文件描述符,再次调用accept就会成功,接受到客户连接之后,
我们马上把它关闭,这样这个连接在系统中占用的资源就会被释放。关闭之后
又会有一个文件描述符空闲,我们再次打开一个文件,占用文件描述符,
等待下一次的EMFILE错误。
*********************************************************************/
void Acceptor::handleRead() // 处理读事件
{
  loop_->assertInLoopThread();
  InetAddress peerAddr;
  // 接受一个连接
  int connfd = acceptSocket_.accept(&peerAddr);
  if (connfd >= 0)
  {
    // 调用新连接到来的回调函数
    if (newConnectionCallback_)
    {
      // TcpServer注册的,创建新的conn,并且加入TcpServer的ConnectionMap中。
      newConnectionCallback_(connfd, peerAddr);
    }
    else
    {
      sockets::close(connfd);
    }
  }
  else
  {
    LOG_SYSERR << "in Acceptor::handleRead";
    // 超过了最大连接数,文件描述符用完了
    if (errno == EMFILE)
    {
      // 关闭预留的文件描述符
      ::close(idleFd_);
      // 然后立即打开,即可得到一个可用的文件描述符,马上接受新链接
      idleFd_ = ::accept(acceptSocket_.fd(), NULL, NULL);
      // 然后立即关闭这个连接,表示服务器不再提供服务,因为系统资源已经不足
      // 服务器使用这个方法来拒绝客户的连接
      ::close(idleFd_);
      // 再次获得一个空洞文件描述符保存到idleFd_中
      idleFd_ = ::open("/dev/null", O_RDONLY | O_CLOEXEC);
    }
  }
}