本貼用于記錄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類的理解,歡迎大家交流討論。