muduo_net程式碼剖析之TcpClient
阿新 • • 發佈:2018-12-05
有了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發來的資料,並列印