《Linux多线程服务端编程》一书的8.4节介绍了Acceptor类的实现,下面是对这个类的分析。
首先从client端入手看看怎么来使用这个类,client的程序如下:
#include "Acceptor.h"
#include "EventLoop.h"
#include "InetAddress.h"
#include "SocketsOps.h"
#include <stdio.h>
void newConnection(int sockfd, const muduo::InetAddress& peerAddr)
{
printf("newConnection(): accepted a new connection from %s\n",
peerAddr.toHostPort().c_str());
::write(sockfd, "How are you?\n", 13);
muduo::sockets::close(sockfd);
}
int main()
{
printf("main(): pid = %d\n", getpid());
muduo::InetAddress listenAddr(9981);
muduo::EventLoop loop;
muduo::Acceptor acceptor(&loop, listenAddr);
acceptor.setNewConnectionCallback(newConnection);
acceptor.listen();
loop.loop();
}
muduo::InetAddress listenAddr(9981)是定义一个类用于描述要监听的端口。这个类比较简单,其私有成员变量就是 struct sockaddr_in addr_。可知这个类就是对linux C里面的sockaddr_in做了进一步的封装,以体现C++程序的封装性和提高代码的可读性。
接下来看看
muduo::Acceptor acceptor(&loop, listenAddr);
这会调用Acceptor类的构造函数,如下:
Acceptor::Acceptor(EventLoop* loop, const InetAddress& listenAddr)
: loop_(loop),
acceptSocket_(sockets::createNonblockingOrDie()),
acceptChannel_(loop, acceptSocket_.fd()),
listenning_(false)
{
acceptSocket_.setReuseAddr(true);
acceptSocket_.bindAddress(listenAddr);
acceptChannel_.setReadCallback(
boost::bind(&Acceptor::handleRead, this));
}
如上所示,在构造函数里面,会完成socket的create(sockets::createNonblockingOrDie()),socket的bind(acceptSocket_.bindAddress(listenAddr);)等等准备工作,这根UNV中TCP server需要调用的socket(),bind()等函数是一致的。同时,构造函数里面还会调用初始化好acceptChannel_,并且为acceptChannel设置相应的回调函数。在完成了这些工作之后,这个socket对应的fd,channel就都已经准备好了。
再接下来是
acceptor.setNewConnectionCallback(newConnection);
acceptor.listen();
第一个setNewConnectionCallback比较简单,就是在accept完一个TCP连接之后,会对应到这个回调函数里面去完成一些业务逻辑。注意这里的回调函数不同于上一步中的
boost::bind(&Acceptor::handleRead
上一步中的这个回调函数实际上是会在loop循环中调用,所完成的还是底层的accept工作。而setNewConnectionCallback这个回调函数是在accept成功之后才会去调用。
至于acceptor.listen(),其程序如下:
void Acceptor::listen()
{
loop_->assertInLoopThread();
listenning_ = true;
acceptSocket_.listen();
acceptChannel_.enableReading();
}
上述程序也很好理解,就是完成了listen()系统调用,并且通过enableReading将accept_channel加到poll数组中
接下来就是调用
loop.loop();
进入loop循环,当有连接到来的时候, 程序会从poll中跳出,然后对应到accept_channel的回调函数中来使用accept系统调用接收请求。在接收完请求之后会调用客户端定义的newConnection回调函数。
从以上程序可知,在使用像muduo这样的网络库时候,比较核心的其实是回调函数的编写。底层的相关操作都是由网络库封装好的。