版权声明:guojawee https://blog.csdn.net/weixin_36750623/article/details/84674215
有了Connector,TcpClient的实现就不难了,它的代码与TcpServer甚至有几分相似,只不过TcpClient只管理一个TcpConnection。先谈几个要点:
- TcpClient具备TcpConnection断开之后重新连接的功能,加上Connector具备反复尝试连接的功能,因此客户端和服务器的启动顺序无关紧要。可以先启动客户端,一旦服务器启动,半分钟之内即可恢复连接(由Connector::kMaxRetryDelayMs常数控制);再客户端运行期间服务器可以重启,客户端也会自动重连。
- 连接断开后初次重试的延迟时间是随机的,比方说服务器崩溃,它所有的客户端连接同时断开,然后0.5s之后同时再次发起连接,这样既可能造成SYN丢包,也可能给服务器带来短期大负载,影响其服务质量。因此每个TcpClient应该等待一段随机的时间(0.5~2s),再充实,避免拥塞。
- 发起连接的时候如果发生TCP SYN丢包,那么系统默认的重试间隔是3s,这期间不会返回错误码,而且这个间隔似乎不容易修改。如果需要缩短间隔,可以再用一个定时器,在0.5s或1s之后发起另一个链接。如果有需求的话,这个功能可以做到Connector中
TcpClient中的成员函数有:
EventLoop* loop_;
ConnectorPtr connector_; //用于主动发起连接
const string name_;
ConnectionCallback connectionCallback_; //连接建立回调函数
MessageCallback messageCallback_; //消息到来回调函数
WriteCompleteCallback writeCompleteCallback_;//数据发送完毕回调函数
bool retry_; // 重连,是指连接建立成功之后又断开的时候是否重连
bool connect_; // atomic
int nextConnId_; //name_+nextConnId_用于标识一个连接
mutable MutexLock mutex_;
//connector_连接成功以后,得到一个TcpConnectionPtr
TcpConnectionPtr connection_;
重要的成员函数是connector_(用于发起连接)、connection_(当连接成功建立后,创建TcpConnection对象用于通信)
- 构造函数
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(
std::bind(&TcpClient::newConnection, this, _1));
// FIXME setConnectFailedCallback
LOG_INFO << "TcpClient::TcpClient[" << name_
<< "] - connector " << get_pointer(connector_);
}
连接成功后,就会调用自己的成员函数TcpClient::newConnection()函数
void TcpClient::newConnection(int sockfd)
{
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));
//创建一个堆上局部TcpConnection对象,并用TcpClient的智能指针connection_保存起来
TcpConnectionPtr conn(new TcpConnection(loop_,
connName,
sockfd,
localAddr,
peerAddr));
//设置各种回调函数
conn->setConnectionCallback(connectionCallback_); //连接建立
conn->setMessageCallback(messageCallback_); //可读
conn->setWriteCompleteCallback(writeCompleteCallback_);//可写
conn->setCloseCallback(
std::bind(&TcpClient::removeConnection, this, _1)); // FIXME: unsafe
{
MutexLockGuard lock(mutex_);
connection_ = conn;
}
//使用conn->connectEstablished()内部会关注可读事件
conn->connectEstablished();
}
void TcpConnection::connectEstablished()
{
loop_->assertInLoopThread(); //断言处于loop线程
assert(state_ == kConnecting); //断言处于未连接状态
setState(kConnected); //将状态设置为已连接
channel_->tie(shared_from_this()); //将自身这个TcpConnection对象提升,由于是智能指针,所以不能直接用this
//shared_from_this()之后引用计数+1,为3,但是shared_from_this()是临时对象,析构后又会减一,
//而tie是weak_ptr并不会改变引用计数,所以该函数执行完之后引用计数不会更改
channel_->enableReading(); //一旦连接成功就关注它的可读事件,加入到Poller中关注
//回调conn->setConnectionCallback(connectionCallback_)
connectionCallback_(shared_from_this());
}
下面是连接断开的函数:
void TcpClient::removeConnection(const TcpConnectionPtr& conn)
{
loop_->assertInLoopThread();
assert(loop_ == conn->getLoop());
{
MutexLockGuard lock(mutex_);
assert(connection_ == conn);
connection_.reset(); //重置
}
//I/O线程中销毁
loop_->queueInLoop(boost::bind(&TcpConnection::connectDestroyed, conn));
if (retry_ && connect_) //是否发起重连
{
LOG_INFO << "TcpClient::connect[" << name_ << "] - Reconnecting to "
<< connector_->serverAddress().toIpPort();
//这里的重连是连接成功后断开的重连,所以实际上是重启
connector_->restart();
}
}
示例代码
客户端程序的功能:
1.从stdin接受输入,发送给连接的server
2.从网络接收server发来的数据,并打印