muduo库的Acceptor类剖析


一:主要功能

>>>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();
}

输出:








  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值