关于golang:muduo源码分析之Buffer

45次阅读

共计 2829 个字符,预计需要花费 8 分钟才能阅读完成。

这一次咱们来剖析下 muduoBuffer的作用,咱们晓得,当咱们客户端向服务器发送数据时候,服务器就会读取咱们发送的数据,而后进行一系列解决,而后再发送到其余中央,在这里咱们设想一下最简略的 EchoServer 服务器,客户端建设一个连贯,当前服务器和客户端之间的通信都是通过这个 connfd 发送和承受数据,于是每一个 connfd 都应该有一个本人 buffer,当咱们发送数据太快,服务器发送的太慢,则服务器会将待发送的数据这个buffer 中,所以这就是这个类的作用。咱们先看下 buffer 的构造是什么:

咱们这里次要针对 connfd 这个对应的 channel 进行剖析,首先上图是 buffer 的初始状态,后面 8 个字节中示意该 buffer 的大小,初始大小为 1024。当客户端发送数据给服务器,同时若服务器承受迟缓,则会向buffer 中开始写数据,则 writerIndex_ 会向右挪动,如果此时挪动到如下模式:

则此时缓冲区能够读的数据为writerIndex_ - readerIndex_,能够写的数据为buffer_.size() - writerIndex_。这时候当服务器有多余资源进行读操作,就能够去缓冲区读数据了,如果这时候的状态为如下:

这就是常见的几个状态,上面咱们去看几个重点的函数:

    // 把 onMessage 函数上报的 buffer 内容转为 string
    std::string retrieveAllAsString()
    {return retrieveAsString(readableBytes()); // 利用可读取数据的长度
    }
    
    // 可读的数据 就是寄存的是即发送的数据
    size_t readableBytes() const 
    {return writerIndex_ - readerIndex_;}
    
    std::string retrieveAsString(size_t len)
    {
        // 从可读数据开始地位,长度为 len 的 char 结构为一个 string
        std::string result(peek(), len);
        retrieve(len); // 下面一句把缓冲区中可读的数据,曾经读取进去,这里必定要对缓冲区进行复位操作
        return result;
    }
    
    // 将缓冲区 len 的长度进行复位
    void retrieve(size_t len)
    {
        // 示意还没有读完数据
        if (len < readableBytes())
        {readerIndex_ += len; // 利用只读取了刻度缓冲区数据的一部分,就是 len,还剩下 readerIndex_ += len -> writerIndex_}
        else   // len == readableBytes()
        {retrieveAll();
        }
    }

以上是根本的操作,上面的 2 个函数很重要,一个是向 connfd 写数据,一个是读数据,对于一个 TcpConnection 而言,当有数据来的时候,回去调用 handleRead 回调函数。咱们晓得 muduo 设置的每次读取的大小为 65536 字节,当缓冲区可写的数据大小大于 65536,就会间接将读到的数据写入到缓冲区中,但当缓冲区的可写数据大小小于65536 的时候,就会将残余数据先写到一个额定的空间

ssize_t Buffer::readFd(int fd, int* saveErrno)
{char extrabuf[65536] = {0}; // 栈上的内存空间  64K
    
    struct iovec vec[2];
    
    // 这是 buffer 可写的数据
    const size_t writable = writableBytes(); 
    vec[0].iov_base = begin() + writerIndex_;
    vec[0].iov_len = writable;

    vec[1].iov_base = extrabuf;
    vec[1].iov_len = sizeof extrabuf;
    
    const int iovcnt = (writable < sizeof extrabuf) ? 2 : 1;
    // 去百度下 readv
    const ssize_t n = ::readv(fd, vec, iovcnt);
    if (n < 0)
    {*saveErrno = errno;}
    else if (n <= writable) // Buffer 的可写缓冲区曾经够存储读出来的数据了
    {writerIndex_ += n;}
    // extrabuf 也写了数据
    else 
    {writerIndex_ = buffer_.size();
        append(extrabuf, n - writable);  // writerIndex_开始写 n - writable 大小的数据
    }

    return n;
}

这里奇妙的应用了一个 readv 函数,能够通过依照大小主动写到不同的中央。其中当 extrabuf 也写了数据,就会调用 append 函数。

    // 要写 len 长度的数据
    void ensureWriteableBytes(size_t len)
    {if (writableBytes() < len)
        {makeSpace(len); // 扩容函数
        }
    }

    // 向缓冲区增加数据
    void append(const char *data, size_t len)
    {ensureWriteableBytes(len);
        std::copy(data, data+len, beginWrite());
        writerIndex_ += len;
    }

留神到有一个 makeSpace 函数,其中有一个留神点,比方当如下这种状态的时候:

此时 readerIndex_ 后面有一部分其实曾经被读完了,是空的数据,所以 makeSpace 函数思考了这一点,采纳内存重组的形式,将 readerIndex_ 向前挪动到 kCheapPrepend 处,而后就能够让空余的内存挨在一起

    void makeSpace(size_t len)
    {if (writableBytes() + prependableBytes() < len + kCheapPrepend)
        {buffer_.resize(writerIndex_ + len);
        }
        else
        {size_t readalbe = readableBytes();
            std::copy(begin() + readerIndex_, 
                    begin() + writerIndex_,
                    begin() + kCheapPrepend);
            readerIndex_ = kCheapPrepend;
            writerIndex_ = readerIndex_ + readalbe;
        }
    }

当向 connfd 发送数据的时候就比较简单了,间接将可读的数据发送给进来就行

// 通过 fd 发送数据
ssize_t Buffer::writeFd(int fd, int* saveErrno)
{ssize_t n = ::write(fd, peek(), readableBytes());
    if (n < 0)
    {*saveErrno = errno;}
    return n;
}

本人的网址:www.shicoder.top
欢送加群聊天 452380935
本文由博客一文多发平台 OpenWrite 公布!

正文完
 0