1. 程式人生 > >muduo原始碼分析:Acceptor類

muduo原始碼分析:Acceptor類

Acceptor用於接受(accept)客戶端的連線,通過設定回撥函式通知使用者。它只在muduo網路庫內部的TcpServer使用,由TcpServer控制它的生命期。

實際上,Acceptor只是對 Channel 的封裝,通過Channel關注listenfd的 readable可讀事件 ,並設定好回撥函式就可以了。因此理解了上一節的muduo:Reactor,那麼Acceptor也比較容易理解。


 

Acceptor.h

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_;		//監聽套接字,Socket是個RAII型,析構時自動close檔案描述符
  Channel acceptChannel_;	//通過channel,設定監聽套接字的readable事件以及回撥函式

  //NewConnectionCallback是:typedef NewConnectionCallback boost::function<void(int sockfd, const InetAddress&)>
  NewConnectionCallback newConnectionCallback_;
  bool listenning_;
  int idleFd_;
};

 

Acceptor::Acceptor() 構造

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(
      boost::bind(&Acceptor::handleRead, this));		//設定監聽套接字可讀回撥。
}

 

Acceptor::~Acceptor() 析構

Acceptor::~Acceptor()
{
  acceptChannel_.disableAll(); // 移除註冊的事件
  acceptChannel_.remove();     // Poller會持有Channel的裸指標,所以需要將該Channel從Poller中刪除,避免Channel析構後,Poller持有空懸指標。
  ::close(idleFd_);
}

 

 

Acceptor::listen()

void Acceptor::listen()
{
  loop_->assertInLoopThread();
  listenning_ = true;
  acceptSocket_.listen();					//listen
  acceptChannel_.enableReading();				//註冊監聽套接字channel可讀事件
}

注意此處註冊可讀事件,發生新連線時會呼叫Channel的 handleEvent 從而呼叫readCallback_ (即建構函式設定的handleRead())


 

Acceptor::handleRead()

在有新連線時被呼叫。

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";
 
    if (errno == EMFILE)
    {
      ::close(idleFd_);
      idleFd_ = ::accept(acceptSocket_.fd(), NULL, NULL);
      ::close(idleFd_);
      idleFd_ = ::open("/dev/null", O_RDONLY | O_CLOEXEC);
    }
  }
}

 

使用示例:

void newConnection(int sockfd, struct sockaddr &in_addr, socklen_t in_len)
{
    printf_address(sockfd, &in_addr, in_len);
    ::write(sockfd, "jinger\n", 8);
    ::close(sockfd);
}

int main()
{
    muduo::net::EventLoop loop;
    muduo::net::Acceptor acceptor(&loop, "8090");
    acceptor.setNewConnectionCallback(newConnection);
    acceptor.listen();
    loop.loop();
    return 0;
}

使用者只需要設定好回撥函式然後listen即可。socket->bind->listen->accept 這些步驟在底層都已經封裝好了。