一、類關係圖
首先看TcpConnection在整個Muduo結構中的關係,如下圖
- TcpConnection是整個網絡庫的核心,封裝一次Tcp連接,注意它不能發起連接。
- TcpServer和TcpClient都用到了TcpConnection。
二、源碼分析
TcpConnection.h
///
/// TCP connection, for both client and server usage.
///
/// This is an interface class, so don't expose too much details.
class TcpConnection : boost::noncopyable,
public boost::enable_shared_from_this<TcpConnection>
{
public:
/// Constructs a TcpConnection with a connected sockfd
///
/// User should not create this object.
TcpConnection(EventLoop* loop,
const string& name,
int sockfd,
const InetAddress& localAddr,
const InetAddress& peerAddr);
~TcpConnection();
EventLoop* getLoop() const { return loop_; } //獲取當前TcpConnection所在的EventLoop
const string& name() const { return name_; } //TcpConnection名稱
const InetAddress& localAddress() { return localAddr_; }//當前服務端地址
const InetAddress& peerAddress() { return peerAddr_; }//遠程連接客戶端地址
bool connected() const { return state_ == kConnected; }//檢測是否連接
//發送數據
// void send(string&& message); // C++11
void send(const void* message, size_t len);
void send(const StringPiece& message);
// void send(Buffer&& message); // C++11
void send(Buffer* message); // this one will swap data
//關閉,裏面有一定的處理邏輯
void shutdown(); // NOT thread safe, no simultaneous calling
//設置tcpNoDelay
void setTcpNoDelay(bool on);
//設置內容。這個內容可以是任何數據,主要是用着一個臨時存儲作用。
void setContext(const boost::any& context)
{ context_ = context; }
//獲取內容的引用(獲取當前內容,一般在回調中使用)
const boost::any& getContext() const
{ return context_; }
//獲取內容的地址(獲取當前內容,一般在回調中使用)
boost::any* getMutableContext()
{ return &context_; }
//設置連接回調。一般用於做什麼?
//a、連接的建立、連接的銷燬、產生關閉事件時會調用此回調,通知外部狀態。
void setConnectionCallback(const ConnectionCallback& cb)
{ connectionCallback_ = cb; }
//設置消息回調。一般接收到數據之後會回調此方法
void setMessageCallback(const MessageCallback& cb)
{ messageCallback_ = cb; }
//寫完成回調。
void setWriteCompleteCallback(const WriteCompleteCallback& cb)
{ writeCompleteCallback_ = cb; }
//設置高水位回調。
void setHighWaterMarkCallback(const HighWaterMarkCallback& cb, size_t highWaterMark)
{ highWaterMarkCallback_ = cb; highWaterMark_ = highWaterMark; }
//獲取輸入Buffer地址
Buffer* inputBuffer()
{ return &inputBuffer_; }
///關閉回調
/// Internal use only.
void setCloseCallback(const CloseCallback& cb)
{ closeCallback_ = cb; }
// called when TcpServer accepts a new connection
void connectEstablished(); // should be called only once
// called when TcpServer has removed me from its map
void connectDestroyed(); // should be called only once
private:
enum StateE { kDisconnected, kConnecting, kConnected, kDisconnecting };
void handleRead(Timestamp receiveTime);
void handleWrite();
void handleClose();
void handleError();
//void sendInLoop(string&& message);
void sendInLoop(const StringPiece& message);
void sendInLoop(const void* message, size_t len);
void shutdownInLoop();
void setState(StateE s) { state_ = s; }
EventLoop* loop_;
string name_;
StateE state_; // FIXME: use atomic variable
// we don't expose those classes to client.
boost::scoped_ptr<Socket> socket_; //連接Socket
boost::scoped_ptr<Channel> channel_;//通道
InetAddress localAddr_; //當前服務端地址
InetAddress peerAddr_; //當前連接客戶端地址
//回調函數
ConnectionCallback connectionCallback_;
MessageCallback messageCallback_;
WriteCompleteCallback writeCompleteCallback_;
HighWaterMarkCallback highWaterMarkCallback_;
CloseCallback closeCallback_;
size_t highWaterMark_;//高水位線
Buffer inputBuffer_; //輸入Buffer
Buffer outputBuffer_; // FIXME: use list<Buffer> as output buffer.
boost::any context_; //?
// FIXME: creationTime_, lastReceiveTime_
// bytesReceived_, bytesSent_
};
typedef boost::shared_ptr<TcpConnection> TcpConnectionPtr;
/默認連接回調。輸出連接狀態
void muduo::net::defaultConnectionCallback(const TcpConnectionPtr& conn)
{
LOG_TRACE << conn->localAddress().toIpPort() << " -> "
<< conn->peerAddress().toIpPort() << " is "
<< (conn->connected() ? "UP" : "DOWN");
}
//默認的有消息時執行的回調。默認取走所有數據
void muduo::net::defaultMessageCallback(const TcpConnectionPtr&,
Buffer* buf,
Timestamp)
{
buf->retrieveAll();
}
//TcpConnection創建時的值
//Eventloop、名稱、套接字、本地服務端地址、客戶端地址
//初始化的默認值
//狀態、創建一個Socket管理、創建一個通道
//本地地址、遠程客戶端地址
//高水位標誌。FIXME 這個標誌目前沒理解
//a、sockfd是已連接上的句柄。
//b、TcpConnection的名稱。
//c、連接狀態初始化正在連接中。
//d、封裝sockfd爲Socket。
//e、利用loop和sockfd,創建一個通道。
//f、本地地址+ 客戶端地址。
//g、設置默認高水位閥值
TcpConnection::TcpConnection(EventLoop* loop,
const string& nameArg,
int sockfd,
const InetAddress& localAddr,
const InetAddress& peerAddr)
: loop_(CHECK_NOTNULL(loop)),
name_(nameArg),
state_(kConnecting),
socket_(new Socket(sockfd)),
channel_(new Channel(loop, sockfd)),
localAddr_(localAddr),
peerAddr_(peerAddr),
highWaterMark_(64*1024*1024)
{
//設置讀回調,會傳一個參數
channel_->setReadCallback(
boost::bind(&TcpConnection::handleRead, this, _1));
//設置寫回調。
channel_->setWriteCallback(
boost::bind(&TcpConnection::handleWrite, this));
//設置關回調。
channel_->setCloseCallback(
boost::bind(&TcpConnection::handleClose, this));
//設置錯誤回調
channel_->setErrorCallback(
boost::bind(&TcpConnection::handleError, this));
LOG_DEBUG << "TcpConnection::ctor[" << name_ << "] at " << this
<< " fd=" << sockfd;
socket_->setKeepAlive(true); //開啓協議棧層心跳
}
//析構時就輸出下日誌,這裏沒任何資源的釋放,
//應該都是外部進行管理。FIXME 待確認。
TcpConnection::~TcpConnection()
{
LOG_DEBUG << "TcpConnection::dtor[" << name_ << "] at " << this
<< " fd=" << channel_->fd();
}
void TcpConnection::send(const void* data, size_t len)
{
//處於連接狀態才發送
if (state_ == kConnected)
{
if (loop_->isInLoopThread())
{
//如果是當前線程就直接發送
sendInLoop(data, len);
}
else
{
//如果Loop在別的線程中這放到loop待執行回調隊列執行。
//會涉及到數據拷貝
string message(static_cast<const char*>(data), len);
loop_->runInLoop(
boost::bind(&TcpConnection::sendInLoop,
this, // FIXME
message));
}
}
}
//功能同上,都是發送數據
void TcpConnection::send(const StringPiece& message)
{
if (state_ == kConnected)
{
if (loop_->isInLoopThread())
{
sendInLoop(message);
}
else
{
loop_->runInLoop(
boost::bind(&TcpConnection::sendInLoop,
this, // FIXME
message.as_string()));
//std::forward<string>(message)));
}
}
}
//這個send是發送所有數據,要注意效率問題
// FIXME efficiency!!!
void TcpConnection::send(Buffer* buf)
{
if (state_ == kConnected)
{
if (loop_->isInLoopThread())
{
sendInLoop(buf->peek(), buf->readableBytes());
buf->retrieveAll();
}
else
{
loop_->runInLoop(
boost::bind(&TcpConnection::sendInLoop,
this, // FIXME
buf->retrieveAllAsString()));
//std::forward<string>(message)));
}
}
}
void TcpConnection::sendInLoop(const StringPiece& message)
{
sendInLoop(message.data(), message.size());
}
//發送數據重點函數
void TcpConnection::sendInLoop(const void* data, size_t len)
{
loop_->assertInLoopThread();
ssize_t nwrote = 0; //已經發送數據長度
size_t remaining = len; //還剩要發送的
// if no thing in output queue, try writing directly
if (!channel_->isWriting() && outputBuffer_.readableBytes() == 0)
{
//如果通道沒在寫數據,同時輸出緩存是空的
//則直接往fd中寫數據,即發送
nwrote = sockets::write(channel_->fd(), data, len);
if (nwrote >= 0)
{
//發送數據 >= 0
remaining = len - nwrote;
if (remaining == 0 && writeCompleteCallback_)
{
//若數據一次性都發完了,同時也設置了寫完成回調。
//則調用下寫完成回調函數。
loop_->queueInLoop(boost::bind(writeCompleteCallback_, shared_from_this()));
}
}
else // nwrote < 0
{
//如果寫返回值小於0,表示寫出錯
//則把已寫數據大小設置爲0。
nwrote = 0;
if (errno != EWOULDBLOCK)
{
LOG_SYSERR << "TcpConnection::sendInLoop";
}
}
}
//a、不直接發送數據情況。
//b、數據未發送完的情況。
assert(remaining <= len);
if (remaining > 0)
{
LOG_TRACE << "I am going to write more data";
size_t oldLen = outputBuffer_.readableBytes();
if (oldLen + remaining >= highWaterMark_
&& oldLen < highWaterMark_
&& highWaterMarkCallback_)
{
//添加新的待發送數據之後,如果數據大小已超過設置的警戒線
//則回調下設置的高水平閥值回調函數,對現有的長度做出處理。
//高水平水位線的使用場景?
loop_->queueInLoop(boost::bind(highWaterMarkCallback_, shared_from_this(), oldLen + remaining));
}
//往outputBuffer後面添加數據。涉及到數據的拷貝
outputBuffer_.append(static_cast<const char*>(data)+nwrote, remaining);
if (!channel_->isWriting())
{
//將通道置成可寫狀態。這樣當通道活躍時,
//就好調用TcpConnection的可寫方法。
//對實時要求高的數據,這種處理方法可能有一定的延時。
channel_->enableWriting();
}
}
}
//關閉動作,如果狀態是連接,
//則要調用下關閉動作。
void TcpConnection::shutdown()
{
// FIXME: use compare and swap
if (state_ == kConnected)
{
setState(kDisconnecting);
// FIXME: shared_from_this()?
loop_->runInLoop(boost::bind(&TcpConnection::shutdownInLoop, this));
}
}
//a、通道不再寫數據,則直接關閉寫
//b、通道若處於寫數據狀態,則不做
// 處理,留給後面處理。
// 後面是指在handleWrite的時候,
// 如果發現狀態是斷開,則調用shutdownWrite
void TcpConnection::shutdownInLoop()
{
loop_->assertInLoopThread();
if (!channel_->isWriting())
{
// we are not writing
socket_->shutdownWrite();
}
}
//開啓TcpNoDelay狀態,禁用Nagle算法。
//開啓目的是避免連續發包出現延遲,
//這對編寫低延遲網絡服務很重要
void TcpConnection::setTcpNoDelay(bool on)
{
socket_->setTcpNoDelay(on);
}
//連接建立完成方法,
//當TcpServer accepts a new connection時,調用此方法
//a、設置下狀態
//b、通道tie下,並設置可讀
//c、調用下連接建立完成的回調函數
void TcpConnection::connectEstablished()
{
loop_->assertInLoopThread();
assert(state_ == kConnecting);
setState(kConnected);
channel_->tie(shared_from_this());
channel_->enableReading();
//輸出一些信息
connectionCallback_(shared_from_this());
}
//連接銷燬。當TcpServer將TcpConnection從
//map列表中清除時,會調用此方法。
//a、設置下狀態
//b、關閉通道
//c、調用下連接回調函數。
//d、移除通道。
void TcpConnection::connectDestroyed()
{
loop_->assertInLoopThread();
if (state_ == kConnected)
{
setState(kDisconnected);
channel_->disableAll();
connectionCallback_(shared_from_this());
}
channel_->remove(); //移除當前通道
}
//有可讀事件時.
void TcpConnection::handleRead(Timestamp receiveTime)
{
loop_->assertInLoopThread();
int savedErrno = 0;
//直接將數據讀到inputBuffer
ssize_t n = inputBuffer_.readFd(channel_->fd(), &savedErrno);
if (n > 0)
{
//a、讀取數據大於0,調用下回調
messageCallback_(shared_from_this(), &inputBuffer_, receiveTime);
}
else if (n == 0)
{
//b、等於0表示要socket關閉
handleClose();
}
else
{
//c、小於0表示有錯誤。
errno = savedErrno;
LOG_SYSERR << "TcpConnection::handleRead";
handleError();
}
}
//回調調用可寫函數
void TcpConnection::handleWrite()
{
loop_->assertInLoopThread();
if (channel_->isWriting())
{
//通道可寫才進入
//寫緩存裏所有數據
ssize_t n = sockets::write(channel_->fd(),
outputBuffer_.peek(),
outputBuffer_.readableBytes());
if (n > 0)
{
//發送了多少數據,設置Buffer索引,
//當外部調用TcpConnection::shutdown時也不直接關閉
//要等數據發送完了之後再關閉。
outputBuffer_.retrieve(n);
if (outputBuffer_.readableBytes() == 0)
{
//如果Buffer可讀數據爲0表示都已經發送完畢。
//關閉通道的寫狀態。
channel_->disableWriting();
if (writeCompleteCallback_)
{
//如果有寫完成回調函數,就調用下。
loop_->queueInLoop(boost::bind(writeCompleteCallback_, shared_from_this()));
}
//如果狀態已經是斷開中,
//則要關閉。FIXME_hqb 是哪裏設置的呢?
//TcpConnection::shutdown調用時會設置狀態爲kDisconnecting
if (state_ == kDisconnecting)
{
shutdownInLoop();
}
}
else
{
//未寫完,則繼續寫
LOG_TRACE << "I am going to write more data";
}
}
else
{
LOG_SYSERR << "TcpConnection::handleWrite";
// if (state_ == kDisconnecting)
// {
// shutdownInLoop();
// }
}
}
else
{
LOG_TRACE << "Connection is down, no more writing";
}
}
//連接關閉
//這裏fd不關閉,fd是外部傳入的
//當TcpConnection析構時,Sockets會析構
//由Sockets去關閉socket
void TcpConnection::handleClose()
{
loop_->assertInLoopThread();
LOG_TRACE << "TcpConnection::handleClose state = " << state_;
assert(state_ == kConnected || state_ == kDisconnecting);
// we don't close fd, leave it to dtor, so we can find leaks easily.
setState(kDisconnected);
channel_->disableAll();
TcpConnectionPtr guardThis(shared_from_this());
connectionCallback_(guardThis);
// must be the last line
closeCallback_(guardThis);
}
//輸出下錯誤日誌。
void TcpConnection::handleError()
{
int err = sockets::getSocketError(channel_->fd());
LOG_ERROR << "TcpConnection::handleError [" << name_
<< "] - SO_ERROR = " << err << " " << strerror_tl(err);
}
三、連接的建立
當有新連接到達時,TcpServer會爲新連接創建對應的TcpConnection對象,即每個連接一個TcpConnection對象。
四、連接的斷開
Muduo只有一種關閉連接的方式:被動關閉。即對方先關閉連接,本地read(2)返回0,觸發關閉邏輯。
- 當TcpServer或者TcpClinet析構時會涉及到TcpConnection::connectDestroyed()關閉本端連接。
- 遠程的TcpClinet或者TcpServer就好read(2)返回0,最後關閉本端連接。
五、關鍵點總結
1、高低水位問題
非阻塞網絡編程中的發送數據比讀取數據要困難的多:
- 一方面什麼時候關注“writable事件”問題,這只是帶來編碼方面的難度。
- 一方面如果發送數據的速度高於對方接受數據的速度,會造成數據在本地內存中的堆積,
這帶來設計及安全性方面的難度。
Muduo對此解決辦法是提供兩個回調,有的網絡庫把它們稱爲“高水位回調”和“低水位回調”,Muduo使用
HighWaterMarkCallback和WriteCompleCallback這兩個名字。
1.1 WriteCompleCallback
如果發送緩存區被清空,就調用它。TcpConnection有兩處可能觸發此回調。
- TcpConnection::sendInLoop()。
- TcpConnection::handleWrite()。
1.2 HighWaterMarkCallback
如果輸出緩衝的長度超過用戶指定大小,就會觸發回調(只在上升沿觸發一次)。
在非阻塞的發送數據情況下,假設Server發給Client數據流,爲防止Server發過來的數據撐爆Client的輸出緩衝區,一種做法是在Client的HighWaterMarkCallback中停止讀取Server的數據,而在Client的WriteCompleteCallback中恢復讀取Server的數據。
2、TcpConnection::shutdown沒直接關閉Tcp連接
Muduo TcpConnection沒有提供close,而只提供shutdown(),這麼做是爲了收發數據的完整性。
-
Tcp是一個全雙工協議,同一個文件描述符即可讀又可寫,shutdownWrite() 關閉了 “ 寫”
方向的連接,保留了“ 讀 ”方向,這稱爲Tcphalf-close。如果直接close(socket_fd),
那麼socket_fd就不能讀或寫了。 -
用shutdown而不用close的效果是,如果對方已經發送了數據,這些數據還“ 在路上 ”,
那麼muduo不會漏收這些數據。換句話說,muduo在TCP這一層面解決了“當你打算關閉網絡忘記的時候,如何得知對方是否發了一些數據而你還沒有收到?”這一問題。當然,這個問題也可以在上面的協議層解決,雙方協商好不再互發數據,就可以直接斷開連接了。
Muduo把“主動關閉連接”這件事分成兩步來做,如果要主動關閉連接,它先關閉本地的“寫”端,等對方關閉之後,再關閉本地“讀”端。
另外如果當前output buffer裏面還有數據尚未發出的話,Muduo也不會立刻調用shutwownWrite,而是等到數據發送完畢再shutdown,可以避免對方漏收數據。
3、Muduo什麼時候真正close socket
在TcpConnection對象析構的時候,TcpConnection持有一個Socket對象,Socket是一個RAII handler,它的析構函數會close(sockfd_)。