muduo网络库TcpConnection

268 阅读7分钟

TcpConnection主要负责服务器和客户端之间建立的一条连接,所以它表示连接成功的用户在服务端数据封装的一种表示。 mainLoop通过Acceptor获取一个新用户的连接以后,就会把相应的Socket、Channel封装在TcpConnection,TcpConnection会设置相应的回调函数到Channel,然后把Channel注册到Poller,Poller监听到事件后就会把调用相应的回调函数,通过轮询算法交给一个subloop,所以TcpConnection的成员就包含了Socket、Channel(实际上Channel里面也有个sockfd就是Socket),更重要的是TcpConnection拿到了用户给这条连接预设值的connectionCallback_,messageCallback_,writeCompleteCallback_,highWaterMarkCallback_,closeCallback_。

1. TcpConnection.h

#include "Buffer.h"

#include <errno.h>
#include <sys/uio.h>
#include <unistd.h>

//从fd上读取数据,底层的Poller工作在LT模式,存放到writerIndex_,返回实际读取的数据大小 
//底层的buffer缓冲区是有大小的,但是从fd上读数据的时候,却不知道tcp数据最终的大小
//Buffer缓冲区是有大小的(占用堆区内存),但是我们无法知道fd上的流式数据有多少,
//如果我们将缓冲区开的非常大,大到肯定能容纳所有读取的数据,这就太浪费空间了,
//muduo库中使用readv方法,根据读取的数据多少开动态开辟缓冲区
ssize_t Buffer::readFd(int fd, int* saveErrno)
{
    char extrabuf[65536] = {0}; // 64K栈空间,会随着函数栈帧回退,内存自动回收
    
    struct iovec vec[2];
    
    const size_t writable = writableBytes(); // 这是Buffer底层缓冲区剩余的可写空间大小

    /**
     * 当我们用readv从fd上读数据,会先填充vec[0]的缓冲区
     * vec[0]填充满以后会自动把数据填充在extrabuf里面
     * 最后extrabuf里面如果有内容的话,就把extrabuf里面的内容添加在缓冲区里面
     * 这样的结果就是缓冲区刚好存放所有需要写入的内容,内存空间利用率高
    */
    vec[0].iov_base = begin() + writerIndex_; // 第一块缓冲区
    vec[0].iov_len = writable; // iov_base缓冲区可写的大小

    vec[1].iov_base = extrabuf; // 第二块缓冲区
    vec[1].iov_len = sizeof extrabuf;

    // 如果Buffer有65536字节的空闲空间,就不使用栈上的缓冲区
    //如果不够65536字节,就使用栈上的缓冲区,即readv一次最多读取65536字节数据
    const int iovcnt = (writable < sizeof extrabuf) ? 2 : 1; 
    const ssize_t n = ::readv(fd, vec, iovcnt);
    if(n < 0)
    {
        *saveErrno = errno;
    }
    else if(n <= writable)
    {
         // 读取的数据n小于Buffer底层的可写空间,readv会直接把数据存放在begin() + writerIndex_
         writerIndex_ += n;
    }
    else
    {
        //extrabuf里面也写入了数据
        writerIndex_ = buffer_.size();
        // 从extrabuff里读取 n - writable 字节的数据存入Buffer底层的缓冲区
        append(extrabuf, n - writable); 
    }
    return n;
}

ssize_t Buffer::writeFd(int fd,  int* saveErrno)
{
    ssize_t n = ::write(fd, peek(), readableBytes());
    if(n < 0)
    {
        *saveErrno = errno;
    }
    return n;
}

2. TcpConnection.cc

  • 读取数据

fd上有读事件到来时,Poller会通知Channel调用相应的回调函数,即handleRead。这个函数用于读取fd上的数据存入inputBuffer_

  • 发送数据

sendInLoop首先会往fd上写入nwrote个字节的数据,要么写完要么没写完

没写完就会把剩余的数据存入outputBuffer_,并给Channel设置EPOLLOUT事件,当内核的TCP发送缓冲区没有数据时,Poller会给Channel通知,Channel就会调用writeCallback_,即TcpConnection::handleWrite

handleWrite用于继续向TCP的发送缓冲区中写入outputBuffer_中的数据,调用handleWrite后若发送完了outputBuffer_可读区间的数据,就会设置当前Channel对读事件不感兴趣,Poller就不会再通知Channel有关读事件了。若outputBuffer_可读区间的数据依然没有发送完,就不会设置Channel的读事件,等Poller再次通知,直到outputBuffer_可读区间没有数据

  • 关闭连接

当用户调用了shutdown方法后,会设置state_为kDisconnecting,然后调用shutdownInLoop,若数据发送完了,shutdownInLoop关闭当前socket的写端,就会触发EPOLLHUP关闭事件,Poller会通知Channel调用closeCallBack_方法,即TcpConnection::handleClose

如果调用shutdownInLoop时,数据没有发送完,TcpConnection::handleWrite会继续发送数据,完成后由于state_在shutdown被设置为kDisconnecting,TcpConnection::handleWrite内会调用shutdownInLoop

TcpConnection::handleClose会设置fd对所有事件不感兴趣,然后调用connectionCallback_和closeCallback_,这个connectionCallback_实际上就是用户传入的on_connection,而closeCallback_是TcpServer::removeConnection,removeConnection会把这个connection信息在TcpServer的connectionMap删除掉,再拿到这条连接对应的loop,执行这条连接的TcpConnection::connectDestroyed,connectDestroyed方法会把channel从poller中删除掉

#include "TcpConnection.h"
#include "Logger.h"
#include "Socket.h"
#include "Channel.h"
#include "EventLoop.h"

#include <functional>
#include <errno.h>
#include <memory>
#include <sys/types.h>         
#include <sys/socket.h>
#include<strings.h>
#include <netinet/tcp.h>
#include <netinet/in.h>
#include <unistd.h>

static EventLoop* CheckLoopNotNull(EventLoop* loop)
{
    if(loop == nullptr)
    {
        LOG_FATAL("%s:%s:%d TcpConnection Loop is null \n", __FILE__, __FUNCTION__, __LINE__);
    }
    return loop;
}

TcpConnection::TcpConnection(EventLoop *loop,
                const std::string &nameArg,
                int sockfd,
                const InetAddress& localAddr,
                const InetAddress& peerAddr)
                :loop_(CheckLoopNotNull(loop))
                ,name_(nameArg)
                ,state_(kConnecting) //初始状态为正在连接
                ,reading_(true)
                ,socket_(new Socket(sockfd))
                ,channel_(new Channel(loop, sockfd))
                ,localAddr_(localAddr)
                ,peerAddr_(peerAddr)
                ,highWaterMark_(64*1024*1024)
{
    // 我们muduo用户把这些操作注册给TcpServer,TcpServer传递给TcpConnection,
    // TcpConnection给Channel设置回调函数,这些方法都是Poller监听到事件后,Channel需要调用的函数
    channel_->setReadCallBack(
        std::bind(&TcpConnection::handleRead, this, std::placeholders::_1)
    );
    channel_->setWriteCallBack(
        std::bind(&TcpConnection::handleWrite, this)
    );
    channel_->setCloseCallBack(
        std::bind(&TcpConnection::handleClose, this)
    );
    channel_->setErrorCallBack(
        std::bind(&TcpConnection::handleError,this)
    );

    LOG_INFO("TcpConnection::ctor[%s] at fd=%d \n", name_.c_str(), sockfd);
    // 调用setsockopt启动socket的保活机制
    socket_->setKeepAlive(true); 
}
TcpConnection::~TcpConnection()
{
    // 析构函数不需要做什么,只有socket_、channel_是new出来的,这俩用智能指针管理会自动释放
    LOG_INFO("TcpConnection::dtor[%s] at fd=%d state=%d\n", name_.c_str(), socket_->fd(), (int)state_);
}

/**
 * 用户会给TcpServer注册一个onMessage方法,已建立连接的用户在发生读写事件的时候onMessage会响应,
 * 我们在onMessage方法处理完一些业务代码以后,会send给客户端返回一些东西,send最终发的时候,
 * 都是把数据序列化成json或者pb,都是转成相应的字符串,然后通过网络发送出去,
 * 所以就不需要专门初始化buf,而且如果用户值有字符串的话,buffer也没有带字符串相应的构造函数,
 * string也无法直接转成buffer,所以这里对外提供的接口string作为参数
 */


void TcpConnection::send(const std::string& buf)
{
    //state应该是已连接状态
    if (state_ == kConnected)
    {
        /**
         * 当前loop是否在对应线程里面,一般来说肯定在当前线程去执行这个方法,因为TcpConnection最终注册到某一线程里面,
         * poller也是在相应的Loop线程里去通知,可是我们有一些应用场景会把connection全部记录下来,记录下来就有可能在
         * 其他线程里面去调用connection进行send数据发送
         */
        if (loop_->isInLoopThread())
        {
            sendInLoop(buf.c_str(),buf.size());
        }
        else
        {
            loop_->runInLoop(std::bind(
                &TcpConnection::sendInLoop,
                this,
                buf.c_str(),
                buf.size()
                ));
        }
    }
}
/**
 * 发送数据,应用写的快,而内核发送数据慢,需要把待发送数据写入缓冲区,
 * 而且设置了水位回调
 */
void TcpConnection::sendInLoop(const void* data, size_t len)
{
    ssize_t nwrote = 0; // 已发送数据
    size_t remaining = len; //未发送数据
    bool faultError = false; //记录是否产生错误

    //之前调用过该Connection的shutdown,不能再进行发送了
    if (state_ == kDisconnected)
    {
        LOG_ERROR("disconnected, give up writing!");
        return;
    }
    
    //刚开始我们注册的感兴趣的都是socket读事件,写事件刚开始没有注册过
    //表示channel第一次开始写数据,而且缓冲区没有待发送数据
    if (!channel_->isWriting() && outputBuffer_.readableBytes() == 0)
    {
        nwrote = ::write(channel_->fd(), data, len);
        if (nwrote >= 0)
        {
            remaining = len - nwrote;
            if (remaining == 0 && writeCompleteCallback_)
            {
                // 如果数据刚好发送完了 && 用户注册过发送完成的回调writeCompleteCallback_
                // 数据没有发送完时,channel_才对写事件感兴趣
                // 既然在这里数据全部发送完成,就不用再给channel设置epollout事件,handleWrite也不会再执行了,handleWrite就是有epollout事件发生时执行
                loop_->queueInLoop(
                    std::bind(writeCompleteCallback_, shared_from_this())
                );
            }
        }
        else //nwrote < 0
        {
            nwrote = 0;
            // EWOULDBLOCK是由非阻塞没有数据正常的返回
            if (errno != EWOULDBLOCK)
            {
                LOG_ERROR("TcpConnection::sendInLoop \n");
                //接收到对端的socket重置,有错误发生了
                if (errno == EPIPE || errno == ECONNRESET) //SIGPIPE RESET
                {
                    faultError = true;
                }
            }
        }
    }

    /**
     * 说明当前这一次write,并没有把数据全部发送出去,剩余的数据需要保存到缓冲区当中,
     * 然后给channel注册epollout事件,poller发现tcp的发送缓冲区有空间,会通知相应的
     * sock-channel,调用writeCallback_相应的回调方法,也就是调用TcpConnection::handleWrite
     * 方法,把发送缓冲区的数据全部发送完成
     * 
     */
    if (!faultError && remaining > 0)
    {
        // 目前outputBuffer_中积攒的待发送的数据
        size_t oldlen = outputBuffer_.readableBytes();
        if (oldlen + remaining >= highWaterMark_
            && oldlen < highWaterMark_
            && highWaterMarkCallback_)
        {
            // 如果以前积攒的数据不足水位 && 以前积攒的加上本次需要写入outputBuffer_的数据大于水位 && 注册了highWaterMarkCallback_
            // 调用highWaterMarkCallback_
            loop_->queueInLoop(
                std::bind(highWaterMarkCallback_, shared_from_this(), oldlen + remaining)
            );
        }
        // 剩余没发送完的数据写入outputBuffer_
        outputBuffer_.append((char*)data + nwrote, remaining);
        if (!channel_->isWriting())
        {
            // 给Channel注册EPOLLOUT写事件,否则当内核的TCP发送缓冲区没有数据时,Poller不会给Channel通知,
            // Channel就不会调用writeCallback_,即TcpConnection::handleWrite
            channel_->enableWriting();
        }
    }
}

//关闭连接
void TcpConnection::shutdown()
{
    if (state_ == kConnected)
    {
        setState(kDisconnecting);
        loop_->runInLoop(std::bind(&TcpConnection::shutdownInLoop,this));
    }
}

void TcpConnection::shutdownInLoop()
{
    // 有可能数据还没发送完就调用了shutdown,先等待数据发送完,发送完后handleWrite内会调用shutdownInLoop
    if (!channel_->isWriting())//说明outputBuffer_中的数据已经发送完成
    {
        socket_->shutdownWrite();//关闭写端
    }
}
//建立连接
void TcpConnection::connectEstablished()
{
    setState(kConnected);
    channel_->tie(shared_from_this());
    //向Poller注册channel的epollin事件
    channel_->enableReading();
    //新连接建立,执行回调
    connectionCallback_(shared_from_this());
}

//销毁连接
void TcpConnection::connectDestroyed()
{
    if(state_ == kConnected)
    {
        setState(kDisconnected);
        channel_->disableAll(); // 通过epoll_ctl把channel所有感兴趣的事件从poller中del掉
        connectionCallback_(shared_from_this());
    }
    channel_->remove(); //把channel从poller中删除
}

// fd上有读事件到来时,Poller会通知Channel调用相应的回调函数,即handleRead。这个函数用于读取fd上的数据存入inputBuffer_
void TcpConnection::handleRead(Timestamp receiveTime)
{
    int savedErrno = 0;
    // 发生了读事件,从channel_的fd中读取数据,存到inputBuffer_
    ssize_t n = inputBuffer_.readFd(channel_->fd(), &savedErrno);
    if(n > 0)
    {
        // 读取完成后,需要调用用户传入的回调操作,就是我们给TcpServer传入的on_message函数
        // 把当前TcpConnection对象给用户定义的on_message函数,是因为用户需要利用TcpConnection对象给客户端发数据,
        // inputBuffer_就是客户端发来的数据,也传入on_message函数给用户
        // shared_from_this就是获取了当前TcpConnection对象的一个shared_ptr
        messageCallback_(shared_from_this(), &inputBuffer_, receiveTime);
    }
    else if(n == 0)//客户端断开了
    {
        handleClose();
    }
    else//出错了
    {
        errno = savedErrno;
        LOG_ERROR("TcpConnection::handleRead");
        handleError();
    }
}

// TcpConnection::sendInLoop一次write没有发送完数据,将剩余的数据写入outputBuffer_后,然后Channel调用writeCallback_
// Channel调用的writeCallback_就是TcpConnection注册的handleWrite,handleWrite用于继续发送outputBuffer_中的数据到TCP缓冲区,
//直到outputBuffer_可读区间没有数据
void TcpConnection::handleWrite()
{
    if(channel_->isWriting())
    {
        int saveErrno = 0;
        // 往fd上写outputBuffer_可读区间的数据,写了n个字节,即发送数据
        int n = outputBuffer_.writeFd(channel_->fd(), &saveErrno);
        if(n > 0) //有数据发送成功
        {
            outputBuffer_.retrieve(n); // readerIndex_复位
            if(outputBuffer_.readableBytes() == 0)
            {
                // outputBuffer_的可读区间为0,已经发送完了,将Channel封装的events置为不可写,底层还是调用的epoll_ctl
                // Channel调用update remove ==> EventLoop updateChannel removeChannel ==> Poller updateChannel removeChannel
                channel_->disableWriting();
                if(writeCompleteCallback_)
                {
                    //唤醒loop对应的thread线程执行回调
                    loop_->queueInLoop(
                        std::bind(writeCompleteCallback_,shared_from_this())
                    );
                }
                if(state_ = kDisconnected)
                {
                    // 读完数据时,如果发现已经调用了shutdown方法,state_会被置为kDisconnecting,
                    // 则会调用shutdownInLoop,在当前所属的loop里面删除当前TcpConnection对象
                    shutdownInLoop();
                }
            }
        }
        else
        {
            LOG_ERROR("TcpConnection::handleWrite");
        }
    }
    else //对写事件不感兴趣,    要执行handleWrite,但是channel的fd的属性为不可写
    {
        LOG_ERROR("TcpConnection fd=%d is down, no more writing \n", channel_->fd());
    }
}

void TcpConnection::handleClose()
{
    LOG_INFO("TcpConnection::handleClose fd=%d, state=%d \n", channel_->fd(), (int)state_);
    //只有在已连接或者正在断开的状态才能close
    setState(kDisconnected);
    //对channel所有的事件都不感兴趣了,从epoll红黑树中删除
    channel_->disableAll();

    TcpConnectionPtr connPtr(shared_from_this());
    connectionCallback_(connPtr);//执行连接关闭的回调(用户传入的)
    closeCallback_(connPtr); // 执行连接关闭以后的回调,即TcpServer::removeConnection
}

void TcpConnection::handleError()
{
    int optval;
    socklen_t optlen = sizeof optval;
    int err = 0;
    if(::getsockopt(channel_->fd(), SOL_SOCKET, SO_ERROR, &optval, &optlen) < 0)
    {
        err = errno;
    }
    else
    {
        err = optval;
    }
    LOG_ERROR("TcpConnection::handleError name:%s - SO_ERROR:%d \n", name_.c_str(), err);
}