C++ 网络编程(11)服务器逻辑层设计和消息完善
🎯 C++ 网络编程(11)服务器逻辑层设计和消息完善
📅 更新时间:2025年6月7日
🏷️ 标签:C++ | Boost.Asio | 网络编程 | tlv协议 | TCP
文章目录
- 前言
- 一、服务器架构设计
- 二、消息头完善
- 三、MsgNode.h
- 1.代码部分
- 2.代码讲解
- 3.MsgNode中类构造函数实现
- 4.注意点
- 四、Session类改写
- CSession.h
- CSession::Send
- CSession::HandleRead
- 总结
前言
提示:这里可以添加本文要记录的大概内容:
本文概述基于boost::asio实现的服务器逻辑层结构,并且完善之前设计的消息结构。因为为了简化粘包处理,我们简化了发送数据的结构,这次我们给出完整的消息设计,以及服务器架构设计
提示:以下是本篇文章正文内容,下面案例可供参考
一、服务器架构设计
我们接下来要设计的服务器结构是这样的
二、消息头完善
我们这次用一个完整的tlv协议的结果来实现,之前我们只有一个消息长度和消息内容
现在将其完善为如下的样子
为了减少耦合和歧义,我们重新设计消息节点
MsgNode
表示消息节点的基类,头部的消息用这个结构存储。
RecvNode
表示接收消息的节点。
SendNode
表示发送消息的节点。
我们将上述结构定义在MsgNode.h中
三、MsgNode.h
1.代码部分
#pragma once
#include
#include
#include
//基类
class MsgNode
{
friend class CSession;
public:
MsgNode(short max_len) :_total_len(max_len), _cur_len(0)
{
_data = new char[_total_len + 1]();
_data[_total_len] = ' ';
}
~MsgNode()
{
std::cout << "destruct MsgNode " << std::endl;
delete[]_data;
}
void Clear()
{
::memset(_data, 0, _total_len);
_cur_len = 0;
}
short _cur_len;//已经发送/接收的长度
short _total_len;
char* _data;
};
//接收数据
class RecvNode :public MsgNode
{
public:
RecvNode(short max_len, short msg_id);
private:
short _msg_id;
};
//发送数据
class SendNode :public MsgNode
{
public:
SendNode(const char* msg, short max_len, short msg_id);
private:
short _msg_id;
};
2.代码讲解
我们这里用MsgNode
作为基类
然后收发用两个类去实现,因为收信息和发信息都需要数组来存储,所以在基类构造函数中我们new 一个数组
同时收消息
,我们只知道消息id和长度,所以构造函数中就是这两个参数
但发信息
的时候,我们知道消息id 消息长度 消息首地址,所以有三个参数
同时在构造函数初始化的时候,我们直接把数据长度传给基类中构造即可
3.MsgNode中类构造函数实现
#include "MsgNode.h"
#include"const.h"
RecvNode::RecvNode(short max_len, short msg_id)
:MsgNode(max_len), _msg_id(msg_id)
{
}
SendNode::SendNode(const char* msg, short max_len, short msg_id)
:MsgNode(max_len + HEAD_TOTAL_LEN),_msg_id(msg_id)
{
//id
short msg_id_network =
boost::asio::detail::socket_ops::host_to_network_short(_msg_id);
memcpy(_data, &msg_id_network, HEAD_ID_LEN);
//长度
short msg_len_network =
boost::asio::detail::socket_ops::host_to_network_short(max_len);
memcpy(_data + HEAD_ID_LEN, &msg_len_network, HEAD_DATA_LEN);
//消息体
memcpy(_data + HEAD_TOTAL_LEN, msg, msg_len_network);
}
4.注意点
在发信息的构造函数中,别忘记要对消息id和消息长度转换字节序
//id
short msg_id_network =
boost::asio::detail::socket_ops::host_to_network_short(_msg_id);
//长度
short msg_len_network =
boost::asio::detail::socket_ops::host_to_network_short(max_len);
四、Session类改写
SendNode
发送节点构造时,先将id转为网络字节序,然后写入_data
数据域。
然后将要发送数据的长度转为大端字节序,写入_data
数据域,注意要偏移HEAD_ID_LEN
长度。
最后将要发送的数据msg写入_data
数据域,注意要偏移
HEAD_ID_LEN+HEAD_DATA_LEN
CSession.h
因为我们这里设置了三个类,不再是第八章的那样全部用一个类MsgNode了
所以我们这里要改一下
std::queue<shared_ptr<SendNode> > _send_que;
std::mutex _send_lock;
//收到的消息结构
std::shared_ptr<RecvNode> _recv_msg_node;
bool _b_head_parse;
//收到的头部结构
std::shared_ptr<MsgNode> _recv_head_node;
分别用SendNode
RecvNode
MsgNode
去表示收发数据和收发数据的头部
CSession::Send
这里在发送的时候我们需要把消息id也传进来
void CSession::Send(char* msg, short max_length, short msgid) {
std::lock_guard<std::mutex> lock(_send_lock);
int send_que_size = _send_que.size();
if (send_que_size > MAX_SENDQUE) {
std::cout << "session: " <<
_uuid << " send que fulled, size is " << MAX_SENDQUE << endl;
return;
}
_send_que.push(make_shared<SendNode>(msg, max_length, msgid));!!!!!!
if (send_que_size>0) {
return;
}
auto& msgnode = _send_que.front();
boost::asio::async_write(_socket, boost::asio::buffer
(msgnode->_data, msgnode->_total_len),
std::bind(&CSession::HandleWrite,
this, std::placeholders::_1, SharedSelf()));
}
基本上和第八章的一样,就改写了这里
_send_que.push(make_shared<SendNode>(msg, max_length, msgid));
CSession::HandleRead
在读数据进行解析的时候,要用以下的片段写法
比如一开始在判断收入数据不足头部大小的时候,记得是4字节,不再是2字节了
因为此时头部包含消息id
消息长度
所以这里用HEAD_TOTAL_LEN而不是HEAD_HEAD_LEN
//收到的数据不足头部大小
if (bytes_transferred + _recv_head_node->_cur_len < HEAD_TOTAL_LEN) {
..........
.........
........
.....
}
然后在调用Send发送回客户端的时候,我们需要对传入的消息id 和 消息长度这两个参数进行转为网络格式
//取id
msg_id = 0;
memcpy(&msg_id, _recv_head_node->_data, HEAD_ID_LEN);
msg_id = boost::asio::detail::socket_ops::host_to_network_short(msg_id);
总结
本文介绍了服务器逻辑和网络层的设计,并且基于这个架构,完善了消息发送结构,下一篇带着大家设计逻辑类和逻辑队列
❤️ 如果你觉得本文对你有帮助,欢迎点赞、评论与收藏。更多 c++ asio网络编程 开发知识,敬请关注后续更新!