先说说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 */