Muduo分析及總結(六)TcpConnection

一、類關係圖

首先看TcpConnection在整個Muduo結構中的關係,如下圖
圖1

  1. TcpConnection是整個網絡庫的核心,封裝一次Tcp連接,注意它不能發起連接。
  2. 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;

TcpConnection.cc

/默認連接回調。輸出連接狀態
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,觸發關閉邏輯。

  1. 當TcpServer或者TcpClinet析構時會涉及到TcpConnection::connectDestroyed()關閉本端連接。
  2. 遠程的TcpClinet或者TcpServer就好read(2)返回0,最後關閉本端連接。
五、關鍵點總結
1、高低水位問題

非阻塞網絡編程中的發送數據比讀取數據要困難的多:

  1. 一方面什麼時候關注“writable事件”問題,這只是帶來編碼方面的難度。
  2. 一方面如果發送數據的速度高於對方接受數據的速度,會造成數據在本地內存中的堆積,
    這帶來設計及安全性方面的難度。

Muduo對此解決辦法是提供兩個回調,有的網絡庫把它們稱爲“高水位回調”和“低水位回調”,Muduo使用
HighWaterMarkCallbackWriteCompleCallback這兩個名字。

1.1 WriteCompleCallback

如果發送緩存區被清空,就調用它。TcpConnection有兩處可能觸發此回調。

  1. TcpConnection::sendInLoop()。
  2. 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_)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章