首先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就是这样。