• 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;
    }
  • 相关阅读:
    Luogu P2633 Count on a tree
    Luogu P4011 孤岛营救问题
    Luogu P3157 [CQOI2011]动态逆序对
    SCOI2015 国旗计划
    AT2165 Median Pyramid Hard
    BZOJ2959 长跑
    SCOI2015 情报传递
    SDOI2011 染色
    SCOI2010 幸运数字
    SHOI2016 黑暗前的幻想乡
  • 原文地址:https://www.cnblogs.com/slgkaifa/p/7211118.html
Copyright © 2020-2023  润新知