• muduo笔记 网络库(九)输入输出缓冲区Buffer


    应用层缓冲区Buffer设计

    Buffer设计思想

    应用层缓冲区Buffer设计

    • 为什么要需要有应用层缓冲区
    • Buffer结构

    epoll使用LT模式原因

    • 与poll兼容
    • LT模式不会发生漏掉事件的BUG,但POLLOUT事件不能一开始就关注,否则会出现busy loop,而应该在write无法完全写入内核缓冲区的时候才关注,将未写入内核缓冲区的数据添加到应用层output buffer,直到应用层output buffer写完,停止关注POLLOUT事件。-- 相当于写完成回调,处理写完成事件。
    • 读写的时候不必等候EAGAIN,可以节省系统调用次数,降低延迟。(注:如果用ET模式,读的时候读到EAGAIN,写的时候直到output buffer写完成或者EAGAIN)

    为什么需要有应用层缓冲区?

    non-blocking网络编程中,non-blocking IO核心思想是避免阻塞在read()/write()或其他IO系统调用上,可以最大限度复用thread-of-control,让一个线程能服务于多个socket连接。而IO线程只能阻塞在IO-multiplexing函数上,如select()/poll()/epoll_wait(),这样应用层的缓冲区就是必须的,每个TCP socket都要有stateful的input buffer和output buffer。

    具体来说,

    • TcpConnection必须要有output buffer
      一个常见场景:程序想通过TCP连接发送100K byte数据,但在write()调用中,OS只接收80K(受TCP通告窗口advertised window的控制),而程序又不能原地阻塞等待,事实上也不知道要等多久。程序应该尽快交出控制器,返回到event loop。此时,剩余20K数据怎么办?

    对应用程序,它只管生成数据,不应该关系到底数据是一次发送,还是分几次发送,这些应该由网络库操心,程序只需要调用TcpConnection::send()就行。网络库应该接管剩余的20K数据,把它保存到TcpConnection的output buffer,然后注册POLLOUT事件,一旦socket变得可写就立刻发送数据。当然,第二次不一定能完全写入20K,如果有剩余,网络库应该继续关注POLLOUT事件;如果写完20K,网络库应该停止关注POLLOUT,以免造成busy loop。

    如果程序又写入50K,而此时output buffer里还有待发20K数据,那么网络库不应该直接调用write(),而应该把这50K数据append到那20K数据之后,等socket变得可写时再一并写入。

    如果output buffer里还有待发送数据,而程序又想关闭连接,但对程序而言,调用TcpConnection::send()后就认为数据迟早会发出去,此时网络库不应该直接关闭连接,而要等数据发送完毕。因为此时数据可能还在内核缓冲区中,并没有通过网卡成功发送给接收方。
    将数据append到buffer,甚至write进内核,都不代表数据成功发送给对端。

    综上,要让程序在write操作上不阻塞,网络库必须给每个tcp connection配置output buffer。

    • TcpConnection必须要有input buffer
      TCP是一个无边界的字节流协议,接收方必须要处理“收到的数据尚不构成一条完整的消息”“一次收到两条消息的数据”等情况。
      一个常见场景:发送方send 2条10K byte消息(共计20K),接收方收到数据的可能情况:
    • 一次性收到20K数据
    • 分2次收到,第一次5K,第二次15K
    • 分2次收到,第一次15K,第二次5K
    • 分2次收到,第一次10K,第二次10K
    • 分3次收到,第一次6K,第二次8K,第三次6K
    • 其他任何可能

    这些情况统称为粘包问题。

    LT模式下,如何解决的粘包问题?
    可以一次把内核缓冲区中的数据读完,存至input buffer,通知应用程序,进行onMessage(Buffer* buffer)回调。在onMessage回调中,应用层协议判定是否是一个完整的包,如果不是一条完整的消息,不会取走数据,也不会进行相应的处理;如果是一条完整的消息,将取走这条消息,并进行相应的处理。

    如何判断是一条完整的消息?
    相应应用层协议制定协议,不由网络库负责。

    网络库如何处理 “socket可读”事件?
    一次性把socket中数据读完,从内核缓冲区读取到应用层buffer,否则会反复触发POLLIN事件,造成busy-loop。

    综上,tcp网络编程中,网络库必须给每个tcp connection配置input buffer。

    应用层与input buffer、output buffer

    muduo中的IO都是带缓冲的IO(buffered IO),应用层不会自行去read()或write()某个socket,只会操作TcpConnection的input buffer和output buffer。更准确来说,是在onMessage()回调中读取input buffer;调用TcpConnection::send() 来间接操作output buffer,而不直接操作output buffer。


    Buffer要求

    muduo buffer 的设计考虑常见网络编程需求,在易用性和性能之间找一个平衡点。目前更偏向易用性。

    muduo Buffer设计要点:

    • 对外表现为一块连续的内存(char*, len),以便客户代码的编写。
    • size()可以自动增长,以适应不同大小的消息。不是一个fixed size array(不是固定大小数组,char buf[8192])。
    • 内部以vector of char来保存数据,并提供相应的访问函数。

    Buffer更像一个queue,从末尾写入数据,从头部读出数据。

    谁使用Buffer,读、写Buffer?
    TcpConnection有2个Buffer:input buffer,output buffer。

    • input buffer,TcpConnection会从socket读取数据,然后写入input buffer(由Buffer::readFd()完成);客户代码在onMessage回调中,从input buffer读取数据。
    • output buffer,客户代码把数据写入output buffer(用Connection::send()完成);TcpConnection从output buffer读取数据并写入socket。

    input、output是针对客户代码而言的,对TcpConnection来说读写方向相反。

    线程安全

    Buffer故意设计成非线程安全的,原因如下:

    • 对于input buffer,onMessage()回调发生在该TcpConnection所属IO线程,应用程序应该在onMessage()完成对input buffer的操作,并且不要把input buffer暴露给其他线程。这样,对input buffer的操作都在同一个IO线程,因此Buffer class不必是线程安全的。
    • 对于output buffer,应用程序不会直接操作它,而是调用TcpConenction::send()来发送数据,后者是线程安全的。准确来说,是会让output buffer只会在所属IO线程操作。

    Buffer数据结构

    2个indices(标记)readIndex、writeIndex把vector内容分为3块:prependable、readable、writable,各块大小关系:
    prependable = readIndex
    readable = writeIndex - readIndex
    writable = size() - writeIndex

    灰色部分是Buffer的有效载荷(payload),prependable能让程序以极低代价在数据前添加几个字节,从而简化客户代码。

    readIndex,writeIndex满足以下不变式(invariant):
    0 ≤ readIndex ≤ writeIndex ≤ data.size()

    Buffer class的核心数据成员:

    class Buffer : public muduo::copyable
    {
    public:
        ...
    private:
        std::vector<char> buffer_; // 存储数据的线性缓冲区, 大小可变
        size_t readerIndex_; // 可读数据首地址, i.e. readable空间首地址
        size_t writerIndex_; // 可写数据首地址, i.e. writable空间首地址
        ...
    };
    

    Buffer中有2个常数:kCheapPrepend,kInitialSize,分别定义了prependable初始大小,writable的初始大小。readable初始大小0。

        static const size_t kCheapPrepend = 8;   // 初始预留的prependable空间大小
        static const size_t kInitialSize = 1024; // Buffer初始大小
    

    初始化完成后,Buffer数据结构如下:


    Buffer的操作

    基本IO操作

    Buffer初始化完成后,向Buffer写入200byte,其布局是:

    可以看到,writeIndex向后移动了200,readIndex保持不变。readable、writable都有变化。

    如果从Buffer read(), retrieve()(“读入”)50byte,其布局是:

    可以看到,readIndex向后移动50,writeIndex保持不变,readable和writable的值也有变化。

    自动增长

    Buffer长度不是固定的,可以自动增长,因为底层存储利用的是vector

    假设当前可写空间writable为624 byte,现客户代码一次写入1000,那么buffer会自动增长,如下图。

    增长前:

    增长后:

    readable由350增长为1350(刚好增加了1000),writable由624减为0。另外,readIndex由58回到了初始位置8,保证prependable等于kCheapPrependable。

    buffer没有缩小功能,下次写入1350byte就不会重新分配内存,一方面避免浪费内存,另一方面避免反复分配内存。如果有需要,客户代码也可以手动shrink() buffer size()。

    size()和capacity()

    Buffer使用vector存储数据,可以得到它的capacity()机制的好处,减少内存分配次数。在写入的数据不超过capacity() - size()时,都不会重新分配内存。

    在muduo中,不调用reserve()预先分配空间,而是在Buffer构造时把初始size()设为1K,当size()超过1K时,vector会把capacity()加倍,等于说resize()做了reserve()的事。

    内部腾挪

    经过若干次读写,readIndex移到了比较靠后的位置,留下了很大的prependable空间,如下图已由初始8 byte,变成532。

    此时,如果想写入300byte,而writable只有200,怎么办?
    Buffer不会重新分配内存,而是先把已有的数据移到前面去,减小多余prependable空间,为writable腾出空间。

    这样,writable变成724 byte,就可以写入300 byte 数据了。

    prepend

    prepend 提供prependable空间,让程序能以极低的代价在数据前添加几个字节。通过预留kCheapPrepend空间,能简化客户代码。


    Buffer类的实现

    构造函数与析构函数

    构造函数见下,析构函数才用trivial dctor(编译器合成的默认版本,即平凡的析构函数)

        /* 构造函数 内部缓冲区buffer_初始大小默认kCheapPrepend + initialSize (1032byte) */
        explicit Buffer(size_t initialSize = kInitialSize)
        : buffer_(kCheapPrepend + initialSize),
          readerIndex_(kCheapPrepend), // 8
          writerIndex_(kCheapPrepend)  // 8
        {
            assert(readableBytes() == 0);
            assert(writableBytes() == initialSize);
            assert(prependableBytes() == kCheapPrepend);
        }
    
        /*
         * implicit copy-ctor, move-ctor, dtor and assignment are fine
         * NOTE: implicit move-ctor is added in g++ 4.6
         */
    

    读取prependable, readable, writable空间地址、大小等属性的方法

        /* 返回 readable 空间大小 */
        size_t readableBytes() const
        { return writerIndex_ - readerIndex_; }
    
        /* 返回 writeable 空间大小 */
        size_t writableBytes() const
        { return buffer_.size() - writerIndex_; }
    
        /* 返回 prependable 空间大小 */
        size_t prependableBytes() const
        { return readerIndex_; }
    
        /* readIndex 对应元素地址 */
        const char* peek() const
        { return begin() + readerIndex_; }
    
        /* 返回待写入数据的地址, 即writable空间首地址 */
        char* beginWrite()
        { return begin() + writerIndex_; }
        const char* beginWrite() const
        { return begin() + writerIndex_; }
    
        /* 将writerIndex_往后移动len byte, 需要确保writable空间足够大 */
        void hasWritten(size_t len)
        {
            assert(len <= writableBytes());
            writerIndex_ += len;
        }
        /* 将writerIndex_往前移动len byte, 需要确保readable空间足够大 */
        /*
         * Cancel written bytes.
         */
        void unwrite(size_t len)
        {
            assert(len <= readableBytes());
            writerIndex_ -= len;
        }
    
    private:
        /* 返回缓冲区的起始位置, 也是prependable空间起始位置 */
        char* begin()
        {
    //       <=> return buffer_.data();
            return &*buffer_.begin();
        }
        const char* begin() const
        { return &*buffer_.begin(); }
    

    retrieve*取走数据

    retrieve系列函数从readable空间取走数据,只关心移动readableIndex_,改变readable空间大小,通常不关心读取数据具体内容,除非有指定具体的返回值,如retrieveAllAsString。
    retrieve系列函数会改变readable空间大小,但通常不会改变writable空间大小,除非retrieveAll取完所有readable空间的数据,readable空间将会合并到writable空间。

        /* 从readable头部取走最多长度为len byte的数据. 会导致readable空间变化, 可能导致writable空间变化.
         * 这里取走只是移动readerIndex_, writerIndex_, 并不会直接读取或清除readable, writable空间数据 */
        /*
         * retrieve() returns void, to prevent
         * string str(retrieve(readableBytes()), readableBytes());
         * the evaluation of two functions are unspecified
         */
        void retrieve(size_t len)
        {
            assert(len <= readableBytes());
            if (len < readableBytes()) // readable 中数据充足时, 只取走len byte数据
            {
                readerIndex_ += len;
            }
            else
            { // readable中数据不足时, 取走所有数据
                retrieveAll();
            }
        }
        /* 从readable空间取走 [peek(), end)这段区间数据, peek()是readable空间首地址 */
        void retrieveUntil(const char* end)
        {
            assert(peek() <= end);
            assert(end <= beginWrite());
            retrieve(end - peek());
        }
        /* 从readable空间取走一个int64_t数据, 长度8byte */
        void retrieveInt64()
        {
            retrieve(sizeof(int64_t));
        }
        /* 从readable空间取走一个int32_t数据, 长度4byte */
        void retrieveInt32()
        {
            retrieve(sizeof(int32_t));
        }
        /* 从readable空间取走一个int16_t数据, 长度2byte */
        void retrieveInt16()
        {
            retrieve(sizeof(int16_t));
        }
        /* 从readable空间取走一个int8_t数据, 长度1byte */
        void retrieveInt8()
        {
            retrieve(sizeof(int8_t));
        }
        /* 从readable空间取走所有数据, 直接移动readerIndex_, writerIndex_指示器即可 */
        void retrieveAll()
        {
            readerIndex_ = kCheapPrepend;
            writerIndex_ = kCheapPrepend;
        }
        /* 从readable空间取走所有数据, 转换为字符串返回 */
        std::string retrieveAllAsString()
        {
            return retrieveAsString(readableBytes());
        }
        /* 从readable空间头部取走长度len byte的数据, 转换为字符串返回 */
        std::string retrieveAsString(size_t len)
        {
            assert(len <= readableBytes());
            string result(peek(), len);
            retrieve(len);
            return result;
        }
    

    readInt*取数据

    readInt系列函数从readable空间读取指定长度(类型)的数据,不仅从readable空间读取数据,还会利用相应的retrieve函数把数据从中取走,导致readable空间变小。

        /* 从readable空间头部读取一个int64_类型数, 由网络字节序转换为本地字节序 */
        /**
         * Read int64_t from network endian
         *
         * Require: buf->readableBytes() >= sizeof(int64_t)
         */
        int64_t readInt64()
        {
            int64_t result = peekInt64();
            retrieveInt64();
            return result;
        }
        /* 从readable空间头部读取一个int32_类型数, 由网络字节序转换为本地字节序 */
        int32_t readInt32()
        {
            int32_t result = peekInt32();
            retrieveInt32();
            return result;
        }
        /* 从readable空间头部读取一个int16_类型数, 由网络字节序转换为本地字节序 */
        int16_t readInt16()
        {
            int16_t result = peekInt16();
            retrieveInt16();
            return result;
        }
        /* 从readable空间头部读取一个int8_类型数, 由网络字节序转换为本地字节序 */
        int8_t readInt8()
        {
            int8_t result = peekInt8();
            retrieveInt8();
            return result;
        }
    

    peek*读取而不取走缓冲区数据

    peek系列函数只从readable空间头部(peek())读取数据,而不取走数据,不会导致readable空间变化。

        /* 从readable的头部peek()读取一个int64_t数据, 但不移动readerIndex_, 不会改变readable空间 */
        /**
         * Peek int64_t from network endian
         *
         * Require: buf->readableBytes() >= sizeof(int64_t)
         */
        int64_t peekInt64() const
        {
            assert(readableBytes() >= sizeof(int64_t));
            int64_t be64 = 0;
            ::memcpy(&be64, peek(), sizeof(be64));
            return sockets::networkToHost64(be64); // 网络字节序转换为本地字节序
        }
        /* 从readable的头部peek()读取一个int32_t数据, 但不移动readerIndex_, 不会改变readable空间 */
        int32_t peekInt32() const
        {
            assert(readableBytes() >= sizeof(int32_t));
            int32_t be32 = 0;
            ::memcpy(&be32, peek(), sizeof(be32));
            return sockets::networkToHost32(be32); // 网络字节序转换为本地字节序
        }
        /* 从readable的头部peek()读取一个int16_t数据, 但不移动readerIndex_, 不会改变readable空间 */
        int16_t peekInt16() const
        {
            assert(readableBytes() >= sizeof(int16_t));
            int16_t be16 = 0;
            ::memcpy(&be16, peek(), sizeof(be16));
            return sockets::networkToHost16(be16); // 网络字节序转换为本地字节序
        }
        /* 从readable的头部peek()读取一个int8_t数据, 但不移动readerIndex_, 不会改变readable空间.
         * 1byte数据不存在字节序问题 */
        int8_t peekInt8() const
        {
            assert(readableBytes() >= sizeof(int8_t));
            int8_t x = *peek();
            return x;
        }
    

    prepend*预置数据到缓冲区

    prepend系列函数将预置指定长度数据到prependable空间,但不会改变prependable空间大小。

        /* 在prependable空间末尾预置int64_t类型网络字节序的数x, 预置数会被转化为本地字节序 */
        /**
         * Prepend int64_t using network endian
         */
         void prependInt64(int64_t x)
        {
             int64_t be64 = sockets::hostToNetwork64(x);
             prepend(&be64, sizeof(be64));
        }
        /* 在prependable空间末尾预置int32_t类型网络字节序的数x, 预置数会被转化为本地字节序 */
        /**
         * Prepend int32_t using network endian
         */
        void prependInt32(int32_t x)
        {
            int64_t be32 = sockets::hostToNetwork32(x);
            prepend(&be32, sizeof(be32));
        }
        /* 在prependable空间末尾预置int16_t类型网络字节序的数x, 预置数会被转化为本地字节序 */
        void prependInt16(int16_t x)
        {
            int16_t be16 = sockets::hostToNetwork16(x);
            prepend(&be16, sizeof(be16));
        }
        /* 在prependable空间末尾预置int8_t类型网络字节序的数x, 预置数会被转化为本地字节序 */
        void prependInt8(int8_t x)
        {
            prepend(&x, sizeof(x));
        }
        /* 在prependable空间末尾预置一组二进制数据data[len].
         * 表面上看是加入prependable空间末尾, 实际上是加入readable开头, 会导致readerIndex_变化 */
        void prepend(const void* data, size_t len)
        {
            assert(len <= prependableBytes());
            readerIndex_ -= len;
            const char* d = static_cast<const char*>(data);
            std::copy(d, d+len, begin()+readerIndex_);
        }
    

    内部缓冲区操作

    包含3个操作:
    1)shrink自动收缩内部缓冲区(buffer_)大小;
    2)返回buffer_的capaticy;
    3)makeSpace生产足够大小的writable空间,以写入新的len byte数据;

        /* 收缩缓冲区空间, 将缓冲区中数据拷贝到新缓冲区, 确保writable空间最终大小为reserve */
        void shrink(size_t reserve)
        {
            // FIXME: use vector::shrink_to_fit() in C++ 11 if possible.
            Buffer other;
            other.ensureWritableBytes(readableBytes() + reserve);
            other.append(toStringPiece());
            swap(other);
        }
    
        /* 返回buffer_的容量capacity() */
        size_t internalCapacity() const
        {
            return buffer_.capacity();
        }
    
        /* writable空间不足以写入len byte数据时,
         * 1)如果writable空间 + prependable空间不足以存放数据, 就resize 申请新的更大的内部缓冲区buffer_
         * 2)如果足以存放数据, 就将prependable多余空间腾挪出来, 合并到writable空间 */
        void makeSpace(size_t len)
        {
            if (writableBytes() + prependableBytes() < len + kCheapPrepend)
            { // writable 空间大小 + prependable空间大小 不足以存放len byte数据, resize内部缓冲区大小
                // FIXME: move readable data
                buffer_.resize(writerIndex_ + len);
            }
            else
            { // writable 空间大小 + prependable空间大小 足以存放len byte数据, 移动readable空间数据, 合并多余prependable空间到writable空间
                // TODO: ???
                // move readable data to the front, make space inside buffer
                assert(kCheapPrepend < readerIndex_);
                size_t readable = readableBytes();
                std::copy(begin() + readerIndex_,
                          begin() + writerIndex_,
                          begin() + kCheapPrepend);
                readerIndex_ = kCheapPrepend;
                writerIndex_ = readerIndex_ + readable;
                assert(readable == readableBytes());
            }
        }
    

    readFd从指定fd读取数据

    readFd从指定连接对应fd读取数据,当读取的数据超过内部缓冲区writable空间大小时,采用的策略是先用一个64K栈缓存extrabuf临时存储,然后根据需要合并prependable空间到writable空间,或者resize buffer_大小。

    /**
    * 从fd读取数据到内部缓冲区, 将系统调用错误保存至savedErrno
    * @param 要读取的fd, 通常是代表连接的conn fd
    * @param savedErrno[out] 保存的错误号
    * @return 读取数据结果. < 0, 发生错误; >= 成功, 读取到的字节数
    */
    ssize_t Buffer::readFd(int fd, int* savedErrno)
    {
        // saved an ioctl()/FIONREAD call to tell how much to read.
        char extrabuf[65536]; // 65536 = 64K bytes
        struct iovec vec[2];
        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;
        const ssize_t n = sockets::readv(fd, vec, iovcnt);
        if (n < 0)
        { // ::readv系统调用错误
            *savedErrno = errno;
        }
        else if (implicit_cast<size_t>(n) <= writable)
        {
            writerIndex_ += n;
        }
        else
        {// 读取的数据超过现有内部buffer_的writable空间大小时, 启用备用的extrabuf 64KB空间, 并将这些数据添加到内部buffer_的末尾
            // 过程可能会合并多余prependable空间或resize buffer_大小, 以腾出足够writable空间存放数据
            // n >= 0 and n > writable
            // => buffer_ is full, then append extrabuf to buffer_
            writerIndex_ = buffer_.size();
            append(extrabuf, n - writable);
        }
        return n;
    }
    

    readable空间特定字符串查找

    有时,用户需要先对readable空间中的字符进行专门的查找,如找CRLF,EOL,而不取走数据,根据查找结果再决定取走多少数据。muduo库提供了几个辅助函数,便于用户进行这样的读取操作。

        /* 在readable空间找CRLF位置, 返回第一个出现CRLF的位置 */
        // CR = '\r', LF = '\n'
        const char* findCRLF() const
        {
            // FIXME: replace with memmem()?
            const char* crlf = std::search(peek(), beginWrite(), kCRLF, kCRLF+2);
            return crlf == beginWrite() ? NULL : crlf;
        }
    
        /* 在start~writable首地址 之间找CRLF, 要求start在readable地址空间中 */
        // CR = '\r', LF = '\n'
        const char* findCRLF(const char* start) const
        {
            assert(peek() <= start);
            assert(start <= beginWrite());
            // FIXME: replace with memmem()?
            const char* crlf = std::search(start, beginWrite(), kCRLF, kCRLF+2);
            return crlf == beginWrite() ? NULL : crlf;
        }
    
        /* 在readable空间中找EOL, 即LF('\n') */
        // EOL = '\n'
        // find end of line ('\n') from range [peek(), end)
        const char* findEOL() const
        {
            const void* eol = memchr(peek(), '\n', readableBytes());
            return static_cast<const char*>(eol);
        }
    
        /* 在start~writable首地址 之间找EOL, 要求start在readable地址空间中 */
        // EOL = '\n
        // find end of line ('\n') from range [start(), end)
        // @require peek() < start
        const char* findEOL(const char* start) const
        {
            assert(peek() <= start);
            assert(start <= beginWrite());
            const void* eol = memchr(start, '\n', beginWrite() - start);
            return static_cast<const char*>(eol);
        }
    

    测试Buffer类

    主要测试几个public 接口:append, retrieve, peekInt, readInt*, findEOL/findCRLF, std::move。
    测试的时候,主要通过观察prependable, readable, writable 这3个空间的大小变化,看是否等于预期值。这样可以实现自动化比较、测试。

    // test append data to writable space
    // test retrieve data from readable space
    BOOST_AUTO_TEST_CASE(testBufferAppendRetrieve)
    {
        // check constructor
        Buffer buf;
        BOOST_CHECK_EQUAL(buf.readableBytes(), 0);
        BOOST_CHECK_EQUAL(buf.writableBytes(), Buffer::kInitialSize);
        BOOST_CHECK_EQUAL(buf.prependableBytes(), Buffer::kCheapPrepend);
    
        // check append string
        const string str(200, 'x');
        buf.append(str);
        BOOST_CHECK_EQUAL(buf.readableBytes(), str.size());
        BOOST_CHECK_EQUAL(buf.writableBytes(), Buffer::kInitialSize - str.size());
        BOOST_CHECK_EQUAL(buf.prependableBytes(), Buffer::kCheapPrepend);
    
        // check retrieveAsString(num)
        const string str2 = buf.retrieveAsString(50);
        BOOST_CHECK_EQUAL(str2.size(), 50);
        BOOST_CHECK_EQUAL(buf.readableBytes(), str.size() - str2.size());
        BOOST_CHECK_EQUAL(buf.writableBytes(), Buffer::kInitialSize - str.size());
        BOOST_CHECK_EQUAL(buf.prependableBytes(), Buffer::kCheapPrepend + str2.size());
        BOOST_CHECK_EQUAL(str2, string(50, 'x'));
    
        // check append string
        buf.append(str);
        BOOST_CHECK_EQUAL(buf.readableBytes(), 2*str.size() - str2.size());
        BOOST_CHECK_EQUAL(buf.writableBytes(), Buffer::kInitialSize - 2*str.size());
        BOOST_CHECK_EQUAL(buf.prependableBytes(), Buffer::kCheapPrepend + str2.size());
    
        // check retrieveAllAsString()
        const string str3 =  buf.retrieveAllAsString();
        BOOST_CHECK_EQUAL(str3.size(), 350);
        BOOST_CHECK_EQUAL(buf.readableBytes(), 0);
        BOOST_CHECK_EQUAL(buf.writableBytes(), Buffer::kInitialSize);
        BOOST_CHECK_EQUAL(buf.prependableBytes(), Buffer::kCheapPrepend);
        BOOST_CHECK_EQUAL(str3, string(350, 'x'));
    }
    
    // test resize buffer size
    BOOST_AUTO_TEST_CASE(testBufferGrow)
    {
        // check buffer fileSize after ctor
        Buffer buf;
        buf.append(string(400, 'y'));
        BOOST_CHECK_EQUAL(buf.readableBytes(), 400);
        BOOST_CHECK_EQUAL(buf.writableBytes(), Buffer::kInitialSize-400);
    
        // check buffer fileSize after retrieve(num)
        buf.retrieve(50);
        BOOST_CHECK_EQUAL(buf.readableBytes(), 350);
        BOOST_CHECK_EQUAL(buf.writableBytes(), Buffer::kInitialSize-400);
        BOOST_CHECK_EQUAL(buf.prependableBytes(), Buffer::kCheapPrepend+50);
    
        // check buffer fileSize after append
        buf.append(string(1000, 'z'));
        BOOST_CHECK_EQUAL(buf.readableBytes(), 1350);
        BOOST_CHECK_EQUAL(buf.writableBytes(), 0);
        BOOST_CHECK_EQUAL(buf.prependableBytes(), Buffer::kCheapPrepend+50); // FIXME
    
        // check buffer fileSize after retrieveAll()
        buf.retrieveAll();
        BOOST_CHECK_EQUAL(buf.readableBytes(), 0);
        BOOST_CHECK_EQUAL(buf.writableBytes(), 1400); // FIXME
        BOOST_CHECK_EQUAL(buf.prependableBytes(), Buffer::kCheapPrepend);
    }
    
    // test prependable, readable, writable size
    // after makeSpace() growing inside writable size.
    // inside grow type : merge prependable space with writable space
    BOOST_AUTO_TEST_CASE(testBufferInsideGrow)
    {
        // check buffer fileSize after ctor
        Buffer buf;
        buf.append(string(800, 'y'));
        BOOST_CHECK_EQUAL(buf.readableBytes(), 800);
        BOOST_CHECK_EQUAL(buf.writableBytes(), Buffer::kInitialSize-800);
    
        // check buffer fileSize after retrieve(num)
        buf.retrieve(500);
        BOOST_CHECK_EQUAL(buf.readableBytes(), 300);
        BOOST_CHECK_EQUAL(buf.writableBytes(), Buffer::kInitialSize-800);
        BOOST_CHECK_EQUAL(buf.prependableBytes(), Buffer::kCheapPrepend+500);
    
        // check buffer fileSize after append string
        buf.append(string(300, 'z'));
        BOOST_CHECK_EQUAL(buf.readableBytes(), 600);
        BOOST_CHECK_EQUAL(buf.writableBytes(), Buffer::kInitialSize-600);
        BOOST_CHECK_EQUAL(buf.prependableBytes(), Buffer::kCheapPrepend);
    }
    
    /* test shrink */
    BOOST_AUTO_TEST_CASE(testBufferShrink)
    {
        // check buffer fileSize after ctor
        Buffer buf;
        buf.append(string(2000, 'y'));
        BOOST_CHECK_EQUAL(buf.readableBytes(), 2000);
        BOOST_CHECK_EQUAL(buf.writableBytes(), 0);
        BOOST_CHECK_EQUAL(buf.prependableBytes(), Buffer::kCheapPrepend);
    
        // check buffer fileSize after retrieve(num)
        buf.retrieve(1500);
        BOOST_CHECK_EQUAL(buf.readableBytes(), 500);
        BOOST_CHECK_EQUAL(buf.writableBytes(), 0);
        BOOST_CHECK_EQUAL(buf.prependableBytes(), Buffer::kCheapPrepend+1500);
    
        // check buffer fileSize after shrink string
        buf.shrink(0);
        BOOST_CHECK_EQUAL(buf.readableBytes(), 500);
        BOOST_CHECK_EQUAL(buf.writableBytes(), Buffer::kInitialSize-500);
        BOOST_CHECK_EQUAL(buf.retrieveAllAsString(), string(500, 'y'));
        BOOST_CHECK_EQUAL(buf.prependableBytes(), Buffer::kCheapPrepend);
    }
    
    // check buffer prepend
    BOOST_AUTO_TEST_CASE(testBufferPrepend)
    {
        Buffer buf;
        buf.append(string(200, 'y'));
        BOOST_CHECK_EQUAL(buf.readableBytes(), 200);
        BOOST_CHECK_EQUAL(buf.writableBytes(), Buffer::kInitialSize-200);
        BOOST_CHECK_EQUAL(buf.prependableBytes(), Buffer::kCheapPrepend);
    
        int x = 0;
        buf.prepend(&x, sizeof x);
        BOOST_CHECK_EQUAL(buf.readableBytes(), 204);
        BOOST_CHECK_EQUAL(buf.writableBytes(), Buffer::kInitialSize-200);
        BOOST_CHECK_EQUAL(buf.prependableBytes(), Buffer::kCheapPrepend - 4);
    }
    
    // test read int from readable segment
    BOOST_AUTO_TEST_CASE(testBufferReadInt)
    {
        Buffer buf;
        buf.append("HTTP");
    
        BOOST_CHECK_EQUAL(buf.readableBytes(), 4);
        BOOST_CHECK_EQUAL(buf.peekInt8(), 'H');
        int top16 = buf.peekInt16();
        BOOST_CHECK_EQUAL(top16, 'H'*256 + 'T');
        BOOST_CHECK_EQUAL(buf.peekInt32(), top16*65536 + 'T'*256 + 'P');
    
        BOOST_CHECK_EQUAL(buf.readInt8(), 'H');
        BOOST_CHECK_EQUAL(buf.readInt16(), 'T'*256 + 'T');
        BOOST_CHECK_EQUAL(buf.readInt8(), 'P');
        BOOST_CHECK_EQUAL(buf.readableBytes(), 0);
        BOOST_CHECK_EQUAL(buf.writableBytes(), Buffer::kInitialSize);
    
        buf.appendInt8(-1);
        buf.appendInt16(-2);
        buf.appendInt32(-3);
        BOOST_CHECK_EQUAL(buf.readableBytes(), 7);
        BOOST_CHECK_EQUAL(buf.readInt8(), -1);
        BOOST_CHECK_EQUAL(buf.readInt16(), -2);
        BOOST_CHECK_EQUAL(buf.readInt32(), -3);
    }
    
    // test find EOL
    BOOST_AUTO_TEST_CASE(testBufferFindEOL)
    {
        Buffer buf;
        buf.append(string(100000, 'x'));
        const char* null = NULL;
        BOOST_CHECK_EQUAL(buf.findEOL(), null);
        BOOST_CHECK_EQUAL(buf.findEOL(buf.peek()+90000), null);
    }
    
    void output(Buffer&& buf, const void* inner)
    {
        Buffer newbuf(std::move(buf)); // move ctor
        // printf("New Buffer at %p, inner %p\n", &newbuf, newbuf.peek());
        BOOST_CHECK_EQUAL(inner, newbuf.peek());
    }
    
    // test std::move for Buffer
    // NOTE: This test fails in g++ 4.4, passes in g++ 4.6.
    BOOST_AUTO_TEST_CASE(testMove)
    {
        Buffer buf;
        buf.append("muduo", 5);
        const void* inner = buf.peek();
        // printf("Buffer at %p, inner %p\n", &buf, inner);
        output(std::move(buf), inner);
    }
    

    注意:Buffer没有自定义move ctor,采用的是编译器合成的默认版本。


    知识点

    大小端转换

    网络字节序默认大端,本地字节序可能大端,也可能小端。网络字节序和本地字节序,可以用htobe* / betoh* 系列函数转换,也可以用htonl/htons/ntohl/ntohs转换,前者是glibc 2.9的部分,属于BSDs标准,后者属于POSIX.1-2001标准。也就是说,如果要开发可移植的程序,使用htonl/htons/ntohl/ntohs系列函数转换;否则,如果只考虑特定平台如Linux,可以使用htobe* / betoh* 系列函数转换。

    htobe* / betoh* 系列函数:
    支持16bit、32bit、64bit 字节序的数据转换

           #define _BSD_SOURCE             /* See feature_test_macros(7) */
           #include <endian.h>
    
           uint16_t htobe16(uint16_t host_16bits);
           uint16_t htole16(uint16_t host_16bits);
           uint16_t be16toh(uint16_t big_endian_16bits);
           uint16_t le16toh(uint16_t little_endian_16bits);
    
           uint32_t htobe32(uint32_t host_32bits);
           uint32_t htole32(uint32_t host_32bits);
           uint32_t be32toh(uint32_t big_endian_32bits);
           uint32_t le32toh(uint32_t little_endian_32bits);
    
           uint64_t htobe64(uint64_t host_64bits);
           uint64_t htole64(uint64_t host_64bits);
           uint64_t be64toh(uint64_t big_endian_64bits);
           uint64_t le64toh(uint64_t little_endian_64bits);
    

    htonl/htons/ntohl/ntohs系列函数:
    只支持16bit、32bit 字节序的数据转换

           #include <arpa/inet.h>
    
           uint32_t htonl(uint32_t hostlong);
           uint16_t htons(uint16_t hostshort);
           uint32_t ntohl(uint32_t netlong);
           uint16_t ntohs(uint16_t netshort);
    

    参考

    Muduo 网络库使用手册 陈硕

  • 相关阅读:
    Excel中substitute替换函数的使用方法
    如何在Excel中提取小数点后面的数字?
    提升单元测试体验的利器--Mockito使用总结
    SpringMVC项目读取不到外部CSS文件的解决办法及总结
    java8 Lambda表达式的新手上车指南(1)--基础语法和函数式接口
    Spring-data-redis操作redis知识总结
    优雅高效的MyBatis-Plus工具快速入门使用
    Thrift入门初探(2)--thrift基础知识详解
    Thrift入门初探--thrift安装及java入门实例
    spring事件驱动模型--观察者模式在spring中的应用
  • 原文地址:https://www.cnblogs.com/fortunely/p/16136071.html
Copyright © 2020-2023  润新知