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我用的也不是很熟,只能多解决报错来慢慢刷经验了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值