用于管理一个具体的 TCP 连接,比如消息的接收与发送,完成用户指定的连接回调 connectionCallback
。
TcpConnection
构造时接收参数有 TCP 连接的 sockfd,服务端地址 localAddr
,客户端地址 peerAddr
,并通过 Socket 封装 sockfd
。并用 Channel 管理该 sockfd,向 Channel 注册可读、可写、关闭、出错回调函数,用于 Poller 返回就绪事件后 Channel::handleEvent()
执行相应事件的回调。
TcpConnection 有四个状态,简单的状态图:
TcpConnection 有一系列用户指定的事件回调函数,比如 TcpConnection::connectionCallback
、messageCallback
、writeCompleteCallback
,这些是用户通过 TcpServer 传给 TcpConnection。当 Poller 返回 TcpConnection 对应的 Socket 就绪事件时,Channel::handleEvent()
-> TcpConnection::handle*系列函数
-> 这些事件回调函数(如 connectionCallback)。
与上面对应,TcpConnection 有一系列 用于 TcpServer 为连接指定事件 callback 的函数,比如 TcpConnection::setConnectionCallback
、setCloseCallback
等等,是在 TcpServer::newConncetion()
中创建一个 TcpConnection 对象并将用户指定的回调函数(上段说的)通过 TcpConnection::set*Callback
函数传递给 TcpConnection。
TcpConncetion 中还有一系列函数是处理 sockfd 上的事件回调函数,比如 TcpConnection::handleRead()
是在Poller 返回可读事件时由 Channel::handleEvent()
调用的,类似的还有 handleWrite()
、handleClose()
、handleError()
,这些函数会调用用户指定的事件回调函数(上上段说的),比如 TcpConnection::handleRead() -> messageCallback()
。这些事件回调函数是在 TcpConnection 构造时创建完 sockfd 对应的 Channel 后通过 Channel::set*Callback
系列函数注册的。
TcpConnection::handleRead()
:当连接对应的 sockfd 有可读事件发生时调用,主要是将数据读到 Buffer 中,执行消息回调函数 messageCallback_()
。
TcpConnection::handleWrite()
:当连接对应的 sockfd 有可写事件发生时调用,主要是将 Buffer 中的数据发送出去,如果一次性发送完毕,则执行用户指定的回调 writeCompleteCallback_()
,若一次没有发送完, muduo 采用 LT 模式, 会反复触发可写事件,下次还有机会发送剩下的数据。
TcpConnection::handleClose()
:主要执行 Channel::disableAll()
和 TcpConnection::closeCallback()
。
TcpConnection::handleError()
:主要是在日志中输出错误信息。
TcpConnection::closeCallback()
不是给普通用户用的,这个是给 TcpServer 和 TcpClient 用的,用于通知它们移除所持有的 TcpConnectionPtr。它绑定的是 TcpServer::removeConnection()
,通过下面关系:Accetptor 接受一个 Tcp 连接时 Channel::handleEvent() -> Acceptor::handleRead() -> TcpServer::newConnection()
中新建一个 TcpConnection 并通过 TcpConnection::setCloseCallback(bind(&TcpServer::removeConnectionCallback, this, _1))
。 这个回调什么时候被调用呢?当 TcpConnection::handleRead()
中 read 返回0,或者 sockfd 发生 POLLHUP 事件就绪时,都会调用到 TcpConnection::handleClose() -> TcpConnection::closeCallback_() -> TcpServer::removeConnection()
。
TcpConnection 中还有两个函数是给 TcpServer 使用的。
TcpConnection::connectEstablishd()
,是 TcpServer::newConnection()
创建完 TcpConnection 对象,设置好回调函数之后调用的,主要是调用 Channel::enableReading()
将 TcpConnection 对应的 sockfd 注册读事件,然后执行用户指定的 connectionCallback_()
,并将 TcpConnection 状态置为 kConnected。这个函数如何被执行呢? Acceptor 持有的 lfd 有可读事件发生,即有连接请求,此时 Channel::handleEvent() -> Acceptor::handleRead() -> TcpServer::newConnection() -> ……->TcpConnection::connectEstablished()
。中间省略的部分是线程转移,转移到TcpConnection 所在的 IO 线程执行,TcpServer 和 TcpConnection 可能不在同一线程。
TcpConnection::connectDestroyed()
,这是 TcpConnection 析构前最后调用的一个成员函数,它通知用户连接已断开。主要功能是设置 TcpConnection 的状态为 kDisconnected;停止监听所有事件;调用 connectionCallback_()
执行用户指定的回调;从 epoll 监听的 fd 中移除 TcpConnection 对应的 sockfd。那什么时候调用到这个函数呢?一个是 TcpServer 析构时,一般情况下是经由 TcpConnection::handleClose() -> TcpConnection::closeCallback_() -> TcpServer::removeConnection() -> ……->TcpConnection::connectDestroyed()
。
send 一系列函数是可以用户或者其他线程调用,用于发送信息。如果不是在IO线程,它会把实际工作转移到IO线程调用。首先检查 TcpConnection 对应的 Socket 是否注册了可写事件,若注册了可写事件表明输出缓冲区 outputBuffer_中已经有数据等待发送,为了保证不乱序,这次的数据只要追加到输出缓冲区中,通过 Channel::handleEvent() -> TcpConnection::handleWrite()
来发送。如果Socket 没有注册可写事件,输出缓冲区没有数据,那么这次的消息可以直接通过 write 发送,如果没有一次性发送完毕,那么 message 剩余的数据仍然要 append 到 outputBuffer 中,并向 Poller 注册可写事件,当 socket 变得可写时,Channel 会调用 TcpConnection::handleWrite()
来发送 outputBuffer_ 中堆积的数据,发送完毕后立刻停止监听可写事件,避免 busy loop。无论是 sendInLoop() -> write()
还是 Channel::handleEvent() -> handleWrite()
,只要确定发送完 message 或者 outputBuffer_ 中的数据,那么都要调用用户指定的回调 writeCompleteCallback()
。
使用 epoll 的 LT 模式,当 socket 可写时,会不停的触发 socket 的可写事件,这个时候如何解决?第一种方式:需要向 socket 写数据时,注册此 socket 的可写事件,接收到可写事件后,然后调用 write/send 写数据,当所有数据都写完后,立刻停止观察 writable 事件,避免 busy loop。。第二种,需要发送数据时,直接调用 write/send 写,如果没有发送完,那么开是监听此 socket 的 writable 事件,然后接收到可写事件后,调用 write/send 发送数据,当所有数据都写完后,立刻停止观察 writable 事件,避免 busy loop。 muduo 采用的 LT 模式,就是用的第二种解决这个问题。
此外 TcpConnection 还有几个小功能,比如 TcpConnection::setTcpNoDelay()
和 TcpConnection::setKeepAlive()
。TCP No Delay 和 TCP keepalive 都是常用的 TCP 选项,前者的作用是禁止 Nagle 算法,避免连续发包出现延迟,这对编写低延迟网络服务很重要。后者的作用是定期探查 TCP 连接是否还存在,一般来说如果有应用层心跳的话,TCP keepalive 不是必须的。
Buffer 类的设计后面会分析。
TcpConnection.h
#ifndef MUDUO_NET_TCPCONNECTION_H
#define MUDUO_NET_TCPCONNECTION_H
#include <muduo/base/StringPiece.h>
#include <muduo/base/Types.h>
#include <muduo/net/Callbacks.h>
#include <muduo/net/Buffer.h>
#include <muduo/net/InetAddress.h>
#include <boost/any.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/noncopyable.hpp>
#include <boost/scoped_ptr.hpp>
#include <boost/shared_ptr.hpp>
// struct tcp_info is in <netinet/tcp.h>
struct tcp_info;
namespace muduo
{
namespace net
{
class Channel;
class EventLoop;
class Socket;
///
/// 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,