muduo 源码分析(二)客户端工作流程

本文主要分析muduo库中客户端的工作流程,包括主动连接、连接成功后的处理以及数据收发和关闭连接的步骤。客户端通过TcpClient类发起连接,使用非阻塞套接字尝试连接服务端,建立连接后,依赖TcpConnection类进行数据交互。连接成功后,事件循环监控Channel的可写事件,处理数据通信。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

 

0.简介

1.主动连接

2.连接成功

3.数据收发和关闭连接


0.简介

上一篇已讲述了服务端整个工作流程,本篇大致分析一下客户端。客户端相对简单,它只负责一个连接,成功连接到服务端后,即可进行正常的数据交互。

和服务端相对应的,客户端代码由TcpClient类发起,TcpClient里包含一个Connector(回想一下Acceptor),等连接建立后,也是依赖TcpConnection类做大部分事情。

1.主动连接

客户端连接过程如下:

来看一下Connector::connect(), 先创建一个非阻塞的套接口,调用系统函数去连接服务端:

int sockets::connect(int sockfd, const struct sockaddr* addr)
{
  return ::connect(sockfd, addr, static_cast<socklen_t>(sizeof(struct sockaddr_in6)));
}

如果正常返回(注意:此时连接还没有成功),则进入connecting() 函数,否则尝试重连或者直接关闭套接口。

void Connector::connect()
{
  int sockfd = sockets::createNonblockingOrDie(serverAddr_.family());
  int ret = sockets::connect(sockfd, serverAddr_.getSockAddr());
  int savedErrno = (ret == 0) ? 0 : errno;
  switch (savedErrno)
  {
    case 0:
    case EINPROGRESS:
    case EINTR:
    case EISCONN:
      connecting(sockfd);  //正在连接
      break;

    case EAGAIN:
    case EADDRINUSE:
    case EADDRNOTAVAIL:
    case ECONNREFUSED:
    case ENETUNREACH:
      retry(sockfd); // 重连
      break;
    ...
    ...
}

 connecting() 主要是创建一个Channel(上一篇已提到Channel类的作用),设置好回调函数(write callback, error callback), 然后关注channel的可写事件(对于非阻塞套接口,当连接成功,套接口变为可写,当连接失败,套接口变为可读可写)。

void Connector::connecting(int sockfd)
{
  setState(kConnecting);
  assert(!channel_);
  channel_.reset(new Channel(loop_, sockfd));
  channel_->setWriteCallback(
      boost::bind(&Connector::handleWrite, this)); // FIXME: unsafe
  channel_->setErrorCallback(
      boost::bind(&Connector::handleError, this)); // FIXME: unsafe

  // channel_->tie(shared_from_this()); is not working,
  // as channel_ is not managed by shared_ptr
  channel_->enableWriting();
}

以上操作执行之后,客户代码一般会调用EventLoop::loop()用于监控channel的可写事件。 

2.连接成功

当channel变为可写时,执行Connector::handleWrite() 回调函数:

void Connector::handleWrite()
{
  LOG_TRACE << "Connector::handleWrite " << state_;

  if (state_ == kConnecting)
  {
    int sockfd = removeAndResetChannel(); // 当前channel已经没有利用价值了,delete it
    // 套接口变成可写状态,不代表连接成功(也有可能是失败),所以还要再检查一次
    int err = sockets::getSocketError(sockfd);
    if (err)
    {
      LOG_WARN << "Connector::handleWrite - SO_ERROR = "
               << err << " " << strerror_tl(err);
      retry(sockfd);
    }
    else if (sockets::isSelfConnect(sockfd))
    {
      LOG_WARN << "Connector::handleWrite - Self connect";
      retry(sockfd);
    }
    else
    {
      setState(kConnected);
      if (connect_)
      {
        // 到这里,说明连接真的成功了,调用TcpClient::newConnection 创建TcpConnection类对象
        newConnectionCallback_(sockfd);
      }
      else
      {
        sockets::close(sockfd);
      }
    }
  }
  else
  {
    // what happened?
    assert(state_ == kDisconnected);
  }
}

 最后再看一下TcpClient::newConnection()

void TcpClient::newConnection(int sockfd)
{
  loop_->assertInLoopThread();
  InetAddress peerAddr(sockets::getPeerAddr(sockfd));
  char buf[32];
  snprintf(buf, sizeof buf, ":%s#%d", peerAddr.toIpPort().c_str(), nextConnId_);
  ++nextConnId_;
  string connName = name_ + buf;

  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(loop_,
                                          connName,
                                          sockfd,
                                          localAddr,
                                          peerAddr));
  // 设置回调函数
  conn->setConnectionCallback(connectionCallback_);
  conn->setMessageCallback(messageCallback_);
  conn->setWriteCompleteCallback(writeCompleteCallback_);
  conn->setCloseCallback(
      boost::bind(&TcpClient::removeConnection, this, _1)); // FIXME: unsafe
  {
    MutexLockGuard lock(mutex_);
    connection_ = conn;
  }
  
  conn->connectEstablished();  // 定义如下
}

void TcpConnection::connectEstablished()
{
  loop_->assertInLoopThread();
  assert(state_ == kConnecting);
  setState(kConnected);
  channel_->tie(shared_from_this());
  channel_->enableReading();    // 监听可读事件

  connectionCallback_(shared_from_this()); // 告诉用户连接成功了。
}

回忆一下TcpServer::newConnection() 最后一句:

ioLoop->runInLoop(boost::bind(&TcpConnection::connectEstablished, conn));

服务端会把connectEstablished()放到Tcpconnection所在的线程里执行,客户端由于对象都在主线程,所以可直接调用。

3.数据收发和关闭连接

由于都是使用了同一个类TcpConnection, 所以收发过程/关闭连接和服务端完全相同,不再详述,可参考

https://blog.youkuaiyun.com/mr_cai2009/article/details/81189332#3.%20%E6%95%B0%E6%8D%AE%E6%94%B6%E5%8F%91

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值