muduo库分析——net篇(5)Tcp
已经总结了,TcpServer是一个控制器,维护了一个TcpConnection列表和一个Acceptor
Acceptor作用是建立一个监听套接字,用来接受新的连接,并调用相关回调函数
TcpConnection是拥有Socket和Channel两个成员,Socket用于监听相关连接数据,Channel用于事件管理
TcpServer还有一个EventLoopThreadPool成员,这是事件分发到其他线程,在这里就是讲新的连接给其他线程进行管理和监听数据
TcpServer的EventLoop被Acceptor和EventLoopThreadPool共有,这是因为TcpServer是一个总的控制,它的作用是接受新的连接,分发连接到其他线程这两个功能,因此这个IO线程的作用就是管理Acceptor的新连接事件,开启多个线程,以轮调的方式把每个连接分发给新的线程,这些事件都统一交给EventLoop处理
Acceptor一些注释:
acceptSocket_.setReuseAddr(true); //设置socket,主要在time_wait情况下,该地址:端口可以立即重用
acceptSocket_.setReusePort(reuseport);//设置端口可重用,多个线程或进程可以监听此端口
acceptSocket_.bindAddress(listenAddr);//设置地址簇
acceptChannel_.setReadCallback( //设置Channel回调函数
boost::bind(&Acceptor::handleRead, this));
void Acceptor::handleRead() //监听描述符一旦有可读事件说明有新的链接到来,设置相应回调函数
{
loop_->assertInLoopThread(); //判断是否在accpetor线程中
InetAddress peerAddr; //链接到来的IP地址器
//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);
}
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);
}
}
这是一个新的连接到来的过程
///Acceptor的handleread在accept有新的可读事件时设置相关回调函数
acceptor_->setNewConnectionCallback(
boost::bind(&TcpServer::newConnection, this, _1, _2));
///新的连接到来时回调函数
void TcpServer::newConnection(int sockfd, const InetAddress& peerAddr)
{
loop_->assertInLoopThread();
EventLoop* ioLoop = threadPool_->getNextLoop();//获得当前空闲的loop
char buf[64];
snprintf(buf, sizeof buf, "-%s#%d", ipPort_.c_str(), nextConnId_);
++nextConnId_;
string connName = name_ + buf;
LOG_INFO << "TcpServer::newConnection [" << name_
<< "] - new connection [" << connName
<< "] from " << peerAddr.toIpPort();
InetAddress localAddr(sockets::getLocalAddr(sockfd)); //获得本地地址器用于新建TcpConnection
// FIXME poll with zero timeout to double confirm the new connection
// FIXME use make_shared if necessary
TcpConnectionPtr conn(new TcpConnection(ioLoop,
connName,
sockfd,
localAddr,
peerAddr));
connections_[connName] = conn;
conn->setConnectionCallback(connectionCallback_); //相关设置
conn->setMessageCallback(messageCallback_);
conn->setWriteCompleteCallback(writeCompleteCallback_);
conn->setCloseCallback(
boost::bind(&TcpServer::removeConnection, this, _1)); // FIXME: unsafe 应该是因为传递this不安全,而应该传递shared_from_this
//acceptor接受新的连接后,将其分配给所属io线程进行处理
ioLoop->runInLoop(boost::bind(&TcpConnection::connectEstablished, conn));
}
这里要注意,分发给新的IO线程后,相关的注册都要交由这个IO线程调用
这里是移除连接的操作,需要注意的是,这些操作是通过一层一层的回调
首先要明确,对象可以自杀,自杀后只要不使用相关成员属性和虚函数即可
这里使用回调添加remove事件让loop去执行的原因,我个人认为不全是因为生命周期不一样而导致删除了TcpConnection会导致channel回调出现core dump
首先TcpConnection用了智能指针来管理,内部的Channel和Socket也是智能指针管理,你这一层的删除不一定会真正删除,只是引用计数减一,第二,对象自杀时可以调用析构函数,也可以访问其他非虚函数,如果出现core dump那么只能是因为访问了成员属性或者虚函数
以上两个原因就是为什么我不认为作者用这个流程处理断开连接是因为生命周期不一致
我认为作者这么做的原因
第一,符合设计规范,连接的建立是自下而上的,那么断开删除连接就应该自上而下,从总的控制器开始删除,再从其相关的IO线程和TcpConnection删除
第二,如果在回调过程中访问了其他成员,进行一些判断时,这样的设计就不需要担心是否会出现core dump现象,因为这是回调完成后,把删除作为一次事件最后添加到Loop中的事件循环中调用,让设计者不需要瞻前顾后
后面一些删除操作网上其他人都写的挺详细,我就不在此多此一举了