TcpConnection
TcpConnection class是muduo里唯一默认使用shared_ptr来管理的class,也是唯一继承enable_shared_from_this的class
。TcpConnection一旦连接断开,这个TcpConnection对象就没啥用了。它没有发起连接的功能
,其构造函数的参数是已经建立好连接的socket fd。
TcpConnection使用Channel来获得socket上的IO事件,构造TcpConnection对象的时候,Channel注册可读,可写,错误,关闭事件。
他会自己处理writable事件,而把readable事件通过MessageCallback传达给客户。TcpConnection拥有TCP socket,它的析构函数会closed(fd)。
TcpConnection::TcpConnection(EventLoop* loop,
const string& nameArg,
int sockfd,
const InetAddress& localAddr,
const InetAddress& peerAddr)
{
channel_->setReadCallback(
boost::bind(&TcpConnection::handleRead, this, _1));
channel_->setWriteCallback( // 通道可写事件到来的时候,回调TcpConnection::handleWrite
boost::bind(&TcpConnection::handleWrite, this));
channel_->setCloseCallback( // 连接关闭,回调TcpConnection::handleClose
boost::bind(&TcpConnection::handleClose, this));
channel_->setErrorCallback( // 发生错误,回调TcpConnection::handleError
boost::bind(&TcpConnection::handleError, this));
LOG_DEBUG << "TcpConnection::ctor[" << name_ << "] at " << this
<< " fd=" << sockfd;
socket_->setKeepAlive(true);
}
TcpConnection断开连接
muduo只有一种关闭连接的方式:被动关闭。
即对方先关闭连接,本地read返回0,触发关闭逻辑。一般来讲数据的删除比新建要复杂,TCP连接也是这样。复杂在于对象生命期的管理。
TcpConnection断开连接连接时序图
假设现在已经建立了一个新连接,经过几次收发数据后,对等方关闭close套接字,TcpConnection::channel_ 可读事件发生,poll返回,调用Channel::handleEvent()处理活动通道,调用TcpConnection::handleRead(),::read()返回0,进而调用TcpConnection::handleClose()
class TcpConnection : boost::noncopyable,
public boost::enable_shared_from_this<TcpConnection>
void TcpConnection::handleClose()
{
setState(kDisconnected);
channel_->disableAll();
TcpConnectionPtr guardThis(shared_from_this());
connectionCallback_(guardThis);
// must be the last line
closeCallback_(guardThis); // 调用TcpServer::removeConnection
}
TcpConnection class新增closeCallback_事件回调
,这个回调是给TcpServer和TcpClient用的,用于通知它们移除所持有的TcpConnectionPtr。
TcpConnection::handleClose的主要功能是调用closeCallback_,这个回调绑定到TcpServer::removeConnection。
TcpServer::removeConnection,一定要用queueInLoop,否则会出现对象生命期管理问题。
void TcpServer::newConnection(int sockfd, const InetAddress &peerAddr)
{
.....
conn->setCloseCallback(
boost::bind(&TcpServer::removeConnection, this, _1));
}
void TcpServer::removeConnection(const TcpConnectionPtr& conn)
{
// FIXME: unsafe
loop_->runInLoop(boost::bind(&TcpServer::removeConnectionInLoop, this, conn));
}
void TcpServer::removeConnectionInLoop(const TcpConnectionPtr& conn)
{
loop_->assertInLoopThread();
LOG_INFO << "TcpServer::removeConnectionInLoop [" << name_
<< "] - connection " << conn->name();
size_t n = connections_.erase(conn->name());
(void)n;
assert(n == 1);
EventLoop* ioLoop = conn->getLoop();
ioLoop->queueInLoop(
boost::bind(&TcpConnection::connectDestroyed, conn));
}
将TcpConnectionPtr 在connections中erase掉时并不会马上析构TcpConnection对象(引用计数不为0)因为此时正处于Channel::handleEvent()中,`如果析构了TcpConnection,那么它的成员channel也会被析构,即导致core dump。TcpConnection对象生存期要长于handleEvent()函数,直到执行完connectDestroyed()后才会析构。`
TcpConnection::connectDestroyed
调用channel_->remove()。它是TcpConnection析构前最后调用的一个成员函数,它通知用户连接已经断开。
某些情况下,可以不经由handleClose而直接调用connectDestroyed。
void TcpConnection::connectDestroyed()
{
loop_->assertInLoopThread();
if (state_ == kConnected)
{
setState(kDisconnected);
channel_->disableAll();
connectionCallback_(shared_from_this());
}
channel_->remove(); //poll 不再关注此通道
}
Poller::removeChannel,从pollfds_中删除元素是O(1)
,将待删除的元素与最后一个元素交换,再pollfds_.pop_back()。
TcpConnection生命周期
通常TcpServer的生命期长于它建立的TcpConnection,在muduo中,TcpServer的析构函数会关闭连接。
这里用到boost::bind让TcpConnection的生命期长到调用connectDestroyed。
TcpServer::~TcpServer()
{
loop_->assertInLoopThread();
LOG_TRACE << "TcpServer::~TcpServer [" << name_ << "] destructing";
for (ConnectionMap::iterator it(connections_.begin());
it != connections_.end(); ++it)
{
TcpConnectionPtr conn = it->second;
it->second.reset();
conn->getLoop()->runInLoop(
boost::bind(&TcpConnection::connectDestroyed, conn));
conn.reset();
}
}
Channel::handleEvent执行到一半的时候,所属的Channel对象被销毁。在析构函数中加上断言,保证事件处理期间本Channel对象不会被析构。
Channel::~Channel()
{
//保证事件处理期间本Channel对象不会被析构
assert(!eventHandling_);
}
shared_from_this
shared_from_this()会用当前对象的裸指针构造一个临时智能指针对象,引用计数加1,但马上会被析构,又减1,故无论调用多少次,对引用计数都没有影响。
TcpConnectionPtr guardThis(shared_from_this());
为什么不能直接写成TcpConnectionPtr guardThis(this);这样写的话,guardThis的引用计数就为1,而不是2。