muduo-net 源码分析(七)——TcpServer 和 TcpConnection(二)

先序

这一节我们将继续完善 TcpServerTCPConnection 类,并且解决上一节思考的问题【一定要理清思路】;

TcpServer 源码分析

重要的变量

ConnectionCallback connectionCallback_;       //连接到来和结束的回调函数

重要的函数

//可以自己设置,muduo 库是有默认的
void setConnectionCallback(const ConnectionCallback& cb)
  { connectionCallback_ = cb; }
void removeConnection(const TcpConnectionPtr& conn);
void removeConnectionInLoop(const TcpConnectionPtr& conn);

具体实现

TcpServer::TcpServer(EventLoop* loop,
                     const InetAddress& listenAddr,
                     const string& nameArg,
                     Option option)
  : loop_(CHECK_NOTNULL(loop)),   //检查 loop 指针是否为 NULL
    ipPort_(listenAddr.toIpPort()),
    name_(nameArg),
    acceptor_(new Acceptor(loop, listenAddr, option == kReusePort)),
    ------------------------------------------------------------------------
    //threadPool_(new EventLoopThreadPool(loop, name_)),
    //默认的连接回调函数,全局函数
    //这个函数的作用就是输出客户端套接字的状态
    connectionCallback_(defaultConnectionCallback),
    ------------------------------------------------------------------------
    // messageCallback_(defaultMessageCallback),
    nextConnId_(1)
{
  //设置 newConnection 的回调函数,因为有两个参数,所以有两个占位符
  //第一个参数是客户端套接字,第二个参数是客户端地址
  acceptor_->setNewConnectionCallback(
      std::bind(&TcpServer::newConnection, this, _1, _2));
}

//根据 conn 的状态判断现在是连接还是断开
void muduo::net::defaultConnectionCallback(const TcpConnectionPtr& conn)
{
  LOG_TRACE << conn->localAddress().toIpPort() << " -> "
            << conn->peerAddress().toIpPort() << " is "
            << (conn->connected() ? "UP" : "DOWN");
}


void TcpServer::newConnection(int sockfd, const InetAddress& peerAddr)
{
  //断言在 IO 线程中
  loop_->assertInLoopThread();
  //EventLoop* ioLoop = threadPool_->getNextLoop();
  char buf[64];
  snprintf(buf, sizeof buf, "-%s#%d", ipPort_.c_str(), nextConnId_);
  ++nextConnId_;
  //连接的名称是 服务器名称 + 服务器端口号 + 连接 ID
  string connName = name_ + buf;

  //输出客户端的信息
  LOG_INFO << "TcpServer::newConnection [" << name_
           << "] - new connection [" << connName
           << "] from " << peerAddr.toIpPort();

  InetAddress localAddr(sockets::getLocalAddr(sockfd));
  
  // 创建一个 TcpConnectionPtr 对象指向一个 TCPConnection
  //不太理解的地方,传递的 peerAddr 是客户端的地址,localAddr 也是客户端的地址?
  TcpConnectionPtr conn(new TcpConnection(loop,
                                          connName,
                                          sockfd,
                                          localAddr,
                                          peerAddr));
  /*TcpConnectionPtr conn(new TcpConnection(ioLoop,
                                          connName,
                                          sockfd,
                                          localAddr,
                                          peerAddr));
                                          */
      
  //记录客户端连接                                        
  connections_[connName] = conn;
  
  --------------------------------------------------------------
  //注册 TCPConnection 的 connectionCallback 回调函数
  conn->setConnectionCallback(connectionCallback_);
  ----------------------------------------------------------------
  //conn->setMessageCallback(messageCallback_);
  //conn->setWriteCompleteCallback(writeCompleteCallback_);
  ------------------------------------------------------------------
  //注册 TCPConnection 的 CloseCallback 回调函数
  conn->setCloseCallback(
      std::bind(&TcpServer::removeConnection, this, _1)); 
  ---------------------------------------------------------------------
  
  //调用 TcpConnection::connectEstablished 函数
  loop->runInLoop(std::bind(&TcpConnection::connectEstablished, conn));
  //ioLoop->runInLoop(std::bind(&TcpConnection::connectEstablished, conn));
}

//removeConnection 可跨线程使用
void TcpServer::removeConnection(const TcpConnectionPtr& conn)
{
  loop_->runInLoop(std::bind(&TcpServer::removeConnectionInLoop, this, conn));
}

//只能在 IO 线程中使用
void TcpServer::removeConnectionInLoop(const TcpConnectionPtr& conn)
{
  loop_->assertInLoopThread();
  LOG_INFO << "TcpServer::removeConnectionInLoop [" << name_
           << "] - connection " << conn->name();
  
  //将对象从列表中移除,注意此时引用计数会减 1
  //如果不使用 tie,则是引用计数为 0
  //TcpConnection 对象被销毁,回国 TcpConnection 的 channel 对象就会被销毁,而此时我们处于 channel 对象的 handleEvent() 中,会造成 coredump
  //使用 tie 后,调用 handleEvent() 时,weak_ptr 会被升级为 share_ptr,所以此时删除并不会销毁 TcpConnection
  size_t n = connections_.erase(conn->name());

  (void)n;
  assert(n == 1);
  
  //注意,目前这里的 ioLoop 就是上面的 loop_
  EventLoop* ioLoop = conn->getLoop();

  //这里调用 queueInLoop 而不是调用 runInLoop 
  //这也是 queueInLoop  里面有两个条件的原因
  ioLoop->queueInLoop(
      std::bind(&TcpConnection::connectDestroyed, conn));
}

这里只是多给出了 TCPConnection 连接断开时触发的函数;

TCPConnection 源码分析

重要的变量

ConnectionCallback connectionCallback_;       //连接回调函数,包含连接建立和连接断开
CloseCallback closeCallback_;                 //连接断开回调函数

重要的函数

void setConnectionCallback(const ConnectionCallback& cb)
  { connectionCallback_ = cb; }

void setCloseCallback(const CloseCallback& cb)
  { closeCallback_ = cb; }

void connectDestroyed(); 

void handleRead(Timestamp receiveTime);
void handleClose();
void handleError();

具体实现

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)
{
  channel_->setReadCallback(
      std::bind(&TcpConnection::handleRead, this, _1));
 /*
  channel_->setWriteCallback(
      std::bind(&TcpConnection::handleWrite, this));
  */
  ------------------------------------------------------------------
  channel_->setCloseCallback(
      std::bind(&TcpConnection::handleClose, this));
  //发生错误,回调 handleError
  channel_->setErrorCallback(
      std::bind(&TcpConnection::handleError, this));
  ------------------------------------------------------------------
  LOG_DEBUG << "TcpConnection::ctor[" <<  name_ << "] at " << this
            << " fd=" << sockfd;
  socket_->setKeepAlive(true);
}


void TcpConnection::connectEstablished()
{
  loop_->assertInLoopThread();
  assert(state_ == kConnecting);

  //设置状态为已连接
  setState( kConnected);
  
  //share_from_this 是根据裸指针创建一个 shared_ptr 对象
  //这里使用了 Channel 的 tie,这是 Channel 中没有讨论的地方,这里会将 Channel 完善
  channel_->tie(shared_from_this());
  
  //关注 TcpConnect 的可读事件
  channel_->enableReading();

 ---------------------------------------------------
  //这里输出成功连接
  connectionCallback_(shared_from_this());
 ---------------------------------------------------  
}


//接收客户端的信息
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();
  }
  */
 //以下是模拟普通的接收过程
 loop_->assertInLoopThread();
 int saveErrno = 0;
 char buf[65536];
 ssize_t n = ::read(channel->fd(), buf, sizeof buf);
 if (n > 0) {
   messageCallback(shared_from_this(), buf, n);
 }
 //处理连接断开
 else if (n == 0) {
   handleClose();
 }
 //处理错误情况 
 else {
   errno = saveErrno;
   LOG_SYSERR << "TcpConnection::handleRead";
   handleError();
 }
}


void TcpConnection::handleClose()
{
  loop_->assertInLoopThread();
  LOG_TRACE << "fd = " << channel_->fd() << " state = " << stateToString();
  assert(state_ == kConnected || state_ == kDisconnecting);
  
  //这里将状态设置为已断开连接
  setState(kDisconnected);

  //这里并没有调用 channel_ 的 remove(),因为当前还处于 channel_ 的 handleEvent() 函数中
  channel_->disableAll();
 
  
  TcpConnectionPtr guardThis(shared_from_this());
  //connectionCallback_ 函数在这里是显示连接断开
  connectionCallback_(guardThis);
  
  //这里调用的 TcpServer::removeConnection 函数
  closeCallback_(guardThis);
}

//错误输出
void TcpConnection::handleError()
{
  int err = sockets::getSocketError(channel_->fd());
  LOG_ERROR << "TcpConnection::handleError [" << name_
            << "] - SO_ERROR = " << err << " " << strerror_tl(err);
}


//connectDestroyed 将 channel 从管道中移除
void TcpConnection::connectDestroyed()
{
  loop_->assertInLoopThread();
  
  //前面设置过状态为 kDisconnected,所以这里为 fasle
  //前面可以不作处理,在这里进行处理
  if (state_ == kConnected)
  {
    setState(kDisconnected);
    channel_->disableAll();

    connectionCallback_(shared_from_this());
  }
  
  //此时 handleEvent() 已经处理完了,可以调用 remove() 函数了
  channel_->remove();
}

TcpConnection 的声明周期

流程

其实在上面的备注中我已经说明了问什么要使用 tie,但是为了理清整体的思路,这边会详细介绍一下整体的过程【直接从客户端发起连接开始说起】;

  1. 客户端发起连接,服务器的 Acceptor 读事件被触发,调用了 TcpServer::newConnection()
  2. 创建了 TcpConnection 对象,将该对象传递给 channeltie 对象;
  3. 调用 TcpConnection::connectEstablished()客户端套接字的读事件被监听
  4. 客户端主动发起关闭,客户端套接字的读事件被触发,调用 handleEvent()注意此时因为有 tie 所以会将先将 weak_ptr 升级为 shared_ptr;然后发现是读事件,调用 TcpConnection::handleRead()
  5. 因为是关闭连接,所以调用 handleClose(),这里会调用 TcpServer::removeConnection()connections_ 中客户端的记录删除;
  6. 最后在队列中添加 TcpConnection::connectDestroyed 中将通道从列表中移除;

解析

  • 在第 4 步中,如果我们不使用 tie,而是像正常的处理方式调用 handleEvent(),此时引用计数的值为 1
  • 在第 5 步中,我们调用 TcpServer::removeConnection()connections_ 中客户端的记录删除;引用计数会减 1,很不幸,此时引用计数已经为 0 了,那么 TcpConnection 对象被释放,它的 channel 也被释放;但此时仍然处于 channel::handleEvent() 中,所以会发生 coredump
  • 所以我们要保证 TcpConnection 要在 channel::handleEvent() 执行完之后被释放;所以就有了 tie

  • 在使用 tie 的情况下,调用 TcpServer::removeConnection()connections_ 中客户端的记录删除;引用计数会减 1,但此时引用计数为 1,并不会释放 TcpConnection 对象,那我们将这个对象传入到 handleEvent() 函数之后的 EvenLoop::doPendingFunctors()不就行了吗,在这个函数里面将对象释放;
//将 conn 传入到队列中,执行玩这个函数后,conn 会被释放,引用计数减 1,对象销毁
ioLoop->queueInLoop(
      std::bind(&TcpConnection::connectDestroyed, conn));

结束语

TcpServer 中还有多线程没有讨论,TcpConnection 中还有应用层缓冲和输入输出处理函数没有讨论,这是接下来讨论的重点;讨论完后,muduo 库的实现就差不多完成了!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值