muduo库的学习10---TcpServer与TcpClient的基本设计---TcpConnection建立与关闭

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之后。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值