muduo库的Connector以及TcpClient的使用


一:Connector

    Connector可以说是muduo库的连接器,负责客户端向服务器发起连接。实际上说白了就是封装了socket的connect操作。

    

Connector类的成员如下:

class Connector : boost::noncopyable,
                  public boost::enable_shared_from_this<Connector>
{
 public:
  typedef boost::function<void (int sockfd)> NewConnectionCallback;

  Connector(EventLoop* loop, const InetAddress& serverAddr);
  ~Connector();

  void setNewConnectionCallback(const NewConnectionCallback& cb)
  { newConnectionCallback_ = cb; }

  void start();  // can be called in any thread
  void restart();  // must be called in loop thread
  void stop();  // can be called in any thread

  const InetAddress& serverAddress() const { return serverAddr_; }

 private:
  enum States { kDisconnected, kConnecting, kConnected };
  static const int kMaxRetryDelayMs = 30*1000;  //默认最大重连时间30000ms
  static const int kInitRetryDelayMs = 500;    //默认重连延迟时间500ms

  void setState(States s) { state_ = s; }
  void startInLoop();
  void stopInLoop();
  void connect();
  void connecting(int sockfd);
  void handleWrite();
  void handleError();
  void retry(int sockfd);
  int removeAndResetChannel();
  void resetChannel();

  EventLoop* loop_;               //所属的EventLoop
  InetAddress serverAddr_;    //服务器端的地址
  bool connect_; // atomic          
  States state_;  // FIXME: use atomic variable
  boost::scoped_ptr<Channel> channel_;    //Connector所对应的Channel
  NewConnectionCallback newConnectionCallback_;   //连接成功回调函数
  int retryDelayMs_;    //重连延迟时间(单位ms)
};
构造函数是这样的:

Connector::Connector(EventLoop* loop, const InetAddress& serverAddr)
  : loop_(loop),
    serverAddr_(serverAddr),
    connect_(false),
    state_(kDisconnected),
    retryDelayMs_(kInitRetryDelayMs)   //初始化延时
{
  LOG_DEBUG << "ctor[" << this << "]";
}

构造函数初始化了I/O线程,服务器地址,并设置为未连接状态以及初始化了重连延时时间。注意,这里的重连是指发起连接失败重连。

Connector启动流程是这样的:

//发起连接
void Connector::start()
{
  connect_ = true;
  loop_->runInLoop(boost::bind(&Connector::startInLoop, this)); // FIXME: unsafe
}

实际上调用了:

void Connector::startInLoop()
{
  loop_->assertInLoopThread();
  assert(state_ == kDisconnected);
  if (connect_)   //调用前必须connect_为true,start()函数中会这么做
  {
    connect();  //连接具体实现
  }
  else
  {
    LOG_DEBUG << "do not connect";
  }
}

如果connect_为true,才真正开始连接,调用connect():

//连接实现
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:  //非阻塞套接字,未连接成功返回码是EINPROGRESS表示正在连接
    case EINTR:
    case EISCONN:   //连接成功
      connecting(sockfd);
      break;

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

    case EACCES:
    case EPERM:
    case EAFNOSUPPORT:
    case EALREADY:
    case EBADF:
    case EFAULT:
    case ENOTSOCK:
      LOG_SYSERR << "connect error in Connector::startInLoop " << savedErrno;
      sockets::close(sockfd);   //这几种情况不能重连,
      break;

    default:
      LOG_SYSERR << "Unexpected error in Connector::startInLoop " << savedErrno;
      sockets::close(sockfd);
      break;
  }
}

这个函数虽然很长,但实际上就是设置套接字为非阻塞,然后底层调用socket的conenct()函数,进而判断errno采取相应的操作。

操作主要有三种情况,下面分三点讨论:

1.errno为EINPROGRESS、EINTR、EISCONN

    说明连接成功,调用connecting()函数,对成功的连接进行处理:

//如果连接成功
void Connector::connecting(int sockfd)
{
  setState(kConnecting);
  assert(!channel_);
  //Channel与sockfd关联
  channel_.reset(new Channel(loop_, sockfd));
  //设置可写回调函数,这时候如果socket没有错误,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();
}

总的来说,连接成功就是更改连接状态+设置各种回调函数+加入poller关注可写事件。

2.errno为 EAGAIN等错误码

    说明连接暂时失败,但是仍可能成功,需要重连。调用retry()函数:

//重连函数,采用back-off策略重连,也就是退避策略
//也就是重连时间逐渐延长,0.5s,1s,2s,...一直到30s
void Connector::retry(int sockfd)
{
  sockets::close(sockfd);   //先关闭连接
  setState(kDisconnected);  
  if (connect_)
  {
    LOG_INFO << "Connector::retry - Retry connecting to " << serverAddr_.toIpPort()
             << " in " << retryDelayMs_ << " milliseconds. ";
    //隔一段时间后重连,重新启用startInLoop
    loop_->runAfter(retryDelayMs_/1000.0,
                    boost::bind(&Connector::startInLoop, shared_from_this()));
    //间隔时间2倍增长
    retryDelayMs_ = std::min(retryDelayMs_ * 2, kMaxRetryDelayMs);
  }
  else   //超出最大重连时间后,输出连接失败
  {
    LOG_DEBUG << "do not connect";
  }
}
3.彻底失败,返回errno为EACCES等错误码

    这种情况只能关掉sockfd,因为再怎么试也成功不了的。

 sockets::close(sockfd);

断开连接函数就不剖析了,相对比较简单,接下来看TcpClient类。

二:TcpClient

Tcpclient主要有下列成员:

 EventLoop* loop_;
  ConnectorPtr connector_; // avoid revealing Connector
  const string name_;
  ConnectionCallback connectionCallback_;
  MessageCallback messageCallback_;
  WriteCompleteCallback writeCompleteCallback_;
  bool retry_;   // atomic   //是否重连,是指建立的连接成功后又断开是否重连。而Connector的重连是一直不成功是否重试的意思
  bool connect_; // atomic
  // always in loop thread
  int nextConnId_;  //name_+nextConnid_用于标识一个连接
  mutable MutexLock mutex_;
  TcpConnectionPtr connection_; // @GuardedBy mutex_ //Connector连接成功后,得到一个TcpConnection

需要注意的是,这里的重连是连接成功后又断开的重连,而不是Connector类的连不上一直尝试的重连。

构造函数:

TcpClient::TcpClient(EventLoop* loop,
                     const InetAddress& serverAddr,
                     const string& nameArg)
  : loop_(CHECK_NOTNULL(loop)),
    connector_(new Connector(loop, serverAddr)),
    name_(nameArg),
    connectionCallback_(defaultConnectionCallback),
    messageCallback_(defaultMessageCallback),
    retry_(false),
    connect_(true),
    nextConnId_(1)
{
  //一旦连接建立连接,回调newConnection
  connector_->setNewConnectionCallback(
      boost::bind(&TcpClient::newConnection, this, _1));
}
构造函数创建了一个Connector,毕竟是TcpClient嘛,需要一个东西来发起连接。并且注册连接成功的回调函数。

它的使用方法,先connect:

void TcpClient::connect()
{
  connect_ = true;
  connector_->start();
}

底层就是调用我们上文中分析的Connector的一系列连接机制,向服务端发起连接。

连接成功后,就会调用自己的成员函数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
  //创建一个TcpConnection对象,智能指针。
  //根据Connector中的handleWrite()函数,连接建立后会把sockfd从poller中移除,以后不会再关注可写事件了
  //否则会出现busy loop,因为已连接套接字一直处于可写状态
  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;   //保存TcpConnection
  }
  conn->connectEstablished();  //这里会关注可读事件,并且回调connectionCallback_,
}
该函数创建一个堆上局部TcpConnection对象,并用TcpClient的智能指针connection_保存起来,这样本函数中conn即便析构掉,connection_依然维护该连接。

然后设置各种回调函数。由于为了避免busy loop,在Connector中一旦连接成功,我们取消关注sockfd的可写事件。并且本函数使用conn->connectEstablished()内部会关注可读事件:

void TcpConnection::connectEstablished()
{
  loop_->assertInLoopThread();   //断言处于loop线程
  assert(state_ == kConnecting);   //断言处于未连接状态
  setState(kConnected);   //将状态设置为已连接

  channel_->tie(shared_from_this());   //将自身这个TcpConnection对象提升,由于是智能指针,所以不能直接用this
  //shared_from_this()之后引用计数+1,为3,但是shared_from_this()是临时对象,析构后又会减一,
  //而tie是weak_ptr并不会改变引用计数,所以该函数执行完之后引用计数不会更改
  
  channel_->enableReading();   //一旦连接成功就关注它的可读事件,加入到Poller中关注

  connectionCallback_(shared_from_this());
}


下面是连接断开的函数:

//连接断开
void TcpClient::removeConnection(const TcpConnectionPtr& conn)
{
  loop_->assertInLoopThread();
  assert(loop_ == conn->getLoop());

  {
    MutexLockGuard lock(mutex_);
    assert(connection_ == conn);
    connection_.reset();   //重置
  }

  //I/O线程中销毁
  loop_->queueInLoop(boost::bind(&TcpConnection::connectDestroyed, conn));
  if (retry_ && connect_)  //是否发起重连
  {
    LOG_INFO << "TcpClient::connect[" << name_ << "] - Reconnecting to "
             << connector_->serverAddress().toIpPort();
    //这里的重连是连接成功后断开的重连,所以实际上是重启
    connector_->restart();
  }
}

基本就这些了。



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Muduo是一个C++网络,用于编写多线程的TCP服务器和客户端。它提供了一些连接器(connector)类,用于建立和管理TCP连接。 Muduo的连接器类主要有两个:TcpClient和TcpServer。TcpClient用于作为TCP客户端,TcpServer用于作为TCP服务器。 TcpClient类提供了一些方法来建立和管理TCP连接。例如,可以使用connect()方法来连接到服务器,使用disconnect()方法来断开连接。还可以使用send()方法发送数据,使用setConnectionCallback()方法设置连接回调函数,以便在连接建立或断开时执行相应的操作。 TcpServer类用于创建TCP服务器。它提供了一些方法来管理连接。例如,可以使用start()方法启动服务器,使用setConnectionCallback()方法设置连接回调函数,以便在有新连接建立或连接断开时执行相应的操作。 以下是一个使用Muduo连接器的示例: ```cpp #include <muduo/net/TcpClient.h> #include <muduo/net/EventLoop.h> #include <muduo/net/InetAddress.h> using namespace muduo; using namespace muduo::net; void onConnection(const TcpConnectionPtr& conn) { if (conn->connected()) { printf("Connected to server\n"); // 连接建立后的操作 } else { printf("Disconnected from server\n"); // 连接断开后的操作 } } int main() { EventLoop loop; InetAddress serverAddr("127.0.0.1", 8888); TcpClient client(&loop, serverAddr, "TcpClient"); client.setConnectionCallback(onConnection); client.connect(); loop.loop(); return 0; } ``` 这个示例演示了如何使用TcpClient类连接到服务器,并在连接建立或断开时执行相应的操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值