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 等工具包,还是很复杂… 后面好好学吧,学,学无止境;