欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 汽车 > 时评 > QT聊天项目DAY04

QT聊天项目DAY04

2025/4/19 13:02:56 来源:https://blog.csdn.net/yumianxiaolangju/article/details/147304785  浏览:    关键词:QT聊天项目DAY04

1. Beast实现 http Get 请求处理

1.1 新建CServer类

CServer类构造函数接受一个端口号,创建acceptor接受新到来的链接

#ifndef __CSERVER_H__
#define __CSERVER_H__#include <boost/beast/http.hpp>
#include <boost/beast.hpp>
#include <boost/asio.hpp>#include <memory>namespace beast = boost::beast;
namespace http = beast::http;
namespace net = boost::asio;
using tcp = boost::asio::ip::tcp;class CServer : public std::enable_shared_from_this<CServer>
{};#endif

1.2 编写头文件

1. ioc: 负责管理异步I/O操作的执行,处理所有的IO事件的循环

    port: 服务器需要监听的端口号

CServer(boost::asio::io_context& ioc, unsigned short& port);

2. 服务器的启动逻辑

void Start();

3. 接收TCP连接的对象,服务器通过acceptor来监听指定的端口,并等待客户端连接请求,当客户端连接到达时创建新的套接字(tcp::socket) 来与客户端进行通信

tcp::acceptor _acceptor;

4. 负责所有的I/O事件循环

net::io_context& _ioc;

5. 表示与客户端建立连接的TCP套接字,通过该套接字服务器可以发送和接受数据

tcp::socket _socket;

1.3 编写构造函数

1. 使用IPv4协议,该_accepter 会监听本机的某一个端口,等待客户端的连接

CServer::CServer(boost::asio::io_context& ioc, unsigned short& port):_ioc(ioc),_acceptor(ioc, tcp::endpoint(tcp::v4(), port)), _socket(ioc)
{}

1.4 服务器开始监听端口有没有新的连接到来

1. 服务器启动,为监听到的连接分配套接字,一个端口可以有多个连接,由于套接字是管理连接的对象,不存在拷贝构造为了获取这个连接的套接字则使用移动拷贝,从服务器那里取走该连接的管理权

关于移动拷贝构造和拷贝构造,就需要了解一下什么是左值引用什么是右值引用

简单的理解,可以取地址的是左值引用,不可以取地址的临时构造出来的值就是右值引用

所以拷贝构造就是复制对方的引用,双方都可以管理同一片内存地址(浅拷贝)或者复制资源并另外创建一片新的内存空间(深拷贝),移动拷贝就是将对方地址拿过来自己用,并将对方从原地址迁移出去;

// 浅拷贝
Shallow(const Shallow& other) {data = other.data; // 只是复制指针
}
// 深拷贝
Deep(const Deep& other) {data = new int(*(other.data)); // 分配新内存并复制内容
}

如果对方不能对自己取地址可以直接触发移动拷贝构造,直接接管对方的内存资源,如果对方可以对自己地址则需要使用std::move()将左值变为右值并接管对方的资源

void CServer::Start()
{shared_ptr<CServer> self = shared_from_this();_acceptor.async_accept([self](boost::system::error_code ec){try {if (ec){self->Start();											// 重新监听return;}make_shared<HttpConnection>(move(self->_socket))->Start();	// 移交连接的管理权,并监听IO事件self->Start();												// 继续监听}catch (const std::exception& e) {std::cerr << "Exception: " << e.what() << "\n";}});
}

1.5 创建HTTPConnect连接类

#ifndef GLOBALHEAD_H
#define GLOBALHEAD_H#include <boost/beast/http.hpp>
#include <boost/beast.hpp>
#include <boost/asio.hpp>#include <memory>namespace beast = boost::beast;
namespace http = beast::http;
namespace net = boost::asio;
using tcp = boost::asio::ip::tcp;#endif // GLOBALHEAD_H
#ifndef HTTPCONNECTION_H
#define HTTPCONNECTION_H#include "GlobalHead.h"class HttpConnection : public std::enable_shared_from_this<HttpConnection>
{
public:HttpConnection(tcp::socket socket);void Start();                                                   // 启动连接IO数据的监听private:void CheckDeadline();                                           // 检查连接是否超时void WriteResponse();                                           // 向客户端发送响应数据void HandleRequest();                                           // 处理客户端的请求数据private:tcp::socket _socket;                                            // 客户端连接套接字beast::flat_buffer _buffer{ 8192 };                             // 接收缓冲区http::request<http::dynamic_body> _request;                     // 请求数据http::response<http::dynamic_body> _response;                   // 响应数据net::steady_timer _deadline{ _socket.get_executor(), std::chrono::seconds(60) };                                 // 超时定时器
};#endif // HTTPCONNECTION_H

1. 启动连接的IO监听

_request 时Http的请求对象,代表着客户端请求的全部信息,包括请求方法,URL,版本,头部字段,正文等

一旦读取到数据,就会将其缓存到_buffer中,并把读取出来的HTTP请求解析并存进Http的请求对象_request中

/* 启动连接IO数据的监听 */
void HttpConnection::Start()
{shared_ptr<HttpConnection> self = shared_from_this();http::async_read(_socket, _buffer, _request, [self](beast::error_code ec, std::size_t bytes_transferred){try{if (ec){cout << "Http read error: " << ec.what() << endl;}boost::ignore_unused(bytes_transferred);						// 忽略掉未使用的变量self->HandleRequest();self->CheckDeadline();}catch (const std::exception& e){cout << "Exception: " << e.what() << endl;}});
}

2. 处理客户端发来的请求

判断是否是Get请求,如果是Get请求,会把请求的URL和处理当前连接的对象发送当成接口的入参去处理;

请求的URL和查询参数(_request.Target())

没有处理成功,返回404(响应状态码),然后设置HTTP的响应头部字段的Content-Type 是纯文本类型

如果是处理成功,设置状态码为200(OK),然后设置响应头为服务器的名称,即成功处理了GET请求,并回应给客户端200并附带了自定义的服务器名字段

/* 处理客户端的请求数据 */
void HttpConnection::HandleRequest()
{_response.version(_request.version());_response.keep_alive(false);													// 处理完该请求后断开连接if (_request.method() == http::verb::get)										// 如果是Http的Get请求{bool IsSucceed = LogicSystem::GetInstance()->HandleGet(_request.target(), shared_from_this());if (!IsSucceed){_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;}
}

3. 向客户端回送响应

如果发送数据成功直接断开连接,然后取消定时器的轮询

/* 向客户端发送响应数据 */
void HttpConnection::WriteResponse()
{shared_ptr<HttpConnection> self = shared_from_this();// 发送完数据的回调函数http::async_write(_socket, _response, [self](beast::error_code ec, std::size_t bytes_transferred){self->_socket.shutdown(tcp::socket::shutdown_send, ec);self->_deadline.cancel();});
}
/* 检查客户端连接是否超时 */
void HttpConnection::CheckDeadline()
{shared_ptr<HttpConnection> self = shared_from_this();_deadline.async_wait([self](beast::error_code ec){if (!ec){self->_socket.close();}});
}

定时器超时也会关掉连接

4.四次挥手

实际上在上述的代码里只做了第一次挥手,剩下的挥手过程都是操作系统帮忙做的

形象的比喻:打电话的时候,你说你说完了,就把电话挂上了,然后放下话筒,剩余的事情就不需要你再去做考虑了

而shutdown(send)只是关闭了发送端的连接,但是接收端的连接依旧保留,所以还是能够回送ACK确认的

无论是哪一方发送了断开另一方都会先回响应报文,因为这个时候对方可能还在处理发送方发送来的数据

2. 创建单例模板类

这一部分我在之前的文章中提及过

#include <memory>
#include <mutex>
#include <iostream>
using namespace std;
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;

3. 创建登录处理类

也就是客户端发来的Get请求

3.1 修改全局头文件

#ifndef GLOBALHEAD_H
#define GLOBALHEAD_H#include <boost/beast/http.hpp>
#include <boost/beast.hpp>
#include <boost/asio.hpp>#include <memory>#include <iostream>#include "Singletion.h"
#include <functional>
#include <map>namespace beast = boost::beast;
namespace http = beast::http;
namespace net = boost::asio;
using tcp = boost::asio::ip::tcp;#endif // GLOBALHEAD_H


3.2 实现头文件

#ifndef LOGICSYSTEM_H
#define LOGICSYSTEM_H
#include "GlobalHead.h"// 把函数地址当作变量类型来使用
class HttpConnection;
typedef std::function<void(std::shared_ptr<HttpConnection>)> HttpHandler;class LogicSystem : public std::enable_shared_from_this<LogicSystem>, public Singletion<LogicSystem>
{friend class Singletion<LogicSystem>;																// 为了访问该类的构造函数public:~LogicSystem();																						bool HandleGet(std::string, std::shared_ptr<HttpConnection>);void RegisterGet(std::string, HttpHandler);private:LogicSystem();std::map<std::string, HttpHandler> _postHandlers;std::map<std::string, HttpHandler> _getHandlers;
};#endif // LOGICSYSTEM_H

这里为什么用URL和请求参数来绑定对应的回调,由于回调函数写的不清楚,我也不知道它到底想干嘛,难不成URL还能复用不成?难道连接断开在重新连起来(客户端的同一个行为触发的)还能是同一个URL和请求参数不同?

#include "LogicSystem.h"
#include "HttpConnection.h"LogicSystem::LogicSystem()
{RegisterGet("/getTest", [](std::shared_ptr<HttpConnection> connetion){beast::ostream(connetion->_response.body()) << "receive getTest request";	// 向 Http响应体中写入内容});
}LogicSystem::~LogicSystem()
{}bool LogicSystem::HandleGet(std::string url, std::shared_ptr<HttpConnection> connection)
{if (_getHandlers.find(url) == _getHandlers.end())return false;_getHandlers[url](connection);return true;
}void LogicSystem::RegisterGet(std::string url, HttpHandler handler)
{_getHandlers[url] = handler;
}

不纠结这个了

4.编译

报错了,说的模板参数不匹配,问一下AI,应该传什么参数

直接用成员变量

ok,一切顺利,带上头文件,在编译一次,一切顺利

参数这种东西是记不住的,也没必要去记,反而报错类型好记,一般像模板实例上下文这样的,就是参数不匹配,问一下AI正确的参数类型是什么,补上就完事了

5. 关于Git的使用

对于Git我用的也不是很熟,只能多解决报错来慢慢刷经验了

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词