说明
TcpConnection有两个缓冲区,一个输入缓冲区,一个输出缓冲区。
提出问题:为什么non-blocking网络编程中应用层buffer是必须的
outputBuffer必须有 | 场景:程序想通过tcp连接发送100k的数据,但是write调用时操作系统只接受了80k(受内核发送缓冲区影响),你肯定不想原地等待。应该快速交出线程控制权。返回eventloop。 |
inputBuffer必须有 | tcp是一个无边界的字节流协议。接收方必须处理两种情况。1. 收到的数据尚不构成一条完整的信息 2.一次收到两条消息的数据。 网络库处理socket可读事件,必须一次性的把内核buffer读到应用层buffer,否则会反复触发POLLIN事件(LT模式)造成busy-loop,那么必然出现“数据不完整的情况",所以先放到inputBuffer中等构成一条完整的消息再通知程序的业务逻辑(这通常是codec的职责) |
其次,必须先理解缓冲区的结构,其他操作都是基于结构的。只有先理解结构,其他问题迎刃而解。陈硕已经在注释中写的很清楚了。
/// +-------------------+------------------+------------------+
/// | prependable bytes | readable bytes | writable bytes |
/// | | (CONTENT) | |
/// +-------------------+------------------+------------------+
/// | | | |
/// 0 <= readerIndex <= writerIndex <= size
三个区域被两个index切分:
1. prependable区域:留白方便数据插入后前叉长度等数据使用。
2. readable区域:可读区域,处于readerIndex和writerIndex之间。
3. writable区域:可写区域,写数据和追加数据从writerIndex开始。
提出问题:为什么使用index而不使用迭代器呢?
因为缓冲区维护的是vector, 会自动扩容,扩容后迭代器失效。用index更能方便表示可读可写的具体位置。
成员变量
重点就三个:
1. buffer:缓存容器
2. readerIndex:读位置下标
3. writerIndex: 写位置下标
std::vector<char> buffer_; //缓存容器
size_t readerIndex_; //读位置下标
size_t writerIndex_; //写位置下标
static const char kCRLF[]; //就是存储’\r\n’
方法
1. readFd: 最重要的一个函数。通过将缓冲区容器和栈上数组合成一个IO向量数组,使用readv往向量数组读取数据。小于当前缓冲区容量则改变writerIndex_即可,大于的话就把栈上的剩余数据append到缓冲区。提高了内存使用效率,如果一开始就直接分配64k空间的话,可能用不到,就浪费空间了
补充:readv读取数据到Io向量数组,会将数据按io向量容器次序依次放入数据。
// 从套接字读取数据然后添加到缓冲区中,使用栈上的空间,避免内存使用过大,并且一次尽可能读取更多的数据
// vec[2]为两块缓冲区,第一块指向写的位置,第二块指向栈上的缓冲区,如果第一块缓冲区足够容纳缓冲区数据,就直接返回了.否则才使用第二块缓冲区,即栈上的空间,
// 在append()到缓冲区,这样就提高了内存使用效率,如果一开始就直接分配64k空间的话,可能用不到,就浪费空间了.
// 这里使用了自己封装的readv,实际还是调用标准的readv(),这里的第三个参数是指明有多少块内存数据需要从fd读出.如果可写空间<64k的话,这里就填2,否则就填1.
// 这里这个判断不咋理解,不明白为什么这样判断
ssize_t Buffer::readFd(int fd, int* savedErrno)
{
// saved an ioctl()/FIONREAD call to tell how much to read
char extrabuf[65536];
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;
// when there is enough space in this buffer, don't read into extrabuf.
// when extrabuf is used, we read 128k-1 bytes at most.
const int iovcnt = (writable < sizeof extrabuf) ? 2 : 1;
const ssize_t n = sockets::readv(fd, vec, iovcnt);
if (n < 0)
{
*savedErrno = errno;
}
else if (implicit_cast<size_t>(n) <= writable)
{
writerIndex_ += n;
}
else
{
writerIndex_ = buffer_.size();
append(extrabuf, n - writable);
}
// if (n == writable + sizeof extrabuf)
// {
// goto line_30;
// }
return n;
}
剩下的方法都是在基本结构上进行操作,看注释即可
// implicit copy-ctor, move-ctor, dtor and assignment are fine
// NOTE: implicit move-ctor is added in g++ 4.6
//交换缓冲区
void swap(Buffer& rhs)
{
buffer_.swap(rhs.buffer_);
std::swap(readerIndex_, rhs.readerIndex_);
std::swap(writerIndex_, rhs.writerIndex_);
}
//可读的数据大小,写位置-读位置
size_t readableBytes() const
{ return writerIndex_ - readerIndex_; }
//可写的空间大小,buffer_大小-写位置
size_t writableBytes() const
{ return buffer_.size() - writerIndex_; }
//返回readerIndex_,表示之前有多大的空间是空的
size_t prependableBytes() const
{ return readerIndex_; }
//返回读位置的指针
const char* peek() const
{ return begin() + readerIndex_; }
//这个就是在读位置和写位置之间的数据中找’\r\n’换行,找到的话返回这个指针,没找到返回NULL
const char* findCRLF() const
{
// FIXME: replace with memmem()?
const char* crlf = std::search(peek(), beginWrite(), kCRLF, kCRLF+2);
return crlf == beginWrite() ? NULL : crlf;
}
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;
}
//从start位置开始找,功能如上
const char* findEOL() const
{
const void* eol = memchr(peek(), '\n', readableBytes());
return static_cast<const char*>(eol);
}
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);
}
// 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())
{
readerIndex_ += len;
}
else
{
retrieveAll();
}
}
//这个参数为指针,把这个指针之前的数据读取完,移动读指针
void retrieveUntil(const char* end)
{
assert(peek() <= end);
assert(end <= beginWrite());
retrieve(end - peek());
}
// 下面面四个分别是取出8,4,2,1字节,retrieve*系列函数都比较简单
void retrieveInt64()
{
retrieve(sizeof(int64_t));
}
void retrieveInt32()
{
retrieve(sizeof(int32_t));
}
void retrieveInt16()
{
retrieve(sizeof(int16_t));
}
void retrieveInt8()
{
retrieve(sizeof(int8_t));
}
//把读写下标置为初始位置
void retrieveAll()
{
readerIndex_ = kCheapPrepend;
writerIndex_ = kCheapPrepend;
}
//以下两个,是吧读取的数据转换成字符串返回,并调用retrieve移动读下标
string retrieveAllAsString()
{
return retrieveAsString(readableBytes());
}
string retrieveAsString(size_t len)
{
assert(len <= readableBytes());
string result(peek(), len);
retrieve(len);
return result;
}
//把可读数据转换为StringPiece类型返回
StringPiece toStringPiece() const
{
return StringPiece(peek(), static_cast<int>(readableBytes()));
}
//以下三个就是把数据添加到buffer_缓冲区
void append(const StringPiece& str)
{
append(str.data(), str.size());
}
void append(const char* /*restrict*/ data, size_t len)
{
ensureWritableBytes(len);
std::copy(data, data+len, beginWrite());
hasWritten(len);
}
void append(const void* /*restrict*/ data, size_t len)
{
append(static_cast<const char*>(data), len);
}
//就是确保缓冲区空间够写,动态增长空间
void ensureWritableBytes(size_t len)
{
if (writableBytes() < len)
{
makeSpace(len);
}
assert(writableBytes() >= len);
}
//返回写位置的指针
char* beginWrite()
{ return begin() + writerIndex_; }
const char* beginWrite() const
{ return begin() + writerIndex_; }
//移动写位置下标,+len
void hasWritten(size_t len)
{
assert(len <= writableBytes());
writerIndex_ += len;
}
//写位置下标-len
void unwrite(size_t len)
{
assert(len <= readableBytes());
writerIndex_ -= len;
}
///
/// Append int64_t using network endian
///
//以下四个把主机字节序的转换成网络字节序追加到缓冲区后面
void appendInt64(int64_t x)
{
int64_t be64 = sockets::hostToNetwork64(x);
append(&be64, sizeof be64);
}
///
/// Append int32_t using network endian
///
void appendInt32(int32_t x)
{
int32_t be32 = sockets::hostToNetwork32(x);
append(&be32, sizeof be32);
}
void appendInt16(int16_t x)
{
int16_t be16 = sockets::hostToNetwork16(x);
append(&be16, sizeof be16);
}
void appendInt8(int8_t x)
{
append(&x, sizeof x);
}
///
/// Read int64_t from network endian
///
/// Require: buf->readableBytes() >= sizeof(int32_t)
//以下4个读取这些类型的数据返回,并移动读下标,代码比较好理解
int64_t readInt64()
{
int64_t result = peekInt64();
retrieveInt64();
return result;
}
///
/// Read int32_t from network endian
///
/// Require: buf->readableBytes() >= sizeof(int32_t)
int32_t readInt32()
{
int32_t result = peekInt32();
retrieveInt32();
return result;
}
int16_t readInt16()
{
int16_t result = peekInt16();
retrieveInt16();
return result;
}
int8_t readInt8()
{
int8_t result = peekInt8();
retrieveInt8();
return result;
}
///
/// Peek int64_t from network endian
///
/// Require: buf->readableBytes() >= sizeof(int64_t)
//以下4个把可读缓冲区中相应字节大小的数据读出来返回
int64_t peekInt64() const
{
assert(readableBytes() >= sizeof(int64_t));
int64_t be64 = 0;
::memcpy(&be64, peek(), sizeof be64);
return sockets::networkToHost64(be64);
}
///
/// Peek int32_t from network endian
///
/// Require: buf->readableBytes() >= sizeof(int32_t)
int32_t peekInt32() const
{
assert(readableBytes() >= sizeof(int32_t));
int32_t be32 = 0;
::memcpy(&be32, peek(), sizeof be32);
return sockets::networkToHost32(be32);
}
int16_t peekInt16() const
{
assert(readableBytes() >= sizeof(int16_t));
int16_t be16 = 0;
::memcpy(&be16, peek(), sizeof be16);
return sockets::networkToHost16(be16);
}
int8_t peekInt8() const
{
assert(readableBytes() >= sizeof(int8_t));
int8_t x = *peek();
return x;
}
///
/// Prepend int64_t using network endian
///
//以下5个在可读数据前面增加一些数据
void prependInt64(int64_t x)
{
int64_t be64 = sockets::hostToNetwork64(x);
prepend(&be64, sizeof be64);
}
///
/// Prepend int32_t using network endian
///
void prependInt32(int32_t x)
{
int32_t be32 = sockets::hostToNetwork32(x);
prepend(&be32, sizeof be32);
}
void prependInt16(int16_t x)
{
int16_t be16 = sockets::hostToNetwork16(x);
prepend(&be16, sizeof be16);
}
void prependInt8(int8_t x)
{
prepend(&x, sizeof x);
}
void prepend(const void* /*restrict*/ data, size_t len)
{
assert(len <= prependableBytes());
readerIndex_ -= len;
const char* d = static_cast<const char*>(data);
std::copy(d, d+len, begin()+readerIndex_);
}
//缩小空间,使得空间为可读数据+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();
}
/// Read data directly into buffer.
///
/// It may implement with readv(2)
/// @return result of read(2), @c errno is saved
ssize_t readFd(int fd, int* savedErrno);
private:
//以下两个返回buffer_.begin()
char* begin()
{ return &*buffer_.begin(); }
const char* begin() const
{ return &*buffer_.begin(); }
//对空间的动态调整,如果读位置前面可用的空间+写位置后面可用的空间够用的话,把数据复制到开始,如果不够用的话,再在后面动态增加空间
void makeSpace(size_t len)
{
if (writableBytes() + prependableBytes() < len + kCheapPrepend)
{
// FIXME: move readable data
buffer_.resize(writerIndex_+len);
}
else
{
// 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());
}
}