muduo::Connector、TcpClient分析

来源:互联网 发布:为人谋而不忠乎的读音 编辑:程序博客网 时间:2024/05/17 02:16

  • 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;}
0 0