一、类关系图
首先看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_)。