(十五)TcpClient

TcpClient 和 TcpServer 实现相似,都有newConnection 建立连接,removeConnection 断开连接,但是TcpClient 只管理一个TcpConnection 的生命周期,即主动连接时(newConnection)产生的;

class TcpClient {
	// ...
 private:
    void newConnection(int sockfd);
    void removeConnection(const TcpConnectionPtr &conn);

    EventLoop *_loop;
    ConnectorPtr _connector;
    ConnectionCallback _connectionCallback;
    MessageCallback _messageCallback;
    WriteCompleteCallback _writeCompleteCallback;
    bool _retry;
    bool _connect;
    int _nextConnId;
    mutable MutexLock _mutex;
    TcpConnectionPtr _connection;
};

TcpClient 在Connector 基础上提供了一层封装,并在构造中设置好Connector newConnection;

TcpClient::TcpClient(EventLoop *loop, const InetAddress &serverAddr)
    : _loop(CHECK_NOTNULL(loop)), _connector(new Connector(loop, serverAddr)),
      _retry(false), _connect(false), _nextConnId(1) {
    _connector->setNewConnectionCallback(
        std::bind(&TcpClient::newConnection, this, _1));
    // FIXME setConnectFailedCallback
    LOG_INFO << "TcpClient::TcpClient[" << this << "] - connector "
             << _connector.get();
}

值得注意的是:
_nextConnId 表示下一次连接编号(触发newConection 会导致其增加)
_mutex 保障线程间接触TcpClient::TcpConnectionPtr _connection 的线程安全

_mutex 在TcpClient.cc 出现过四次

void TcpClient::removeConnection(const TcpConnectionPtr &conn) {
    _loop->assertInLoopThread();
    assert(_loop == conn->getLoop());
    {
        MutexLockGuard lock(_mutex);
        assert(_connection == conn);
        _connection.reset();
    }
	// ...
}
TcpClient::~TcpClient() {
	// ...
    TcpConnectionPtr conn;
    {
        MutexLockGuard lock(_mutex);
        conn = _connection;
    }
	// ...
}
void TcpClient::disconnect() {
    _connect = false;
    {
        MutexLockGuard lock(_mutex);
        if (_connector) {
            _connection->shutdown();
        }
    }
}
void TcpClient::newConnection(int sockfd) {
    _loop->assertInLoopThread();
	// ...
    {
        MutexLockGuard lock(_mutex);
        _connection = conn;
    }
    conn->connectEstablished();
}

removeConnection 和 newConnection 保证只在IO 线程触发,而TcpClient 对外接口disconnect 无此需求;

跨线程调用TcpClient disconnect,TcpConnectionPtr _connection 此时不可被占用(假如TcpConnection 还在占线,禁止连接会导致未预期的结果),因此_mutex 在_connection 操作时需要先lock 一下;(client 只有一个connector,在新连接和旧连接的处理选择中一定要保证生命周期正常结束)

有趣的是类似TcpClient 的TcpServer 类中并没有TcpConnectionPtr,在其newConnection 中TcpConnectionPtr 连接即用即产生,由Map 保存并延长生命周期(server 1:n,client 1:1),因此在TcpServer 中每个连接都是新连接,在操作时不会受到跨线程干扰;

析构中的处理也要保证连接正常结束,在muduo 源码更直观:

TcpClient::~TcpClient()
{
  LOG_INFO << "TcpClient::~TcpClient[" << name_
           << "] - connector " << get_pointer(connector_);
  TcpConnectionPtr conn;
  bool unique = false;
  {
    MutexLockGuard lock(mutex_);
    unique = connection_.unique();
    conn = connection_;
  }
   //....
}

conn 连接是否为unique(还有处理没有结束),会决定Client 析构中关不关闭连接;

if (conn)
  {
    assert(loop_ == conn->getLoop());
    // FIXME: not 100% safe, if we are in different thread
    CloseCallback cb = std::bind(&detail::removeConnection, loop_, _1);
    loop_->runInLoop(
        std::bind(&TcpConnection::setCloseCallback, conn, cb));
    if (unique)
    {
      conn->forceClose();
    }
  }
  else
  {
    connector_->stop();
    // FIXME: HACK
    loop_->runAfter(1, std::bind(&detail::removeConnector, connector_));
  }

else 分支处理的是没有触发newConnection,TcpClient 安稳关闭,而这儿runAfter 好有灵性…

因为需要关闭connector 关系到 _timerId(retry 实现依据_timerId),而stop 本质就是将cancel _timerId 放到Loop 中等待IO 线程中注销(不一定是即时的),在此过程中TcpClient 会析构,_connector 作为shared_ptr 引用会减少,runAfter 相当于无条件延长_connector 生命周期1s,相信在1s 内其IO 线程的Loop 会处理完 cancel 事件… 怪不得有FIXME: Hack

这儿安全处理应该添加判断retry 分支,再在有retry 设定的分支中等待Loop 完成IO 注销;
关键怎么等待?…
如果就是在IO 线程cancel 则没有问题,Loop 会立刻处理,但如不在,runInLoop 变成 queueInLoop cancel 则不会即时处理;
不知道,😋!应该要用到线程通信中的消息队列吧,那么整个EventLoop 体系都要修改了…

自己写 removeConnection,两个都写在类内了,成类中重载成员函数,导致bind 时充分体验到了name mangling 的好处了

 std::bind(
            (void(TcpClient::*)(EventLoop *, const TcpConnectionPtr &)) &
                TcpClient::removeConnection,
            this, _loop, _1);
            
std::bind((void(TcpClient::*)(const TcpConnectionPtr &)) &
                       TcpClient::removeConnection,
                   this, _1)

但是下面这俩不能放在类内

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

void TcpClient::removeConnector(const ConnectorPtr &connector) {
    // connector->
}

因为生命周期受到TcpClient 限制了;

Muduo 也考虑了TcpClient 比Loop 生命周期短的情况,测试要使用更复杂的工具;

当TcpClient TcpConnectionPtr 连接结束后会触发

void TcpClient::removeConnection(const TcpConnectionPtr &conn) {
    _loop->assertInLoopThread();
    assert(_loop == conn->getLoop());
    {
        MutexLockGuard lock(_mutex);
        assert(_connection == conn);
        _connection.reset();
    }

    _loop->queueInLoop(std::bind(&TcpConnection::connectDestroyed, conn));
    if (_retry && _connect) {
        LOG_INFO << "TcpClient::connect[" << this << "] - Reconnecting to "
                 << _connector->serverAddress().toHostPort();
        _connector->restart();
    }
}

早点释放_connection 智能指针,并删除连接,考虑重启;

很多地方都是FIXME:unsafe 的标注,我想应该是生命周期的问题,有的类会比依赖类提前结束,比如TcpServer 和 TcpConnection,很多bind 传递this 的地方都有这样的风险,好难写啊…

网络库初步成形了,下面扼要看看epoll 实现原理,也算稍微理解Muduo 了;后面Muduo 增加了Http 服务和 Rpc 服务,运用了protobuf 等工具包,还是很复杂… 后面好好学吧,学,学无止境;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值