Epoll-ET模式下非阻塞读写之Buffer的封装

    xiaoxiao2021-12-14  19

    先说说Epoll的ET模式

    epoll默认的模式是LT,要说ET不得不提到LT,LT与ET的区别可以用一句话概括: LT模式下只要socket处于可读状态(添加EPOLLIN事件时)或可写状态(添加EPOLLOUT事件时),就会一直返回其socket。 ET模式下在第一次返回socket后,只有当socket由不可写到可写(添加EPOLLIN事件时)或由不可读到可读(添加EPOLLOUT事件时),才会返回其socket。

    进一步捋一下

    由上可得,我们对ET操作必须要做到以下条件: 如果读:必须要将缓冲区的内容全部读出,即读到缓冲区空为止 如果写:一直写,知道需写的数据写完或是缓冲区满为止。 要做到以上要求,我们必须使用非阻塞套接字,否则socket会常常处在阻塞情况下,从而导致其他套接字饿死的情况发生。(何谓饿死,程序阻塞在当前套接字的操作上,而其他套接字根本没有机会进行操作)

    为什么我们需要Buffer

    TCP 是一个无边界的字节流协议,接收方必须要处理“收到的数据尚不构成一条完整的消息”和“一次收到两条消息的数据”等等情况。 如果是阻塞模式下,上面的情况根本不需要考虑,因为只要你recv,数据最终一定会过来,但代价就是你会一直阻塞在那里,我们编写服务器当然要尽可能的避免这种情况的出现,所以我们使用非阻塞套接字。 然而,非阻塞模式下,我们就必须要解决上面的情况。 那么,想想,如果给你一个内存空间,你读取数据时,无论取多少,都先放进这个空间,而要处理时从这个空间取数据即可。 有了这个空间,无论是消息不完整还是多个消息,我们都得以解决。 写也是一样的,当你向socket发送数据时,如果发送了一部分,缓冲区满了,此时我们该怎么办呢,只好一直阻塞,直至socket变回可写,再将剩余的数据全部写入。显然,代价还是阻塞,如果我们不想阻塞,仍然需要一个类似上面的空间。 这个空间,就是我们说的buffer,也可以叫做用户缓冲区。

    Buffer的实现思路

    首先,我们希望保证任何时候向buffer里面添加数据,都可以添加成功,也就是说,我们的buffer的空间需要足够大,但我们又不能确定一个固定的数值,因为如果数字设定的小了,还是会出现添加失败的情况,但如果设置的大了,又会导致大量空间的浪费。 所以我们将空间设置为可变的,用vector来保存,因为vector空间增长是以2的幂的形式扩展,很高效。 用两个指针或是变量来作为读标志和写标志,如下图所示。图片摘自Muduo 设计与实现之一:Buffer 类的设计) 这张图画的相当的清晰明了,一目了然,就不再具体描述了。 另外有两点优化,第一点是当Buffer内没有数据的时候(也就是readindex=writeindex时),要将两个标志全部归零,以免空间一直无限制增长下去,前面的空间反而浪费了。 第二点是,要添加数据时,如果剩余的空间不够(writeable),而加上前面空闲的空间(prependable+writeable)能够放下的话,将数据移动到buffer起始位置,以避免一次空间的增长。

    代码

    // // Buffer.h // QuoridorServer // // Created by shiyi on 2016/12/2. // Copyright © 2016年 shiyi. All rights reserved. // #ifndef Buffer_H #define Buffer_H #include <stdio.h> #include <iostream> #include <vector> using namespace std; class Buffer { public: Buffer() : m_widx(0), m_ridx(0) {} ~Buffer(){} void init() { m_widx = m_ridx = 0; m_buf.clear(); } //增加内容 void PutData(char *data, int len) { //如果调整空间后足够存放,则进行调整 int capa = m_buf.capacity(); if(capa < m_widx + len && capa > len + m_widx - m_ridx) adjust(); for(int i = 0; i < len; i++) m_buf.push_back(data[i]); m_widx += len; } //返回获取的包的大小,数据不完整返回-1 int GetData(char* data, int len) { len = min(m_widx-m_ridx, len); for(int i = 0; i < len; i++) { if(m_ridx >= m_widx) break; data[i] = m_buf[m_ridx++]; } if(m_ridx >= m_widx) { m_ridx = m_widx = 0; m_buf.clear(); } return len; } private: //将数据移至容器头部,充分利用空间 void adjust() { vector<char> t(m_buf.begin()+m_ridx, m_buf.begin()+m_widx); m_widx -= m_ridx; m_ridx = 0; m_buf.clear(); for(int i=0; i<m_widx; i++) m_buf.push_back(t[i]); } private: int m_ridx; int m_widx; std::vector<char> m_buf; }; #endif /* Buffer_H */

    参考博客 Muduo 设计与实现之一:Buffer 类的设计

    转载请注明原文地址: https://ju.6miu.com/read-968169.html

    最新回复(0)