muduo::Connector、TcpClient分析

Connector

Connector用来发起连接。

在非堵塞网络中,主动发起连接比被动接收连接更为复杂,由于要考虑错误处理,还要考虑重试。
主要难点在于
1、socket是一次性的,一旦出错无法恢复。仅仅能关闭重来。使用新的fd后,用新的channel。
2、错误代码与acce(2)不同。

及时是socket可写。也不意味着已经成功建立连接。还须要用getsockopt(sockfd, SOL_SOCKET, SO_ERROR, ……)再次确认。
3、重试的间隔时间应该逐渐延长,直至back-off。重试使用了EventLoop::runAfter,防止Connector在定时器到时前析构,在Connector的析构函数中要注销定时器。
4、要防止自连接发生。

对于第2点。这里解释一下。非堵塞的socket调用connect后会马上返回,这时三次握手还在进行。这时能够用poll/epoll来检查socket。


当连接成功时。socket变为可写。
当连接失败时,socket变为可读可写。
因此还须要再次确认一下。还有其它方法再次确认:
1、调用getpeername,假设调用失败,返回ENOTCONN,表示连接失败。
2、调用read。长度參数为0。假设read失败。表示connect失败。
3、再调用connect一次,其应该失败。假设错误是EISCONN,表示套接字已建立并且连接成功。

Connector.h

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;//最大重试延迟
  static const int kInitRetryDelayMs = 500;//初始化重试延迟

  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_;//server地址
  bool connect_; // atomic
  States state_;  // FIXME: use atomic variable
  boost::scoped_ptr<Channel> channel_;
  NewConnectionCallback newConnectionCallback_;
  int retryDelayMs_;
};

Connector.cc

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

Connector::~Connector()
{
  LOG_DEBUG << "dtor[" << this << "]";
  assert(!channel_);
}

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();//開始建立连接
  }
  else
  {
    LOG_DEBUG << "do not connect";
  }
}

void Connector::stop()
{
  connect_ = false;
  loop_->queueInLoop(boost::bind(&Connector::stopInLoop, this)); // FIXME: unsafe
  // FIXME: cancel timer
}

void Connector::stopInLoop()
{
  loop_->assertInLoopThread();
  if (state_ == kConnecting)
  {
    setState(kDisconnected);
    int sockfd = removeAndResetChannel();
    retry(sockfd);
  }
}

void Connector::connect()//建立连接
{
  int sockfd = sockets::createNonblockingOrDie();//创建sockfd
  int ret = sockets::connect(sockfd, serverAddr_.getSockAddrInet());//连接服务器
  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; 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); // connectErrorCallback_(); break; } } void Connector::restart()//重新启动 { loop_->assertInLoopThread(); setState(kDisconnected); retryDelayMs_ = kInitRetryDelayMs; connect_ = true; startInLoop(); } void Connector::connecting(int sockfd) { setState(kConnecting); assert(!channel_);//这里设置channel。由于有了sockfd后才干够设置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(); } int Connector::removeAndResetChannel() { channel_->disableAll(); channel_->remove(); int sockfd = channel_->fd(); // Can't reset channel_ here, because we are inside Channel::handleEvent loop_->queueInLoop(boost::bind(&Connector::resetChannel, this)); // FIXME: unsafe return sockfd; } void Connector::resetChannel()//reset后channel_为空 { channel_.reset(); } void Connector::handleWrite()//可写不一定表示已经建立连接 { LOG_TRACE << "Connector::handleWrite " << state_; if (state_ == kConnecting) { int sockfd = removeAndResetChannel();//移除channel。Connector中的channel仅仅管理建立连接阶段。连接建立后,交给TcoConnection管理。 int err = sockets::getSocketError(sockfd);//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_) { newConnectionCallback_(sockfd); } else { sockets::close(sockfd); } } } else { // what happened?

assert(state_ == kDisconnected); } } void Connector::handleError() { LOG_ERROR << "Connector::handleError state=" << state_; if (state_ == kConnecting) { int sockfd = removeAndResetChannel(); int err = sockets::getSocketError(sockfd); LOG_TRACE << "SO_ERROR = " << err << " " << strerror_tl(err); retry(sockfd); } } void Connector::retry(int sockfd)//又一次尝试连接 { sockets::close(sockfd); setState(kDisconnected); if (connect_) { LOG_INFO << "Connector::retry - Retry connecting to " << serverAddr_.toIpPort() << " in " << retryDelayMs_ << " milliseconds. "; loop_->runAfter(retryDelayMs_/1000.0, boost::bind(&Connector::startInLoop, shared_from_this())); retryDelayMs_ = std::min(retryDelayMs_ * 2, kMaxRetryDelayMs);//延迟加倍。但不超过最大延迟 } else { LOG_DEBUG << "do not connect"; } }

TcpClient

Connector类补单独使用。它封装在类TcpClient中。一个Connector相应一个TcpClient,Connector用来建立连接,建立成功后把控制交给TcpConnection。因此TcpClient中也封装了一个TcpConnection。

class Connector;
typedef boost::shared_ptr<Connector> ConnectorPtr;

class TcpClient : boost::noncopyable
{
 public:
  // TcpClient(EventLoop* loop);
  // TcpClient(EventLoop* loop, const string& host, uint16_t port);
  TcpClient(EventLoop* loop,
            const InetAddress& serverAddr,
            const string& nameArg);
  ~TcpClient();  // force out-line dtor, for scoped_ptr members.

  void connect();
  void disconnect();
  void stop();

  TcpConnectionPtr connection() const
  {
    MutexLockGuard lock(mutex_);
    return connection_;
  }

  EventLoop* getLoop() const { return loop_; }
  bool retry() const;
  void enableRetry() { retry_ = true; }

  const string& name() const
  { return name_; }

  /// Set connection callback.
  /// Not thread safe.
  void setConnectionCallback(const ConnectionCallback& cb)
  { connectionCallback_ = cb; }

  /// Set message callback.
  /// Not thread safe.
  void setMessageCallback(const MessageCallback& cb)
  { messageCallback_ = cb; }

  /// Set write complete callback.
  /// Not thread safe.
  void setWriteCompleteCallback(const WriteCompleteCallback& cb)
  { writeCompleteCallback_ = cb; }

#ifdef __GXX_EXPERIMENTAL_CXX0X__
  void setConnectionCallback(ConnectionCallback&& cb)
  { connectionCallback_ = std::move(cb); }
  void setMessageCallback(MessageCallback&& cb)
  { messageCallback_ = std::move(cb); }
  void setWriteCompleteCallback(WriteCompleteCallback&& cb)
  { writeCompleteCallback_ = std::move(cb); }
#endif

 private:
  /// Not thread safe, but in loop
  void newConnection(int sockfd);
  /// Not thread safe, but in loop
  void removeConnection(const TcpConnectionPtr& conn);

  EventLoop* loop_;
  ConnectorPtr connector_; // avoid revealing Connector
  const string name_;
  ConnectionCallback connectionCallback_;
  MessageCallback messageCallback_;
  WriteCompleteCallback writeCompleteCallback_;
  bool retry_;   // atomic
  bool connect_; // atomic
  // always in loop thread
  int nextConnId_;
  mutable MutexLock mutex_;
  TcpConnectionPtr connection_; // @GuardedBy mutex_
};

TcpClient.cc

void removeConnection(EventLoop* loop, const TcpConnectionPtr& conn)
{
  loop->queueInLoop(boost::bind(&TcpConnection::connectDestroyed, conn));
}

void removeConnector(const ConnectorPtr& connector)
{
  //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)
{
  connector_->setNewConnectionCallback(
      boost::bind(&TcpClient::newConnection, this, _1));
  // FIXME setConnectFailedCallback
  LOG_INFO << "TcpClient::TcpClient[" << name_
           << "] - connector " << get_pointer(connector_);
}

TcpClient::~TcpClient()
{
  LOG_INFO << "TcpClient::~TcpClient[" << name_
           << "] - connector " << get_pointer(connector_);
  TcpConnectionPtr conn;
  bool unique = false;
  {
    MutexLockGuard lock(mutex_);
    unique = connection_.unique();
    conn = connection_;
  }
  if (conn)
  {
    assert(loop_ == conn->getLoop());
    // FIXME: not 100% safe, if we are in different thread
    CloseCallback cb = boost::bind(&detail::removeConnection, loop_, _1);
    loop_->runInLoop(
        boost::bind(&TcpConnection::setCloseCallback, conn, cb));
    if (unique)
    {
      conn->forceClose();
    }
  }
  else
  {
    connector_->stop();
    // FIXME: HACK
    loop_->runAfter(1, boost::bind(&detail::removeConnector, connector_));
  }
}

void TcpClient::connect()
{
  // FIXME: check state
  LOG_INFO << "TcpClient::connect[" << name_ << "] - connecting to "
           << connector_->serverAddress().toIpPort();
  connect_ = true;
  connector_->start();//開始连接
}

void TcpClient::disconnect()
{
  connect_ = false;

  {
    MutexLockGuard lock(mutex_);
    if (connection_)
    {
      connection_->shutdown();
    }
  }
}

void TcpClient::stop()
{
  connect_ = false;
  connector_->stop();
}

void TcpClient::newConnection(int sockfd)//新连接创建后。用TcpConnection接管连接
{
  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_,//把新连接创建为TcpConnection
                                          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 TcpClient::removeConnection(const TcpConnectionPtr& conn)
{
  loop_->assertInLoopThread();
  assert(loop_ == conn->getLoop());

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

  loop_->queueInLoop(boost::bind(&TcpConnection::connectDestroyed, conn));
  if (retry_ && connect_)
  {
    LOG_INFO << "TcpClient::connect[" << name_ << "] - Reconnecting to "
             << connector_->serverAddress().toIpPort();
    connector_->restart();
  }
}

能够使用TcpClient写一个Echo客户端。用一个channel监听键盘的输入事件,有了输入就发送。
echoClient.h

#include <muduo/net/TcpClient.h>
#include <muduo/net/Channel.h>
#include <muduo/net/TcpConnection.h>
#include <muduo/base/Timestamp.h>
#include <muduo/net/EventLoop.h>

using namespace muduo;
using namespace muduo::net;

class EchoClient{
public:
    EchoClient(EventLoop* loop, const InetAddress& serverAddr, const string& nameArg);
    void start();
    void send();
private:
    void onConnection(const TcpConnectionPtr& conn);
    void onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp time);
    TcpClient client_;
    Channel channel_;
};

echoClient.cpp

#include "echoClient.h"
#include <muduo/net/Buffer.h>
#include <muduo/net/InetAddress.h>

#include <boost/bind.hpp>
#include <unistd.h>

#include <iostream>

EchoClient::EchoClient(EventLoop* loop,
               const InetAddress& serverAddr,
               const string& nameArg):
    client_(loop, serverAddr, nameArg),
    channel_(loop, STDIN_FILENO)      
{
    client_.setConnectionCallback(boost::bind(&EchoClient::onConnection, this, _1));
    client_.setMessageCallback(boost::bind(&EchoClient::onMessage, this, _1, _2, _3));
    channel_.enableReading();
    channel_.setReadCallback(boost::bind(&EchoClient::send, this));
}

void EchoClient::onConnection(const TcpConnectionPtr& conn)
{
    if(conn->connected())
    {
        std::cout<<"Connect to"<<conn->peerAddress().toIpPort()<<" successfully"<<std::endl;
    }
    else
        std::cout<<"Connect to"<<conn->peerAddress().toIpPort()<<" failed"<<std::endl;
}

void EchoClient::onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp time)
{
    std::cout<<"Receive :"<<buf->retrieveAllAsString()<<std::endl;
}
void EchoClient::send()
{
    string msg;
    std::cin>>msg;
    Buffer buf;
    buf.append(msg);
    client_.connection()->send(&buf);
}
void EchoClient::start()
{
    client_.connect();
}

main.cpp

#include "echoClient.h"

int main()
{
    EventLoop loop;
    InetAddress serverAddr("127.0.0.1", 8000);
    EchoClient client(&loop, serverAddr, "echoClient");
    client.start();
    loop.loop();
    return 0;
}
原文地址:https://www.cnblogs.com/slgkaifa/p/7211118.html