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_)。

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