1.shutdown与close的区别
(1)两种半关闭状态
①关闭读一半
套接字不再接受数据,套接字接受缓冲区中的现有数据都会被丢弃。
②关闭写一半
称为半关闭
套接字先将当前发送缓冲区中的数据发送完,然后发送TCP正常连接终止序列。
不能对套接字调用任何写函数(协议栈仍然能自动发送FIN的确认信息)。
注:①、②说的缓冲区都是说的内核协议栈缓冲区,不是我们之前研究的Buffer,这是应用层的缓冲区。
(2)close的坏处
它需要根据描述符的计数来发送FIN序列。
2.连接建立
(1)图
(2)涉及的关键源代码
1.
void Acceptor::handleRead()
{
loop_->assertInLoopThread();
InetAddress peerAddr;
//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 livev.
if (errno == EMFILE)
{
::close(idleFd_);
idleFd_ = ::accept(acceptSocket_.fd(), NULL, NULL);
::close(idleFd_);
idleFd_ = ::open("/dev/null", O_RDONLY | O_CLOEXEC);
}
}
}
2.
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,
localAddr,
peerAddr));
connections_[connName] = conn;
conn->setConnectionCallback(connectionCallback_);
conn->setMessageCallback(messageCallback_);
conn->setWriteCompleteCallback(writeCompleteCallback_);
conn->setCloseCallback(
boost::bind(&TcpServer::removeConnection, this, _1)); // FIXME: unsafe
ioLoop->runInLoop(boost::bind(&TcpConnection::connectEstablished, conn));
}
3.
void TcpConnection::connectEstablished()
{
loop_->assertInLoopThread();
assert(state_ == kConnecting);
setState(kConnected);
channel_->tie(shared_from_this());
channel_->enableReading();
connectionCallback_(shared_from_this());
}
(3)代码解释
①首先在连接请求事件到达时
listenfdChannel调用Acceptor::handleRead()
A。accept连接请求。获得connfd与peerAddr
B。用connfd和peerAddr调用TcpServer::newConnection
②在newConnection中
A。新建一个TcpConnection,这里用conn表示,将其放入connections
B。对这个conn调用各种callback的set函数
C。调用TcpConnection::connectEstablished
③在connectEstablished中
A。使能connChannel开始监听
B。调用connectionCallback
(4)3个callback
①Acceptor::handleRead
由listenChannel调用,绑定newConnectionCallback
②newConnectionCallback
由handleRead调用,绑定TcpServer::newConnection
③connectionCallback
由TcpConnetion::connectEstablished或者TcpConnection::handleClose调用,绑定用户给定的代码
3.连接关闭
(1)图
(2)涉及的关键源代码
1.
void TcpConnection::handleRead(Timestamp receiveTime)
{
loop_->assertInLoopThread();
int savedErrno = 0;
ssize_t n = inputBuffer_.readFd(channel_->fd(), &savedErrno);
if (n > 0)
{
messageCallback_(shared_from_this(), &inputBuffer_, receiveTime);
}
else if (n == 0)
{
handleClose();
}
else
{
errno = savedErrno;
LOG_SYSERR << "TcpConnection::handleRead";
handleError();
}
}
2.
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();
TcpConnectionPtr guardThis(shared_from_this());
connectionCallback_(guardThis);
// must be the last line
closeCallback_(guardThis);
}
3.
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));
}
4.
void TcpConnection::destroyed()
{
loop_->assertInLoopThread();
if (state_ == kConnected)
{
setState(kDisconnected);
channel_->disableAll();
connectionCallback_(shared_from_this());
}
channel_->remove();
}
(3)代码解释
这里的连接关闭都是被动关闭。
被动关闭就是说首先由read到n=0,之后进行的处理。
(1)TcpConnection::handleRead()
由read到n=0,调用TcpConnection::handleClose
(2)TcpConnection::handleClose()
①关闭connChannel监听功能
②将conn的引用加1,调用connectionCallback和closeCallback
(3)TcpServer::removeConnection
转换调用者为TcpServer
①从TcpServer中的connections里面移除conn
因为使用了参数传递conn所以,conn表示的那个TcpConnection引用计数为2(connctions里面有1个,这个传递的参数有1个),
这里移除后,引用计数变为1
②调用TcpConnection::connectDestroyed
(4)TcpConnection::connectDestroyed
使得connChannel,remove出loop监听队列
这个函数在doPendingFunctors执行完成之后,conn的引用计数变为0,这个时候conn对象将会被销毁。
属于这个conn的connfd,connChanne都会被销毁
(4)关于P274页提到的conn生命周期问题
①图
②解释
由图7-55所示,在以connChannel调用handleEvent的时候,如果在里面调用一个函数onClose
如果这个函数调用removeConnection,如果conn只在connections里面有一个引用计数,一旦connctions.erase(conn)
那么conn将会被析构,这个时候connChannel作为其成员也会被析构,但是正处于connChannel的函数的调用中,这样会造成遭难
③解决关键步骤
A。在源代码2中的TcpConnection::handleClose中,取得出conn的一个引用,并将其作为参数传递,
保证connctions.earse(conn)的时候,conn不会被直接析构
B。在调用的时候采用XXXinLoop,使用bind这样调用函数的对象conn作为参数传递,使其一直不被析构,
直到在loop中完成了remove(connChannel)
④关闭步骤这么复杂的原因
是因为简单地断开一个connction涉及到多个类的操作,你必须要通过runInLoop到那个类中,进行相应操作,且顺序不能乱
A。将conn从connctions里面移除
B。将connChannel从Loop中的监听队列中unregister
C。在A,B的基础上你又要保证conn的生命周期,使得conn存活到B之后。