Server关闭连接的情况有两种,一种是被动关闭,即对方先关闭连接,本地read(2)返回零,触发关闭逻辑。另一种是主动断开连接,即调用TcpConnection::forceClose()函数。
1 被动关闭
时序图如下
对方关闭时,read(2)会收到0字节,因此触发关闭逻辑。首先loop事件循环中channel触发handleEvent,然后调用readCallback_(),readCallback_在TcpConnection的构造函数中已绑定在TTcpConnection::handleRead上,如下:
void Channel::handleEvent(Timestamp receiveTime)
{
std::shared_ptr<void> guard;
if (tied_)
{
guard = tie_.lock();
if (guard)
{
handleEventWithGuard(receiveTime);
}
}
else
{
handleEventWithGuard(receiveTime);
}
}
void Channel::handleEventWithGuard(Timestamp receiveTime)
{
eventHandling_ = true;
LOG_TRACE << reventsToString();
if ((revents_ & POLLHUP) && !(revents_ & POLLIN))
{
if (logHup_)
{
LOG_WARN << "fd = " << fd_ << " Channel::handle_event() POLLHUP";
}
if (closeCallback_) closeCallback_();
}
if (revents_ & POLLNVAL)
{
LOG_WARN << "fd = " << fd_ << " Channel::handle_event() POLLNVAL";
}
if (revents_ & (POLLERR | POLLNVAL))
{
if (errorCallback_) errorCallback_();
}
if (revents_ & (POLLIN | POLLPRI | POLLRDHUP))
{
if (readCallback_) readCallback_(receiveTime);
}
if (revents_ & POLLOUT)
{
if (writeCallback_) writeCallback_();
}
eventHandling_ = false;
}
//TcpConnection的构造函数
TcpConnection::TcpConnection(EventLoop* loop,
const string& nameArg,
int sockfd,
const InetAddress& localAddr,
const InetAddress& peerAddr)
: loop_(CHECK_NOTNULL(loop)),
name_(nameArg),
state_(kConnecting),
reading_(true),
socket_(new Socket(sockfd)),
channel_(new Channel(loop, sockfd)),
localAddr_(localAddr),
peerAddr_(peerAddr),
highWaterMark_(64*1024*1024)
{
//通道可读事件到来的时候,回调TcpConnection::handleRead,_1是事件发生的时间
channel_->setReadCallback(
std::bind(&TcpConnection::handleRead, this, _1));
//通道可写事件到来的时候,回调cpConnection::handleWrite
channel_->setWriteCallback(
std::bind(&TcpConnection::handleWrite, this));
//连接关闭,回调TcpConnection::handleClose
channel_->setCloseCallback(
std::bind(&TcpConnection::handleClose, this));
//发生错误,回调TcpConnection::handleError
channel_->setErrorCallback(
std::bind(&TcpConnection::handleError, this));
LOG_DEBUG << "TcpConnection::ctor[" << name_ << "] at " << this
<< " fd=" << sockfd;
socket_->setKeepAlive(true);
}
void TcpConnection::handleRead(Timestamp receiveTime)
{
loop_->assertInLoopThread();
int savedErrno = 0;
ssize_t n = inputBuffer_.readFd(channel_->fd(), &savedErrno); //接受数据
if (n > 0) //读取n字节数据
{
messageCallback_(shared_from_this(), &inputBuffer_, receiveTime);
}
else if (n == 0) //对端关闭连接
{
handleClose();
}
else
{
errno = savedErrno;
LOG_SYSERR << "TcpConnection::handleRead";
handleError();
}
}
void TcpConnection::handleClose()
{
loop_->assertInLoopThread();
LOG_TRACE << "fd = " << channel_->fd() << " state = " << stateToString();
assert(state_ == kConnected || state_ == kDisconnecting);
// we don't close fd, leave it to dtor, so we can find leaks easily.
setState(kDisconnected);
channel_->disableAll(); //取消对channel的关注,并更新poller
TcpConnectionPtr guardThis(shared_from_this());
connectionCallback_(guardThis);
// must be the last line
closeCallback_(guardThis); //调用TcpServer::removeConnection()
}
由于读取到了0字节,因此inputBuffer_.readFd函数返回0,回调TcpConnection::handleClose,TcpConnection::handleClose()会取消channel的关注,并且调用调用connectionCallback_和closeCallback_,closeCallback_这个回调绑定到TcpServer::removeConnection().这个绑定发生在TcpConnection对象建立的时候,如下:
void TcpServer::newConnection(int sockfd, const InetAddress& peerAddr)
{
loop_->assertInLoopThread();
EventLoop* ioLoop = threadPool_->getNextLoop();
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));
// FIXME poll with zero timeout to double confirm the new connection
// FIXME use make_shared if necessary
TcpConnectionPtr conn(new TcpConnection(ioLoop,
connName,
sockfd,//客户端socket文件描述符
localAddr,
peerAddr));
connections_[connName] = conn;
conn->setConnectionCallback(connectionCallback_);
conn->setMessageCallback(messageCallback_);
conn->setWriteCompleteCallback(writeCompleteCallback_);
//设置CloseCallback
conn->setCloseCallback(
std::bind(&TcpServer::removeConnection, this, _1)); // FIXME: unsafe
ioLoop->runInLoop(std::bind(&TcpConnection::connectEstablished, conn));
}
在TcpServer::removeConnection()会把conn从ConnectionMap(TcpServer的数据成员,记录连接列表,即TcpConnection(share_ptr形式)和其名称)中移除,并将TcpConnection::connectionDestroyed()函数加入pendingFunctors_,在事件循环时调用TcpConnection::connectionDestroyed()。
void TcpServer::removeConnection(const TcpConnectionPtr& conn)
{
// FIXME: unsafe
loop_->runInLoop(std::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());//返回key为conn->name()的value的个数
(void)n;
assert(n == 1);
EventLoop* ioLoop = conn->getLoop();
ioLoop->queueInLoop(
std::bind(&TcpConnection::connectDestroyed, conn));
}
void TcpConnection::connectDestroyed()
{
loop_->assertInLoopThread();
//在TCphandleClose中已经设置了state_和使channel取消关注事件,因此下面if语句正常情况条件为假
if (state_ == kConnected)
{
setState(kDisconnected);
channel_->disableAll();
connectionCallback_(shared_from_this());
}
channel_->remove();
}
在TcpConnection::connectDestroyed()中,会移除channel,下面附上channel的移除逻辑
void Channel::remove()
{
assert(isNoneEvent()); //移除前需要使channel取消关注事件
addedToLoop_ = false;
loop_->removeChannel(this);
}
void EventLoop::removeChannel(Channel* channel)
{
assert(channel->ownerLoop() == this);
assertInLoopThread();
if (eventHandling_)
{
assert(currentActiveChannel_ == channel ||
std::find(activeChannels_.begin(), activeChannels_.end(), channel) == activeChannels_.end());
}
poller_->removeChannel(channel);
}
void EPollPoller::removeChannel(Channel* channel)
{
Poller::assertInLoopThread();
int fd = channel->fd();
LOG_TRACE << "fd = " << fd;
assert(channels_.find(fd) != channels_.end());
assert(channels_[fd] == channel);
assert(channel->isNoneEvent());
int index = channel->index();
assert(index == kAdded || index == kDeleted);
size_t n = channels_.erase(fd);
(void)n;
assert(n == 1);
if (index == kAdded)
{
update(EPOLL_CTL_DEL, channel);
}
channel->set_index(kNew);
}
2 主动关闭
(1)调用TcpConnection::forceClose()函数。
void TcpConnection::forceClose()
{
// FIXME: use compare and swap
if (state_ == kConnected || state_ == kDisconnecting)
{
setState(kDisconnecting);
loop_->queueInLoop(std::bind(&TcpConnection::forceCloseInLoop, shared_from_this()));
}
}
void TcpConnection::forceCloseWithDelay(double seconds)
{
if (state_ == kConnected || state_ == kDisconnecting)
{
setState(kDisconnecting);
loop_->runAfter(
seconds,
makeWeakCallback(shared_from_this(),
&TcpConnection::forceClose)); // not forceCloseInLoop to avoid race condition
}
}
void TcpConnection::forceCloseInLoop()
{
loop_->assertInLoopThread();
if (state_ == kConnected || state_ == kDisconnecting)
{
// as if we received 0 byte in handleRead();
handleClose();
}
(2)主动断开连接之半关闭
用户可以在相关回调函数(messageCallback_,writeCompleteCallback_,connectionCallback_)中调用TcpConnecion::shutdown函数来实现半关闭。函数如下:
void TcpConnection::shutdown()
{
// FIXME: use compare and swap
if (state_ == kConnected)
{
setState(kDisconnecting);
// FIXME: shared_from_this()?
loop_->runInLoop(std::bind(&TcpConnection::shutdownInLoop, this));
}
}
void TcpConnection::shutdownInLoop()
{
loop_->assertInLoopThread();
if (!channel_->isWriting()) //当channel不关注可写事件时才能关闭连接
{
// we are not writing
socket_->shutdownWrite(); //关闭写端
}
}
shutdown为半关闭状态,即关闭写端,但仍然能接收到对端发送来的数据。shutdown()函数中将连接的状态设置为了state_ = kdisConnecting,然后将shutdownInLoop加入事件循环中去处理。
观察shutdownInLoop()我们会发现存在一些情况导致该函数无法执行socket_->shutdownWrite()。
- 当outputBuffer_中的数据没有发送完成时,此时仍关注着写事件,因此导致channel_->isWriting()条件为真,无法执行socket_->shutdownWrite()。
别急,由于此时仍关注着POLLOUT事件,因此在事件循环中会触发handleWrite来发送数据。我们不妨先看看handleWrite函数:
void TcpConnection::handleWrite()
{
loop_->assertInLoopThread();
if (channel_->isWriting()) //如果关注了可写事件,则直接往套接字文件描述符中写数据
{
ssize_t n = sockets::write(channel_->fd(),
outputBuffer_.peek(),
outputBuffer_.readableBytes());
if (n > 0)
{
outputBuffer_.retrieve(n);
if (outputBuffer_.readableBytes() == 0) //说明发送缓冲区已经清空
{
channel_->disableWriting(); //停止关注POLLOUT事件,以免出现busy loop
if (writeCompleteCallback_) //回调writeCompleteCallback_
{
loop_->queueInLoop(std::bind(writeCompleteCallback_, shared_from_this()));
}
if (state_ == kDisconnecting) //发送缓冲区已经清空,并且连接状态为kDisconnecting,要关闭连接
{
shutdownInLoop(); //关闭连接
}
}
}
else
{
LOG_SYSERR << "TcpConnection::handleWrite";
// if (state_ == kDisconnecting)
// {
// shutdownInLoop();
// }
}
}
else
{
LOG_TRACE << "Connection fd = " << channel_->fd()
<< " is down, no more writing";
}
}
- 在handleWrite函数中我们会发现,当缓冲区中有数据时,会将数据发送完毕,当数据发送完毕时,由于在上面的shutdown函数中我们已经设置连接状态为kdisConnecting,因此在handleWrite函数中会调用shutdownInLoop()来确保完成半关闭。
- 此外,思考一下完成半关闭后如何最后完成关闭呢?半关闭时我们会发送TCP FIN分节,对方会收到0字节,然后对方通常会关闭连接。这样我们会读到0字节,触发handleRead函数,读到0字节时触发handleClose()函数完成连接的关闭。
void TcpConnection::handleRead(Timestamp receiveTime)
{
loop_->assertInLoopThread();
int savedErrno = 0;
ssize_t n = inputBuffer_.readFd(channel_->fd(), &savedErrno); //接受数据
if (n > 0) //读取n字节数据
{
messageCallback_(shared_from_this(), &inputBuffer_, receiveTime);
}
else if (n == 0) //对端关闭连接
{
handleClose();
}
else
{
errno = savedErrno;
LOG_SYSERR << "TcpConnection::handleRead";
handleError();
}
}