image-20250623005037935

GateServer

网关服务器主要应答客户端基本的连接请求,包括根据服务器负载情况选择合适服务器给客户端登录,注册,获取验证服务等,接收http请求并应答。

1.boost的安装

Boost是一个广泛使用的C++库集合,它提供了许多增强C++功能的工具和组件。Boost库包括了用于高性能计算、并发编程、数据结构、算法、正则表达式、数学、图形、网络编程等方面的工具。

特点

  • 跨平台支持:Boost可以在多个操作系统和编译器上使用,包括Windows、Linux、macOS等。
  • 广泛的标准库扩展:Boost库在很多方面补充和扩展了C++标准库,比如提供智能指针(boost::shared_ptr)和容器类型(boost::unordered_map)等。
  • 高效能:Boost的许多实现专注于性能,经过精心优化,适合高性能计算任务。
  • 与C++标准库兼容:许多Boost库的实现是C++标准库的一部分,因此它们的代码常常是可移植的,并且可以随着C++标准库的进化而优化。

常见的Boost库包括:

Boost.Asio:提供了异步输入输出(I/O)的支持,特别适用于网络编程。
Boost.Spirit:用于语法分析,能够帮助你解析和生成各种格式的文本数据。
Boost.Thread:提供多线程编程支持。
Boost.Filesystem:用于处理文件系统操作,如文件路径、目录遍历等。
Boost.Regex:提供正则表达式的处理功能。
Boost.Math:提供各种数学函数,特别是对于高精度数学计算非常有用。

2.beast实现http服务器

Beast 是 Boost 库中的一个组件,专门用于处理 HTTP 和 WebSocket 协议。它提供了一组高效且易于使用的工具,用于构建 HTTP 客户端和服务器应用,以及 WebSocket 客户端和服务器。Beast 结合了 Boost.Asio(用于异步 I/O)和 C++11/14/17 的特性,使得编写高效的网络应用程序变得更加简洁。

几个基本概念:
事件循环:事件循环(Event Loop)是一种编程模式,通常用于异步编程和并发程序设计。它的主要目的是在单线程环境下,通过不断地等待和处理事件来实现高效的 I/O 操作和任务调度
事件循环的工作原理可以简要描述为:

  • 初始化阶段:程序启动时,事件循环被创建并开始运行。它会持续监控和处理事件(例如用户输入、网络请求、定时任务等)。
  • 等待事件:事件循环会等待某些事件的发生,常见的事件类型有:
  • I/O 事件:如网络请求、文件读取、写入等。
  • 定时事件:如定时器超时、定期执行某个操作等。
  • 用户交互:如用户点击按钮、键盘输入等。
  • 处理事件:当事件发生时,事件循环会根据不同的事件类型调用相应的回调函数或处理逻辑。比如,当接收到网络数据时,它会触发一个回调函数来处理这些数据。
  • 重复循环:一旦当前事件处理完毕,事件循环会继续等待下一个事件。这个过程会一直持续,直到程序退出。
    回调函数能不能运行很久?

    回调函数不能运行很久的根本原因
    回调函数的执行时间问题与其在异步编程模型中的位置和作用有关。
    异步 I/O 模型中的回调:
    在 Boost.Asio 或 Node.js 等事件驱动的编程模型中,回调函数通常是由事件循环在事件发生时调度执行的。
    如果回调函数做了长时间的计算或等待(例如 I/O 操作、网络请求),它会阻塞事件循环或当前线程,这会导致 后续事件无法及时处理,影响程序的响应性和吞吐量。
    线程阻塞问题:
    如果回调函数运行时间过长(例如涉及大量计算),它会占用当前线程(或者线程池中的一个线程),导致该线程无法处理其他事件。如果这个线程是事件循环的核心线程(例如 io_context.run() 运行的线程),则所有后续事件都无法处理,这会导致系统卡顿或响应延迟。

添加一个Cserver类

作用:

代码说明

前置:enable_shared_from_this 是 C++ 标准库中的一个模板类,用于 允许一个对象通过 shared_ptr 获取指向自身的 shared_ptr,即使它最初是通过裸指针或其他方式创建的。

解释
enable_shared_from_this 是一个基类模板,当类继承自它时,该类的对象可以通过 shared_ptr 安全地访问自己。这是通过 shared_from_this() 成员函数实现的。
当对象是由 shared_ptr 管理时,它会保存一个引用计数,这样可以确保对象在不再被引用时被销毁。当你希望能够通过 shared_ptr 来管理对象并确保其生命周期时,enable_shared_from_this 提供了一个安全的方式来获取指向该对象的 shared_ptr。

CServer.h声明acceptor, 以及用于事件循环的上下文iocontext,和构造函数

class CServer:public std::enable_shared_from_this<CServer>
{
public:
CServer(boost::asio::io_context& ioc, unsigned short& port);
void Start();
private:
tcp::acceptor _acceptor;
net::io_context& _ioc;
boost::asio::ip::tcp::socket _socket;
};

CServer的构造函数

初始化了一个 TCP 服务器对象。构造函数的作用是通过 boost::asio::io_context 创建网络监听套接字(_acceptor),并准备好接收客户端连接。

//boost::asio::io_context& ioc:boost::asio::io_context& ioc:
//ioc 是 Boost.Asio 的核心对象,负责处理异步 I/O 操作。它管理异步任务的调度和事件循环,允许你注册网络事件(例如连接、数据读取、写入等)并在事件发生时调用回调函数。

unsigned short& port:
port 是要监听的端口号,类型为无符号短整数,通常用来指定 TCP 服务器接收连接的端口号(例如 8080)。
CServer::CServer(boost::asio::io_context& ioc, unsigned short& port) :_ioc(ioc),
_acceptor(ioc, tcp::endpoint(tcp::v4(), port)),_socket(ioc) {

}

成员初始化列表:
_ioc(ioc)
这行将构造函数参数 ioc 初始化为 CServer 类的成员变量 _ioc。通过这个成员变量,CServer 类的实例可以与 io_context 交互,注册和处理异步事件。

_acceptor(ioc, tcp::endpoint(tcp::v4(), port))
这一行代码创建了一个 tcp::acceptor 对象 _acceptor,用于监听传入的 TCP 连接。具体含义是:

tcp::acceptor 是 Boost.Asio 用于接收传入连接的类。

ioc:将 io_context 传递给 _acceptor,以便它可以将接收到的连接事件注册到事件循环中。

tcp::endpoint(tcp::v4(), port):创建一个 tcp::endpoint,即一个 IP 地址和端口的组合,用于指定监听的地址和端口。

tcp::v4():表示使用 IPv4 协议(你也可以使用 tcp::v6() 来选择 IPv6)。

port:表示监听的端口号(例如 8080)。
_socket(ioc) 这一行创建了一个 tcp::socket 对象 _socket,它表示与客户端建立连接的通信套接字。ioc 作为参数传递给 _socket,允许它将事件注册到事件循环中
_socket 还没有连接到任何客户端,它只是一个未连接的套接字,准备与客户端建立连接。

CServer

void CServer::Start()
{
auto self = shared_from_this();
_acceptor.async_accept(_socket, [self](beast::error_code ec) {
try {
//出错则放弃这个连接,继续监听新链接
if (ec) {
self->Start();
return;
}

//处理新链接,创建HpptConnection类管理新连接
std::make_shared<HttpConnection>(std::move(self->_socket))->Start();
//继续监听
self->Start();
}
catch (std::exception& exp) {
std::cout << "exception is " << exp.what() << std::endl;
self->Start();
}
});
}

HttpConnection

类文件
#include "const.h"

class HttpConnection: public std::enable_shared_from_this<HttpConnection>
{
friend class LogicSystem;
public:
HttpConnection(tcp::socket socket);
void Start();

private:
void CheckDeadline();
void WriteResponse();
void HandleReq();
tcp::socket _socket;
// The buffer for performing reads.
beast::flat_buffer _buffer{ 8192 };

// The request message.
http::request<http::dynamic_body> _request;

// The response message.
http::response<http::dynamic_body> _response;

// The timer for putting a deadline on connection processing.
net::steady_timer deadline_{
_socket.get_executor(), std::chrono::seconds(60) };
};
}
}
);
}

_buffer 用来接受数据

_request 用来解析请求

_response 用来回应客户端

_deadline 用来做定时器判断请求是否超时

start函数
void HttpConnection::Start()
{
auto self = shared_from_this();
http::async_read(_socket, _buffer, _request, [self](beast::error_code ec,
std::size_t bytes_transferred) {
try {
if (ec) {
std::cout << "http read err is " << ec.what() << std::endl;
return;
}

//处理读到的数据
boost::ignore_unused(bytes_transferred);
self->HandleReq();
self->CheckDeadline();
}
catch (std::exception& exp) {
std::cout << "exception is " << exp.what() << std::endl;
}
}
);
}

http::async_read(...) {...});
Boost.Beast 提供的函数,用于 异步读取 HTTP 请求,它做了几件事:
_socket:与客户端建立的 TCP 连接。
_buffer:一个 beast::flat_buffer 类型的缓冲区,用来临时存储接收到的数据。
_request:一个 http::requesthttp::string_body 类型的对象,用于解析并保存 HTTP 请求的内容。
lambda 回调函数:当请求读完后调用。
🟢 本质上:这一行代码表示 注册一个异步读操作,等客户端发来 HTTP 请求时再执行下面的回调函数。

实现HandleReq
void HttpConnection::HandleReq() {
//设置版本
_response.version(_request.version());
//设置为短链接
_response.keep_alive(false);

if (_request.method() == http::verb::get) {
bool success = LogicSystem::GetInstance()->HandleGet(_request.target(), shared_from_this());
if (!success) {
_response.result(http::status::not_found);
_response.set(http::field::content_type, "text/plain");
beast::ostream(_response.body()) << "url not found\r\n";
WriteResponse();
return;
}

_response.result(http::status::ok);
_response.set(http::field::server, "GateServer");
WriteResponse();
return;
}
}

为了方便我们先实现Get请求的处理,根据请求类型为get调用LogicSystem的HandleGet接口处理get请求.

LogicSystem

#include <memory>
#include <mutex>
#include <iostream>
template <typename T>
class Singleton {
protected:
Singleton() = default;
Singleton(const Singleton<T>&) = delete;
Singleton& operator=(const Singleton<T>& st) = delete;

static std::shared_ptr<T> _instance;
public:
static std::shared_ptr<T> GetInstance() {
static std::once_flag s_flag;
std::call_once(s_flag, [&]() {
_instance = shared_ptr<T>(new T);
});

return _instance;
}
void PrintAddress() {
std::cout << _instance.get() << endl;
}
~Singleton() {
std::cout << "this is singleton destruct" << std::endl;
}
};

template <typename T>
std::shared_ptr<T> Singleton<T>::_instance = nullptr;
#include "Singleton.h"
#include <functional>
#include <map>
#include "const.h"

class HttpConnection;
typedef std::function<void(std::shared_ptr<HttpConnection>)> HttpHandler;
class LogicSystem :public Singleton<LogicSystem>
{
friend class Singleton<LogicSystem>;
public:
~LogicSystem();
bool HandleGet(std::string, std::shared_ptr<HttpConnection>);
void RegGet(std::string, HttpHandler handler);
private:
LogicSystem();
std::map<std::string, HttpHandler> _post_handlers;
std::map<std::string, HttpHandler> _get_handlers;
};

_post_handlers和_get_handlers分别是post请求和get请求的回调函数map,key为路由,value为回调函数。

RegGet函数
void LogicSystem::RegGet(std::string url, HttpHandler handler) {
_get_handlers.insert(make_pair(url, handler));
}

在构造函数中实现具体的消息注册

LogicSystem::LogicSystem() {
RegGet("/get_test", [](std::shared_ptr<HttpConnection> connection) {
beast::ostream(connection->_response.body()) << "receive get_test req";
});
}
HandleGet函数
bool LogicSystem::HandleGet(std::string path, std::shared_ptr<HttpConnection> con) {
if (_get_handlers.find(path) == _get_handlers.end()) {
return false;
}

_get_handlers[path](con);
return true;
}

主函数

int main()
{
try
{
unsigned short port = static_cast<unsigned short>(8080);
net::io_context ioc{ 1 };
boost::asio::signal_set signals(ioc, SIGINT, SIGTERM);
//async_wait 方法等待这些信号的发生,并在收到信号时调用回调函数。
signals.async_wait([&ioc](const boost::system::error_code& error, int signal_number) {

if (error) {
return;
}
ioc.stop();
});
std::make_shared<CServer>(ioc, port)->Start();
ioc.run();
}
catch (std::exception const& e)
{
std::cerr << "Error: " << e.what() << std::endl;
return EXIT_FAILURE;
}
}

目前无法实现带参数的get请求,所以要加解析url的处理方法
在这里插入图片描述

解决参数解析的问题

URL 参数解析总结:

这段代码实现了 URL 编码URL 解码GET 请求的参数解析。我们可以将其分为几大部分:十六进制与十进制转换,URL 编码与解码,GET 请求的参数解析,以及如何在 HTTP 连接中实现这些操作。下面是整个流程和方法的总结:


1. 十进制字符转为十六进制字符

在 URL 编码中,字符需要转化为十六进制的 ASCII 码值,通常用于将特殊字符转换成 % 形式。例如,字符 'A' 转换成十六进制就是 %41

// 十进制转十六进制
unsigned char ToHex(unsigned char x) {
if (x >= 0 && x <= 9) return x + 48; // '0' to '9'
else return x + 55; // 'A' to 'F'
}
  • 对于 0-9 的数字,直接加 48,得到对应的 ASCII 码值。
  • 对于大写字母 A-Z 和小写字母 a-z,分别加上 55 和 87,以转换成相应的 ASCII 码。

2. 十六进制字符转为十进制字符

unsigned char FromHex(unsigned char x) {
unsigned char y;
if (x >= 'A' && x <= 'Z') y = x - 'A' + 10; // 'A' to 'Z'
else if (x >= 'a' && x <= 'z') y = x - 'a' + 10; // 'a' to 'z'
else if (x >= '0' && x <= '9') y = x - '0'; // '0' to '9'
else assert(0); // 非法字符
return y;
}
  • 这个函数将十六进制字符(如 'A''1')转为相应的十进制数值。

    • 'A''Z' 对应的是 10 到 15,使用 x - 'A' + 10 来处理。
    • '0''9' 则直接转换为 0 到 9。

3. URL 编码

UrlEncode 函数用于将字符串中的特殊字符编码为 % 加十六进制值(例如空格变为 +,特殊字符变为 %xx 格式)。

std::string UrlEncode(const std::string& str) {
std::string strTemp = "";
size_t length = str.length();
for (size_t i = 0; i < length; i++) {
if (isalnum((unsigned char)str[i]) ||
(str[i] == '-') || (str[i] == '_') ||
(str[i] == '.') || (str[i] == '~'))
strTemp += str[i]; // 如果是字母或数字,直接添加
else if (str[i] == ' ')
strTemp += "+"; // 空格编码为 "+"
else {
strTemp += '%';
strTemp += ToHex((unsigned char)str[i] >> 4); // 高四位
strTemp += ToHex((unsigned char)str[i] & 0x0F); // 低四位
}
}
return strTemp;
}
  • 如果字符是字母或数字,直接拼接。
  • 如果是空格,转换为 +
  • 对于其他字符,使用 % 加上该字符的 ASCII 码的十六进制表示。

4. URL 解码

UrlDecode 函数实现了 URL 解码,即将 URL 编码的字符串(如 %20)还原成原始字符。

std::string UrlDecode(const std::string& str) {
std::string strTemp = "";
size_t length = str.length();
for (size_t i = 0; i < length; i++) {
if (str[i] == '+') strTemp += ' '; // 将 '+' 转回空格
else if (str[i] == '%') {
assert(i + 2 < length);
unsigned char high = FromHex((unsigned char)str[++i]);
unsigned char low = FromHex((unsigned char)str[++i]);
strTemp += high * 16 + low; // 将百分号后的十六进制转换为字符
} else {
strTemp += str[i]; // 其他字符直接添加
}
}
return strTemp;
}
  • + 转换为空格。
  • 对于 %xx 格式的字符,使用 FromHex 将十六进制还原为原字符。

5. GET 请求参数解析

HttpConnection 类中,解析 URL 中的查询字符串(?key1=value1&key2=value2)并提取出参数键值对。

void HttpConnection::PreParseGetParam() {
auto uri = _request.target(); // 获取请求的 URI
auto query_pos = uri.find('?'); // 查找查询字符串的位置
if (query_pos == std::string::npos) {
_get_url = uri; // 没有查询字符串,直接保存 URI
return;
}

_get_url = uri.substr(0, query_pos); // 保存不包含查询参数的部分
std::string query_string = uri.substr(query_pos + 1); // 获取查询字符串
std::string key, value;
size_t pos = 0;

// 解析查询字符串
while ((pos = query_string.find('&')) != std::string::npos) {
auto pair = query_string.substr(0, pos);
size_t eq_pos = pair.find('=');
if (eq_pos != std::string::npos) {
key = UrlDecode(pair.substr(0, eq_pos)); // 解码键
value = UrlDecode(pair.substr(eq_pos + 1)); // 解码值
_get_params[key] = value; // 保存键值对
}
query_string.erase(0, pos + 1);
}

// 处理最后一个参数(如果没有 '&')
if (!query_string.empty()) {
size_t eq_pos = query_string.find('=');
if (eq_pos != std::string::npos) {
key = UrlDecode(query_string.substr(0, eq_pos));
value = UrlDecode(query_string.substr(eq_pos + 1));
_get_params[key] = value;
}
}
}
  • 该方法从请求中提取出 URL 和查询参数,使用 UrlDecode 解码键值对并保存到 _get_params 中。

6. 处理 GET 请求的逻辑

HandleReq 中,当收到 GET 请求时,调用 PreParseGetParam 解析查询参数,并将处理后的数据传递给 LogicSystem

void HttpConnection::HandleReq() {
if (_request.method() == http::verb::get) {
PreParseGetParam(); // 解析 GET 请求的查询参数
bool success = LogicSystem::GetInstance()->HandleGet(_get_url, shared_from_this()); // 处理 GET 请求
}
}

7. 处理 GET 请求的回调

LogicSystem::LogicSystem() {
RegGet("/get_test", [](std::shared_ptr<HttpConnection> connection) {
beast::ostream(connection->_response.body()) << "receive get_test req " << std::endl;
int i = 0;
for (auto& elem : connection->_get_params) {
i++;
beast::ostream(connection->_response.body()) << "param" << i << " key is " << elem.first;
beast::ostream(connection->_response.body()) << ", " << " value is " << elem.second << std::endl;
}
});
}
  • 通过 RegGet 注册一个回调函数,当请求路径是 /get_test 时,会返回请求的参数列表。

总结

  1. URL 编码与解码

    • 编码:将特殊字符转为 %xx 格式,空格转为 +
    • 解码:将 %xx 转换回字符,+ 转为空格。
  2. GET 请求参数解析

    • 提取查询字符串(?key1=value1&key2=value2),并将每对键值进行解码和存储。
  3. 请求处理

    • 通过 PreParseGetParam 解析 URL 查询参数,然后通过回调将参数传递给相应的处理函数。

这些方法有效地实现了 URL 的编码、解码以及 GET 请求参数的解析,为 HTTP 请求的处理提供了便利。如果你有进一步的问题,随时可以问我!

在这里插入图片描述

在这里插入图片描述

思考:

1.端口和socket的联系是什么

端口(port)和套接字(socket)是计算机网络中密切相关的两个概念,它们在网络通信中共同起作用。
端口(port)和套接字(socket)是计算机网络中密切相关的两个概念,它们在网络通信中共同起作用。下面从定义联系举例三个方面来说明:


一、基本定义

概念 定义
端口(Port) 用于区分同一主机中多个网络应用程序的“编号”,每个网络服务通常绑定一个特定的端口号。是一个 16 位无符号整数(范围 0 ~ 65535)。
套接字(Socket) 网络通信的端点,由 IP 地址 + 端口号 + 协议(TCP/UDP)三元组构成,可以看作是进程间通信的“插口”。

二、二者的联系

  1. Socket依赖端口来标识进程

    • 网络中的通信本质上是“进程与进程”的通信,而端口就是用来标识不同进程的。
    • Socket 是一个编程接口,用于创建连接,通过绑定(bind)端口才能监听或建立连接
  2. 完整的Socket地址 = IP + Port

    • 客户端连接服务器,其实就是连接“某台主机的某个端口”。
    • 比如:连接 192.168.1.100:80,表示访问该主机上监听 80 端口的服务(通常是 HTTP 服务)。
  3. 一个Socket可绑定一个端口,但一个端口只能绑定一个Socket(TCP)

    • TCP连接时,如果两个套接字绑定了同一个端口,系统会报错(除非设置SO_REUSEADDR)。

三、类比理解(便于初学者)

实体 类比 说明
IP地址 电话区号 表示是哪台机器
端口号 电话分机号 表示该机器上的哪一个服务
套接字 电话插孔 把IP和端口连接在一起,成为一个“可以打电话”的接口

四、示例说明

// 伪代码:创建一个 socket 并绑定端口
int sockfd = socket(AF_INET, SOCK_STREAM, 0); // 创建TCP套接字
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(8080); // 绑定端口8080
addr.sin_addr.s_addr = INADDR_ANY;

bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)); // 套接字绑定端口
listen(sockfd, SOMAXCONN); // 监听端口,等待连接

此处:

  • 端口:8080
  • Socket:sockfd 代表的 socket 绑定了这个端口
  • 服务程序就可以通过这个 socket 接收来自客户端的连接

五、总结一句话

端口是数字,用来区分不同进程;Socket 是编程接口,通过绑定端口来实现网络通信。

如需深入了解 Socket 编程模型端口复用TCP 四元组,我可以进一步讲解。

2.为什么使用 enable_shared_from_this

  • 确保对象的生命周期:在成员函数中需要使用 shared_ptr 来保持对象的生命周期。例如,可能需要将对象传递给其他函数或线程,确保对象在没有其他 shared_ptr 引用时被销毁。
  • 避免悬挂指针:如果通过裸指针返回指向对象的 shared_ptr,可能会导致悬挂指针(指向已销毁对象的指针)。enable_shared_from_this 确保了通过 shared_ptr 返回对象的正确生命周期管理。
  • 异步操作是延迟执行的,如果没有保留对当前对象的引用,那么在回调触发前对象可能已经销毁,会造成访问空悬指针、崩溃等问题。
    你的问题非常好,让我们一步一步地来彻底解释这句代码:

3. 什么是 io_context

boost::asio::io_context 是 Boost.Asio 的事件调度器,它的作用是:

管理和调度所有异步任务,比如 async_accept()async_read()async_write() 等。
本质上它就是一个事件循环机制

它类似一个“任务大本营”或“事件处理中心”。


4. 那 ioc{ 1 } 中的 1 是什么意思?

io_context ioc{ 1 }; 中的 1 不是指定线程数,而是初始化 io_context 内部任务计数器(work count)的方式。并告诉它:我大概只会用 1 个线程来调用 run() |