本贴用于记录muduo库的学习过程,以下是关于TcpServer的个人理解。
TcpServer内含Acceptor、threadpool等类,算是把主线程所有要做的事封装了起来。
重要成员变量
EventLoop *loop_; // baseloop 用户自定义的loopconst std::string ipPort_;const std::string name_;std::unique_ptr<Acceptor> acceptor_; // 运行在mainloop 任务就是监听新连接事件std::shared_ptr<EventLoopThreadPool> threadPool_; // one loop per threadConnectionCallback connectionCallback_; //有新连接时的回调MessageCallback messageCallback_; // 有读写事件发生时的回调WriteCompleteCallback writeCompleteCallback_; // 消息发送完成后的回调ThreadInitCallback threadInitCallback_; // loop线程初始化的回调int numThreads_;//线程池中线程的数量。std::atomic_int started_;int nextConnId_;ConnectionMap connections_; // 保存所有的连接
loop_:代表baseloop。
acceptor_:用来监听和接收新连接。
threadPool_:用于管理线程。
started_:是否启动的标志。用atomic_int定义为了维护线程安全。
nextConnId:表示是第几个连接。
connections_:保存所有的tcpconnection指针。
重要成员函数
TcpServer::TcpServer(EventLoop *loop,const InetAddress &listenAddr,const std::string &nameArg,Option option): loop_(CheckLoopNotNull(loop)), ipPort_(listenAddr.toIpPort()), name_(nameArg), acceptor_(new Acceptor(loop, listenAddr, option == kReusePort)), threadPool_(new EventLoopThreadPool(loop, name_)), connectionCallback_(), messageCallback_(), nextConnId_(1), started_(0)
{// 当有新用户连接时,Acceptor类中绑定的acceptChannel_会有读事件发生,执行handleRead()调用TcpServer::newConnection回调acceptor_->setNewConnectionCallback(std::bind(&TcpServer::newConnection, this, std::placeholders::_1, std::placeholders::_2));
}
构造函数,acceptor和threadpool都是new出来的,但是eventloop是外部输入的,为什么这样设计?我感觉是为了让生命周期更清晰,loop由外部输入,结束时tcpserver先析构,loop再析构,这样不会存在server调用不存在loop的情况。希望知道的朋友可以告诉我。
void TcpServer::setThreadNum(int numThreads)
{int numThreads_=numThreads;threadPool_->setThreadNum(numThreads_);
}
设置要开启多少线程。
void TcpServer::start()
{if (started_.fetch_add(1) == 0) // 防止一个TcpServer对象被start多次{threadPool_->start(threadInitCallback_); // 启动底层的loop线程池loop_->runInLoop(std::bind(&Acceptor::listen, acceptor_.get()));}
}
tecpserver的启动函数。fetch_add(1)将started_原子性地+1,并返回旧值。
void TcpServer::newConnection(int sockfd, const InetAddress &peerAddr)
{// 轮询算法 选择一个subLoop 来管理connfd对应的channelEventLoop *ioLoop = threadPool_->getNextLoop();char buf[64] = {0};snprintf(buf, sizeof buf, "-%s#%d", ipPort_.c_str(), nextConnId_);++nextConnId_; // 这里没有设置为原子类是因为其只在mainloop中执行 不涉及线程安全问题std::string connName = name_ + buf;LOG_INFO("TcpServer::newConnection [%s] - new connection [%s] from %s\n",name_.c_str(), connName.c_str(), peerAddr.toIpPort().c_str());// 通过sockfd获取其绑定的本机的ip地址和端口信息sockaddr_in local;::memset(&local, 0, sizeof(local));socklen_t addrlen = sizeof(local);if(::getsockname(sockfd, (sockaddr *)&local, &addrlen) < 0){LOG_ERROR("sockets::getLocalAddr");}InetAddress localAddr(local);TcpConnectionPtr conn(new TcpConnection(ioLoop,connName,sockfd,localAddr,peerAddr));connections_[connName] = conn;// 下面的回调都是用户设置给TcpServer => TcpConnection的,至于Channel绑定的则是TcpConnection设置的四个,handleRead,handleWrite... 这下面的回调用于handlexxx函数中conn->setConnectionCallback(connectionCallback_);conn->setMessageCallback(messageCallback_);conn->setWriteCompleteCallback(writeCompleteCallback_);// 设置了如何关闭连接的回调conn->setCloseCallback(std::bind(&TcpServer::removeConnection, this, std::placeholders::_1));ioLoop->runInLoop(std::bind(&TcpConnection::connectEstablished, conn));
}
当acceptor的channel触发事件,进行这个函数回调。getsockname得到此连接对应的本地端口。我们已经获得了连接的socket,接收和发送信息都通过这个socket,为什么还要tcpconnection保存对方的地址peerAddr。比如有人进攻你的网站,你需要一个黑白名单,就可以通过这个peeraddr来设置。
void TcpServer::removeConnection(const TcpConnectionPtr &conn)
{loop_->runInLoop(std::bind(&TcpServer::removeConnectionInLoop, this, conn));
}void TcpServer::removeConnectionInLoop(const TcpConnectionPtr &conn)
{LOG_INFO("TcpServer::removeConnectionInLoop [%s] - connection %s\n",name_.c_str(), conn->name().c_str());connections_.erase(conn->name());EventLoop *ioLoop = conn->getLoop();ioLoop->queueInLoop(std::bind(&TcpConnection::connectDestroyed, conn));
}
当要移除一个连接时,先将removeConnectionInLoop函数放入所属线程等待执行,erase移除在tcpserver里的conn指针,由于conn也是一个shared_ptr(由shared_from_this()传入),所以不会直接销毁,在启动connectiondestroyed函数,对conn内的事件取消监听,结束后自动销毁TCPConnection。
TcpServer::~TcpServer()
{for(auto &item : connections_){TcpConnectionPtr conn(item.second);item.second.reset(); // 把原始的智能指针复位 让栈空间的TcpConnectionPtr conn指向该对象 当conn出了其作用域 即可释放智能指针指向的对象// 销毁连接conn->getLoop()->runInLoop(std::bind(&TcpConnection::connectDestroyed, conn));}
}
当tcpserver要析构时,遍历connections_,reset用于将该智能指针设置为空,再销毁该tcpconnection。
以上就是我对TcpServer类的理解,欢迎大家交流讨论。