http服务器_简易Buffer设计与C++实现(ref muduo)
一、Buffer缓冲区存在的意义
高性能http服务器常见的模型就是基于event loop的reactor反应堆模型,采用IO 复用(linux一般是用epoll) + non-blocking IO 网络编程提高性能。non-blocking编程思想的核心在于避免线程阻塞在read、write等系统调用上,否则对之后的event就不能及时做出响应。IO线程只能阻塞在IO复用的函数上,如epoll_wait,所以每个TCP连接都必须要有input buffer和output buffer。
例如程序想通过TCP连接发送一个100KB的数据,但是在write函数中,系统只接受了60KB的数据(TCP滑动窗口机制),这时候程序不应持续等待,而是应该尽快返回event loop防止对于后续事件没有响应,对于剩下的40KB数据该作何处理呢?此时这些数据应该保存在output buffer中,然后注册EPOLLOUT事件,等待下次可写,然后发送出去。
二、Buffer缓冲区的功能需求
(1)连续内存方便读写和管理,结构尽量简单
(2)长度可变,可以适应不同大小的消息
(3)尽量减少缓冲区占用的内存
三、Buffer缓冲区的实现代码
#ifndef __BUFFER_H__
#define __BUFFER_H__
#include <vector>
#include <string>
#include <iostream>
#include <algorithm>
#include <cassert>
#define INIT_SIZE 1024
namespace huhu{
class Buffer{
public:
Buffer();
~Buffer();
//default copy constructor and assign func.
size_t readableBytes() const;//可读字节数
size_t writableBytes() const;//可写字节数
size_t prependableBytes() const;//先前字节数
//first readable pos
const char* peek() const;//第一个可读位置
//在可读区域中取出字节
void retrieveLen(size_t len);//取出len个字节
void retrieveToEnd(const char* end);//一直取出字节直到end
void retrieveAll();//取出所有字节
std::string retrieveAllAsString();//以字符串方式取出所有字节
//添加字节到缓冲区可写区域中
void append(const std::string& str);
void append(const char* data, size_t len);
void append(const void* data, size_t len);
void append(const Buffer& other_buff);
void ensureWritableBytes(size_t len);//确保空间足够
char* beginWrite();//返回第一个可写位置
const char* beginWrite() const;
void hasWritten(size_t len);//调整可写位置
//通过缓冲区读写fd
ssize_t readFd(int fd, int* savedErrno);
ssize_t writeFd(int fd, int* saveErrno);
const char* findCRLF() const;
const char* findCRLF(const char* start) const;
private:
char* __begin();//返回buffer起始地址
const char* __begin() const;
void __makeSpace(size_t len);//调整空间
private:
std::vector<char> m_buffer;//为了便于动态调整大小,使用vector<char>
size_t m_read_index;//读下标
size_t m_write_index;//写下标
};//class Buffer
}//namespace huhu
#endif//__BUFFER_H__
#include "../include/Buffer.h"
#include <cstring> // perror
#include <iostream>
#include <errno.h>
#include <unistd.h> // write
#include <sys/uio.h> // readv
using namespace huhu;
Buffer::Buffer()
:m_buffer(INIT_SIZE), \
m_read_index(0), \
m_write_index(0){
assert(readableBytes() == 0);
assert(writableBytes() == INIT_SIZE);
}
Buffer::~Buffer(){}
size_t Buffer::readableBytes() const{
return m_write_index - m_read_index;
}
size_t Buffer::writableBytes() const{
return m_buffer.size() - m_write_index;
}
size_t Buffer::prependableBytes() const{
return m_read_index;
}
//first readable position
const char* Buffer::peek() const {
return __begin() + m_read_index;
}
void Buffer::retrieveLen(size_t len){
assert(len <= readableBytes());
m_read_index += len;
}
void Buffer::retrieveToEnd(const char* end){
assert(peek() <= end);
assert(end <= beginWrite());
retrieveLen(end - peek());
}
void Buffer::retrieveAll(){
m_read_index = 0;
m_write_index = 0;
}
std::string Buffer::retrieveAllAsString(){
std::string str(peek(), readableBytes());
retrieveAll();
return str;
}
void Buffer::append(const std::string& str){
append(str.data(), str.size());
}
void Buffer::append(const char* data, size_t len){
ensureWritableBytes(len);
std::copy(data, data + len, beginWrite());
hasWritten(len);
}
void Buffer::append(const void* data, size_t len){
append(static_cast<const char*>(data), len);
}
void Buffer::append(const Buffer& other_buff){
append(other_buff.peek(), other_buff.readableBytes());
}
void Buffer::ensureWritableBytes(size_t len){
if(writableBytes() < len){
__makeSpace(len);
}
assert(writableBytes() >= len);
}
char* Buffer::beginWrite(){
return __begin() + m_write_index;
}
const char* Buffer::beginWrite() const{
return __begin() + m_write_index;
}
void Buffer::hasWritten(size_t len){
m_write_index += len;
}
ssize_t Buffer::readFd(int fd, int* savedErrno){
// ref muduo buffer.p204[chenshuo]
char extrabuf[65536];
struct iovec vec[2];
const size_t writable = writableBytes();
vec[0].iov_base = __begin() + m_write_index;
vec[0].iov_len = writable;
vec[1].iov_base = extrabuf;
vec[1].iov_len = sizeof(extrabuf);
const ssize_t n = ::readv(fd, vec, 2);
if(n < 0) {
printf("[Buffer:readFd]fd = %d readv : %s\n", fd, strerror(errno));
*savedErrno = errno;
}
else if(static_cast<size_t>(n) <= writable)
m_write_index += n;
else {
m_write_index = m_buffer.size();
append(extrabuf, n - writable);
}
return n;
}
ssize_t Buffer::writeFd(int fd, int* savedErrno){
size_t nLeft = readableBytes();
char* bufPtr = __begin() + m_read_index;
ssize_t n;
if((n = ::write(fd, bufPtr, nLeft)) <= 0) {
if(n < 0 && n == EINTR)
return 0;
else {
printf("[Buffer:writeFd]fd = %d write : %s\n", fd, strerror(errno));
*savedErrno = errno;
return -1;
}
} else {
m_read_index += n;
return n;
}
}
const char* Buffer::findCRLF() const{
const char CRLF[] = "\r\n";
const char* crlf = std::search(peek(), beginWrite(), CRLF, CRLF + 2);
return crlf == beginWrite() ? nullptr : crlf;
}
const char* Buffer::findCRLF(const char* start) const{
assert(peek() <= start && start <= beginWrite());
const char CRLF[] = "\r\n";
const char* crlf = std::search(start, beginWrite(), CRLF, CRLF + 2);
return crlf == beginWrite() ? nullptr : crlf;
}
char* Buffer::__begin(){
return &*m_buffer.begin();
}
const char* Buffer::__begin() const{
return &*m_buffer.begin();
}
//空间配置
void Buffer::__makeSpace(size_t len){
if(writableBytes() + prependableBytes() < len){
m_buffer.resize(m_write_index + len);
}
else{
size_t readable = readableBytes();
std::copy(__begin() + m_read_index, \
__begin() + m_write_index, \
__begin());
m_read_index = 0;
m_write_index = m_read_index + readable;
assert(readable == readableBytes());
}
}
四、Buffer缓冲区的关键函数讲解
4.1 从Fd中读到缓冲区函数readFd
关于 struct iovec可以参考GNU readv/writev的相关文档
ssize_t Buffer::readFd(int fd, int* savedErrno){
// ref muduo buffer.p204[chenshuo]
char extrabuf[65536];//额外的临时栈空间
struct iovec vec[2];
const size_t writable = writableBytes();
vec[0].iov_base = __begin() + m_write_index;
vec[0].iov_len = writable;
vec[1].iov_base = extrabuf;
vec[1].iov_len = sizeof(extrabuf);
const ssize_t n = ::readv(fd, vec, 2);
if(n < 0) {
printf("[Buffer:readFd]fd = %d readv : %s\n", fd, strerror(errno));
*savedErrno = errno;
}
else if(static_cast<size_t>(n) <= writable)
m_write_index += n;
else {
m_write_index = m_buffer.size();
append(extrabuf, n - writable);
}
return n;
}
这里在栈上申请了一个大小为65536的临时空间,如果从fd中读入的数据不多,那么就全读入buffer,如果读入的数据超过了buffer的可写字节数writable,就会读到栈上的extrabuf中,然后再把栈里的数据append到buffer中(调整buffer空间再copy)。这样使得一开始buffer的空间不会太大造成浪费,也避免了多次进行read系统调用造成的开销(这里缓冲区较大,一般可以一次读完)。
4.2 空间配置函数__makeSpace
void Buffer::__makeSpace(size_t len){
if(writableBytes() + prependableBytes() < len){
m_buffer.resize(m_write_index + len);
}
else{
size_t readable = readableBytes();
std::copy(__begin() + m_read_index, \
__begin() + m_write_index, \
__begin());
m_read_index = 0;
m_write_index = m_read_index + readable;
assert(readable == readableBytes());
}
}
这里如果writable空间加上prependable空间大于等于len的话,就移动数据位置到缓冲区的最前面。
移动前:
移动后: