Linux-C++开发项目:基于Reactor模式的高性能并发服务器(Ubuntu实现)———学习笔记

目录

1、项目介绍

2、项目部署

3、项目开发过程

3.1网络库模块开发

3.1.1日志宏

3.1.2Buffer模块

​编辑3.1.3Socket模块

3.1.4Channel模块

 3.1.5Poller模块

 3.1.6Timerwheel模块

 3.1.7EvenLoop模块

整合测试1

未完待续......


学习笔记,仅作交流。如有谬误,敬请指正。

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()
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值