目录
未完待续......
学习笔记,仅作交流。如有谬误,敬请指正。
1、项目介绍
本项目实现一个基于从属Reactor模式的高性能并发服务器,并且该服务器可以单独作为一个网络库组件,组件使用者可以利用该网络库组件方便地实现各种各样的服务器。
服务器使用到epoll多路转接模型,并且工作在ET模式下。
2、项目部署
操作系统:Ubuntu(只要支持C++11的正则库)
3、项目开发过程
3.1网络库模块开发
3.1.1日志宏
日志信息分级:FATAL(致命错误)、ERROR(一般错误)、WARN(警告)、INFO(一般信息)、DEBUG(调试信息)
server.hpp
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#define NORMAL 0 // 正常
#define DEBUG 1 // 调试
#define ERROR 2 // 错误
#define LOG_LEVEL DEBUG// 控制输出
#define LOG(level,format,...) do{\
if(level < LOG_LEVEL) break;\
time_t t = time(nullptr);\
struct tm *ltm = localtime(&t);\
char tmp[32] = {0};\
strftime(tmp,sizeof(tmp) - 1,"%H:%M:%S",ltm);\
fprintf(stdout,"[thread:%p]--[%s]--[file:%s|line:%d]=> " format "\n",(void *)pthread_self(),tmp,__FILE__,__LINE__,##__VA_ARGS__);\
}while(0)
#define NORMAL_LOG(format,...) LOG(NORMAL,format,##__VA_ARGS__)
#define DEBUG_LOG(format,...) LOG(DEBUG,format,##__VA_ARGS__)
#define ERROR_LOG(format,...) LOG(ERROR,format,##__VA_ARGS__)
该日志宏使用fprintf,可以将日志输出到文件上。该日志的输出格式为:
[线程地址]–[时:分:秒]–[file:发生日志输出的文件名|line:发生日志输出的行号]=> 输出内容
这段代码的作用在于控制日志的输出,即不符合等级的日志输出统统不输出。
if(level < LOG_LEVEL) break;\
3.1.2Buffer模块
TCP通信的数据都会被放在套接字的缓冲区当中,但是套接字的缓冲区是有大小限制的,尽管开发者可以控制这些缓冲区的大小,但是这样做很没必要。
可以直接在应用层再提供一层缓冲区,这里把它叫做Buffer。。Buffer的作用就是一个处于应用层的缓冲区,它的容量可变,为组件使用者提供一个方便、灵活的缓冲区。
Buffer.hpp
#ifndef BUFFER_HPP
#define BUFFER_HPP
#include <vector>
#include <string>
#include <algorithm>
#include <cstdint>
#define BUFFER_DEFAULT_SIZE 1024
class Buffer
{
public:
Buffer();
char *Begin();//获取缓冲区的起始地址
char *WritePosition();//获取有效数据的结束位置,也就是新数据写入的位置
char *ReadPosition();//有效位置的起始位置,也就是读取数据的起始位置
uint64_t TailFreeSize();/ 获取_writer之后的空闲空间大小
uint64_t HeadFreeSize();// 获取_reader之前的空间空间大小
uint64_t ReadAbleSize();// 获取可读数据大小
void OffsetReader(uint64_t len);// _reader向后移动,说明有数据被读走
void OffsetWriter(uint64_t len);// _writer向后移动,说明有新数据写入
void EnsureWriteSpace(uint64_t len);//确保空间大小足够容纳新数据
void Write(const void *data, uint64_t len);// 向Buffer写入数据
void WriteAndPush(const void *data, uint64_t len);// 向Buffer写入并且造成_wirter偏移
void WriteString(const std::string &data);// 向Buffer写入string对象
void WriteStringAndPush(const std::string &data);// 写入string对象并造成_writer偏移
void WriteBuffer(Buffer &data);// 写入Buffer对象
void WriteBufferAndPush(Buffer &data);// 写入Buffer对象并造成_writer偏移
void Read(void *buf, uint64_t len);// 只能读取有效的数据
void ReadAndPop(void *buf, uint64_t len);// 读取数据并且移动_reader,即从Buffer当中删除数据
std::string ReadAsString(uint64_t len);// 读取len个数据,在该函数内部封装成string对象返回出去
std::string ReadAsStringAndPop(uint64_t len);
char *FindEndOfLine();// 寻找一行的结束标志'\n'
std::string GetLine();// 获取一行数据
std::string GetLineAndPop();
void Clear();
private:
uint67_t _reader;//有效数据的起始位置
uint64_t _writer;//有效数据的结束位置
std::vector<char> _buffer;//使用vector进行空间管理
};
#endif // BUFFER_H
Buffer.cpp
#include "Buffer.hpp"
#include <cstdlib>
#include <cstring>
#include <cstdio>
Buffer::Buffer()
: _reader(0), _writer(0), _buffer(BUFFER_DEFAULT_SIZE)
{}//这个初始化列表的顺序与头文件类中的声明顺序有关,不然会有警告
char *Buffer::Begin() { return &(*(_buffer.begin())); }
char *Buffer::WritePosition() { return Begin() + _writer; }
char *Buffer::ReadPosition() { return Begin() + _reader; }
uint64_t Buffer::TailFreeSize() { return _buffer.size() - _writer; }
uint64_t Buffer::HeadFreeSize() { return _reader; }
uint64_t Buffer::ReadAbleSize() { return _writer - _reader; }
void Buffer::OffsetReader(uint64_t len)
{
if (len == 0) return;
// 最大范围是和_writer处于同一位置,说明Buffer为空;如果超过_writer,就是未定义的行为
if (len > ReadAbleSize()) abort();
_reader += len;
}
void Buffer::OffsetWriter(uint64_t len)
{
if (len == 0) return;
// 最多移动到当前_buffer的最大容量处,一旦超出就可能造成越界访问
if (len > TailFreeSize()) abort();
_writer += len;
}
void Buffer::EnsureWriteSpace(uint64_t len)
{
if (TailFreeSize() >= len) return;//_writer尾部有足够的空间容纳新数据
// _reader之前、_writer之后的空间足够容纳新数据
if (TailFreeSize() + HeadFreeSize() >= len)
{
uint64_t oldsize = ReadAbleSize();// 保存当前有效数据大小
std::copy(ReadPosition(), ReadPosition() + oldsize, Begin());// 将数据往前挪动
_reader = 0; _writer = oldsize;
}
else
{
_buffer.resize(_writer + len);
}
}
void Buffer::Write(const void *data, uint64_t len)
{
if (len == 0) return;
EnsureWriteSpace(len);
const char *d = (const char *)data;
std::copy(d, d + len, WritePosition());// 将[d,d+len]这段区间的数据拷贝到_writer指向的位置之后
}
void Buffer::WriteAndPush(const void *data, uint64_t len)
{
Write(data, len);
OffsetWriter(len);
}
void Buffer::WriteString(const std::string &data)
{
Write(data.c_str(), data.size());
}
void Buffer::WriteStringAndPush(const std::string &data)
{
WriteString(data);
OffsetWriter(data.size());
}
void Buffer::WriteBuffer(Buffer &data)
{
Write(data.ReadPosition(), data.ReadAbleSize());
}
void Buffer::WriteBufferAndPush(Buffer &data)
{
WriteBuffer(data);
OffsetWriter(data.ReadAbleSize());
}
void Buffer::Read(void *buf, uint64_t len)
{
if (len > ReadAbleSize()) abort();
std::copy(ReadPosition(), ReadPosition() + len, (char *)buf);
}
void Buffer::ReadAndPop(void *buf, uint64_t len)
{
Read(buf, len);
OffsetReader(len);
}
std::string Buffer::ReadAsString(uint64_t len)
{
if (len > ReadAbleSize()) abort();
std::string str;
str.resize(len);
Read(&str[0], len);
return str;
}
std::string Buffer::ReadAsStringAndPop(uint64_t len)
{
if (len > ReadAbleSize()) abort();
std::string str = ReadAsString(len);
OffsetReader(len);
return str;
//return std::move(str);
//std::move是将对象的状态或者所有权从一个对象转移到另一个对象,只是转移,没有内存的搬迁或者内存拷贝。
//在处理大型对象时深拷贝可能非常低效,此时可以用移动语义,即将资源的所有权从一个对象转移给另一个对象,无需进行数据的实际复制
}
char *Buffer::FindEndOfLine()// 寻找一行的结束标志'\n'
{
char *res = (char *)memchr(ReadPosition(), '\n', ReadAbleSize());
return res;
}
std::string Buffer::GetLine()// 获取一行数据
{
char *pos = FindEndOfLine();
if (pos == nullptr) return "";
return ReadAsString(pos - ReadPosition() + 1);/ +1是为了将'\n'一并返回
}
std::string Buffer::GetLineAndPop()
{
std::string str = GetLine();
OffsetReader(str.size());
return str;
}
void Buffer::Clear()// 清空
{
_reader = 0;
_writer = 0;
}
Buffer扩容机制
3.1.3Socket模块
Socket模块是将套接字的过程封装起来(有阻塞和非阻塞两种模式进行读取和发送数据;增加端口复用功能)。
Socket.hpp
#ifndef SOCKET_HPP
#define SOCKET_HPP
#include <string>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <cerrno>
#include <fcntl.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#define MAX_LISTEN 64
class Socket
{
public:
Socket(int sockfd = -1);
~Socket();
int GetFd();// 获取套接字文件描述符
bool Create()