muduo之Acceptor解析

首先Acceptor class,用于accept(2)新TCP连接,并通过回调通知使用者,它是内部class,供TcpServer使用,生命期由后者控制。

首先我们看Acceptor类的数据成员:

	EventLoop* loop_;
  	Socket acceptSocket_;
  	Channel acceptChannel_;
  	NewConnectionCallback newConnectionCallback_;
  	bool listenning_;
  	int idleFd_; // idle为空闲的意思

顾名思义,Acceptor是用来接收连接的,一般Acceptor是在mainReactor的,属于一个单独的Eventloop

acceptSocket   用于进行监听的socket
acceptChannel_   进行监听的channel
newConnectionCallback_  是建立连接时调用的回调函数
listenning_ 是否在进行监听,一种状态表示
idleFd_  是一个空闲的描述符,它是在描述符耗尽的时候,用于接收连接的

然后我们看看它的成员函数

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

  	bool listenning() const { return listenning_; }
  	void listen();

private:
  	void handleRead();

首先我们看Acceptor的构造函数:

Acceptor::Acceptor(EventLoop* loop, const InetAddress& listenAddr, bool reuseport)
  : loop_(loop),
    acceptSocket_(socketds::createNonblockingOrDie(listenAdr.family())),
    acceptChannel_(loop, acceptSocket_.fd()),
    listenning_(false),
    idleFd_(::open("/dev/null", O_RDONLY | O_CLOEXEC)) ///dev/null是一个特殊的设备文件,
    //这个文件接收到的任何数据都会被丢弃
{
  assert(idleFd_ >= 0);
  acceptSocket_.setReuseAddr(true);
  acceptSocket_.setReusePort(reuseport);
  acceptSocket_.bindAddress(listenAddr); 
  acceptChannel_.setReadCallback(std::bind(&Acceptor::handleRead, this));
}

就是一些常规操作,设置好监听socket,设置好监听channel

这里需要注意:

 acceptSocket_.setReuseAddr(true);  //  设置套接字描述符为空重复使用
 acceptSocket_.setReusePort(reuseport); // 设置端口复用
 acceptSocket_.bindAddress(listenAddr); // 将listenfd和socket绑定在一起,也就是调用bind
 acceptChannel_.setReadCallback(std::bind(&Acceptor::handleRead, this)); // 设置监听管道的回调函数,也就是处理可读事件
 

初始化好以后就可以进行监听了

void Acceptor::listen()
{
	  loop_->assertInLoopThread();
	  listenning_ = true;
	  acceptSocket_.listen();
	  acceptChannel_.enableReading(); 
}

将状态设置为监听,然后调用监听socket的listen函数,将监听channel的读事件纳入poller的管理

当有客户端发起连接时,监听channel触发读事件,那么调用Acceptor::handleRead

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); // connfd为accept成功获得的文件描述符,peerAddr为客户端的sockaddr_in
	    }
	    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);
	    }
	  }
}

这里会出现几种情况,首先调用socket的accept系统函数,顺便将对端的struct sockaddr_in填充,然后返回得到新连接的文件描述符。
1> 如果返回值大于等于0,代表分配成功,那么调用回调函数,直接将已连接描述符和对端struct sockaddr_in peerAddr传进去。
2> 如果没有分配成功,那么这时候说明文件描述符耗尽了。muduo库里总是预留了一个文件描述符,也就是/dev/null,如果文件描述符耗尽,那么我们关闭这个文件描述符,那么就会有一个空闲的文件描述符空出来,这样我们去接受新连接,然后接收到以后,立马关掉这个连接,然后重新占用这个文件描述符。也算是一个小的trick吧

基本 Acceptor就是这样。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值