muduo网络库源码详解(2) 以EchoServer为例子,从TcpServer的初始化说起,分析ConnectionCallback回调函数是如何一步步设置到Channel里的
主要参考了陈硕的书《Linux多线程服务端编程》,结合自己的逻辑摘了一些重要的的句子和函数、数据成员进行分析。
如下一个简单的EchoServer的启动流程,以它为例子讲解muduo的流程
构造一个EventLoop —— 作为mainloop
构造一个InetAddress —— sockaddr_in的封装
EchoServer 的初始化 —— (TcpServer的初始化、构造函数里 手动绑定回调函数)
server.start(); —— 调用内部TcpServer的start(),开启 监听 listen
开启mainloop(上一篇文章已经讲过) —— pool()、fillActiveChannels、handleEvent、userCallback
注册监听事件
监听事件(可读事件)到来 将如下图所示执行 分发给subLoop执行 accept等后续操作
代码如下
int main()
{
muduo::net::EventLoop loop; // 构造一个loop
muduo::net::InetAddress listenAddr(8888); // sockaddr_in的封装,服务地址未填写默认本地
EchoServer server(&loop, listenAddr); // TcpServer的初始化、构造函数里 手动绑定回调函数
server.start();
loop.loop();
}
本文从TcpServer的构造看起,看看有什么东西
黄色的线表名了 EchoServer注册的创建Connection回调函数是怎么一步步进入到channel的,
loop的函数未分析是因为在上篇文章已经分析过了
TcpServer 有一个Acceptor、一个 EventLoopThreadPool、 一个 ConnectionMap connections_
端口复用选项详解
TcpServer::TcpServer(EventLoop *loop, // 绑定的loop,作为mainLoop
const InetAddress &listenAddr, // 端口信息
const std::string &nameArg, // 名字
Option option) // 端口是否复用选项 默认不开启
: loop_(CheckLoopNotNull(loop))
, ipPort_(listenAddr.toIpPort())
, name_(nameArg)
, acceptor_(new Acceptor(loop, listenAddr, option == kReusePort)) // 构造acceptor,主要干的事情是socket 的
, threadPool_(new EventLoopThreadPool(loop, name_)) // 构造线程池
, connectionCallback_(defaultConnectionCallback) // 设置默认回调
, messageCallback_(defaultMessageCallback) // 设置默认回调
, nextConnId_(1)
, started_(0)
{
// 当有先用户连接时,会执行TcpServer::newConnection回调
acceptor_->setNewConnectionCallback(std::bind(&TcpServer::newConnection, this,
std::placeholders::_1, std::placeholders::_2));
}
using NewConnectionCallback = std::function;
void Acceptor::setNewConnectionCallback(const NewConnectionCallback &cb)
{
newConnectionCallback_ = cb; // 设置回调
}
总结一下TcpServer的构造函数干的事情
针对后面3个事件,分别进行分析
Acceptor class,用于accept4()新TCP连接,并通过回调通知使用者。它是内部class,供TcpServer使用,生命期由后者控制。
Acceptor的数据成员包括Socket、Channel等。其中Socket是一个RAIIhandle,封装了socket文件描述符的生命期。Acceptor的socket是listening socket,即server socket。Channel用于观察此socket上的readable
事件,并回调Acceptor:: handleRead(),后者会调用accept(2)来接受新连接,并回调用户callback。
Acceptor的构造函数和Acceptor::listen()成员函数执行创建TCP服务端的传统步骤,即调用socket(2)、bind(2)、listen(2)等Sockets API
Acceptor::Acceptor(EventLoop *loop, const InetAddress &listenAddr, bool reuseport)
: loop_(loop) // 所属的loop,注意这个loop为mainLoop
, acceptSocket_(createNonblocking()) // socket
, acceptChannel_(loop, acceptSocket_.fd()) // 将监听套接字封装进channel
, listenning_(false) // 设置监听状态
{
acceptSocket_.setReuseAddr(true); // 允许复用地址
acceptSocket_.setReusePort(reuseport); // 允许复用端口
acceptSocket_.bindAddress(listenAddr); // bind
acceptChannel_.setReadCallback(std::bind(&Acceptor::handleRead, this)); // 设置监听套接字的 read回调函数
}
总结一下Acceptor的构造函数干了啥
setsocket函数详解 —— socket对象使用的函数
Acceptor的重要数据成员
private:
void handleRead();
EventLoop *loop_;
Socket acceptSocket_;
Channel acceptChannel_;
NewConnectionCallback newConnectionCallback_; // TcpServer初始化时就设置好的回调函数,当连接建立时就会调用
bool listenning_;
EventLoopThreadPool构造函数
EventLoopThreadPool::EventLoopThreadPool(EventLoop *baseLoop, const std::string &nameArg)
: baseLoop_(baseLoop)
, name_(nameArg)
, started_(false) // 标志是开启
, numThreads_(0)
, next_(0)
{}
针对这个newConnectionCallback_ 的函数,主要分析的时什么时候 这个回调函数 具体什么时候开始执行
void setNewConnectionCallback(const NewConnectionCallback &cb)
{
newConnectionCallback_ = cb;
}
这个回调函数具体在handleRead()中执行
// listenfd有事件发生了,就是有新用户连接了
void Acceptor::handleRead()
{
InetAddress peerAddr;
int connfd = acceptSocket_.accept(&peerAddr);
if (connfd >= 0)
{
if (newConnectionCallback_)
{
newConnectionCallback_(connfd, peerAddr); // 轮询找到subLoop,唤醒,分发当前的新客户端的Channel
}
else
{
::close(connfd);
}
}
else
{
LOG_ERROR("%s:%s:%d accept err:%d \n", __FILE__, __FUNCTION__, __LINE__, errno);
if (errno == EMFILE)
{
LOG_ERROR("%s:%s:%d sockfd reached limit! \n", __FILE__, __FUNCTION__, __LINE__);
}
}
}
using ThreadInitCallback = std::function<void(EventLoop*)>;
// 开启服务器监听 loop.loop()
void TcpServer::start()
{
if (started_++ == 0) // 防止一个TcpServer对象被start多次
{
// 用户设置的回调函数threadInitCallback_,没设置为空
threadPool_->start(threadInitCallback_); // 启动threadPool
loop_->runInLoop(std::bind(&Acceptor::listen, acceptor_.get())); // 在当前线程开启监听
}
}
void Acceptor::listen()
{
listenning_ = true;
acceptSocket_.listen(); // listen
acceptChannel_.enableReading(); // acceptChannel_ => Poller
}
// 设置fd相应的事件状态
void enableReading() { events_ |= kReadEvent; update(); }
// enableReading 为channel添加 感兴趣的事件events_,并将events注册进入poller
总结一下 TcpServer::star()