Zinx框架-游戏服务器开发003:架构搭建-需求分析及TCP通信方式的实现

news2024/11/26 7:50:45

文章目录

  • 1 项目总体架构
  • 2 项目需求
    • 2.1 服务器职责
    • 2.2 消息的格式和定义
  • 3 基于Tcp连接的通信方式
    • 3.1 通道层实现GameChannel类
      • 3.1.1 TcpChannel类
      • 3.1.2 Tcp工厂类
      • 3.1.3 创建主函数,添加Tcp的监听套接字
      • 3.1.4 代码测试
    • 3.2 消息类的结构设计和实现
      • 3.2.1 消息的定义
      • 3.2.2 消息类-用户请求对象的创建
      • 3.2.3 protoc消息的创建
      • 3.2.4 消息对象的构造与解析
      • 3.2.5 代码测试-1
      • 3.2.6 报文里的多条请求
      • 3.2.7 Tcp报文粘包的处理
      • 3.2.8 数据包测试
        • 3.2.8.1 完整数据
        • 3.2.8.2 数据缺失和错误
      • 3.2.9 协议和通道相互绑定
        • 3.2.9.1 循环引用的问题
        • 3.2.9.1 相互绑定的实现
        • 3.2.9.3 代码测试

1 项目总体架构

在这里插入图片描述

2 项目需求

2.1 服务器职责

服务器职责(接收客户端数据,发送数据给客户端)

  • 新客户端连接后,向其发送ID和名称
  • 新客户端连接后,向其发送周围玩家的位置
  • 新客户端连接后,向周围玩家发送其位置
  • 收到客户端的移动信息后,向周围玩家发送其新位置
  • 收到客户端的移动信息后,向其发送周围新玩家位置
  • 收到客户端的聊天信息后,向所有玩家发送聊天内容
  • 客户端断开时,向周围玩家发送其断开的消息

2.2 消息的格式和定义

  • 消息定义

每一条服务器和客户端之前的消息都应该满足以下格式

消息内容的长度(4个字节,低字节在前)| 消息ID(4个字节,低字节在前)| 消息内容 |

消息以及其处理方式已经在客户端实现,本项目要实现的是服务器端的相关处理

  • 详细定义如下
消息ID消息内容发送方向客户端处理服务器处理
1玩家ID和玩家姓名S->C记录自己ID和姓名
2聊天内容C->S广播给所有玩家
3新位置C->S处理玩家位置更新后的信息同步
200玩家ID,聊天内容/初始位置/动作(预留)/新位置S->C根据子类型不通而不同
201玩家ID和玩家姓名S->C把该ID的玩家从画面中拿掉
202周围玩家们的位置S->C在画面中显示周围的玩家

3 基于Tcp连接的通信方式

3.1 通道层实现GameChannel类

3.1.1 TcpChannel类

  • 使用框架提供的Tcp通信类
  • 创建GameChannel类继承ZinxTcpData,重写GetInputNextStage函数,将tcp收到的数据交给协议对象解析

每个协议对象只处理本通道的协议数据

GameProtocol* m_proto = NULL; 

创建对象啊以后交给m_proto,通过该变量访问通道内的数据

AZinxHandler* GameChannel::GetInputNextStage(BytesMsg& _oInput)
{
	return m_proto;
}

3.1.2 Tcp工厂类

  • 创建GameChannelFac类用于创建基于连接的GameChannel对象
  • 因为玩家是通过tcp连接,所以tcp通道,协议对象,和玩家对象是一对一对一的绑定关系
  • 创建通道的时候,需要创建协议,并且绑定协议对象
ZinxTcpData* GameConnFact::CreateTcpDataChannel(int _fd)
{
/*创建tcp通道对象*/
	auto pChannel = new GameChannel(_fd);
/*创建协议对象*/
	auto pProtocol = new GameProtocol();
/*绑定协议对象*/
	pChannel->m_proto = pProtocol;
/*将协议对象添加到kernel, 注意参数需要为指针*/
	ZinxKernel::Zinx_Add_Proto(*pProtocol);
	return pChannel;
}

3.1.3 创建主函数,添加Tcp的监听套接字

#include "GameChannel.h"

int main()
{
	ZinxKernel::ZinxKernelInit();
	/*添加监听通道:需要端口号和连接*/
	ZinxKernel::Zinx_Add_Channel(*(new ZinxTCPListen(8899, new GameConnFact())));
	ZinxKernel::Zinx_Run();
	ZinxKernel::ZinxKernelFini();
}

3.1.4 代码测试

设置标准输入

UserData* GameProtocol::raw2request(std::string _szInput)
{
	cout << _szInput << endl;
	return nullptr;
}

在这里插入图片描述
在这里插入图片描述

3.2 消息类的结构设计和实现

3.2.1 消息的定义

//h
enum MSG_TYPE {
	MSG_TYPE_LOGIN_ID_NAME = 1,
	MSG_TYPE_CHAT_CONTENT = 2,
	MSG_TYPE_NEW_POSTION = 3,
	MSG_TYPE_BROADCAST = 200,
	MSG_TYPE_LOGOFF_ID_NAME = 201,
	MSG_TYPE_SRD_POSTION = 202
} enMsgType;

3.2.2 消息类-用户请求对象的创建

  • 一个类一个请求
//h
class GameMsg :
	public UserData
{
public:
	/*用户的请求信息*/
	google::protobuf::Message * pMsg = NULL;
	enum MSG_TYPE {
		MSG_TYPE_LOGIN_ID_NAME = 1,
		MSG_TYPE_CHAT_CONTENT = 2,
		MSG_TYPE_NEW_POSTION = 3,
		MSG_TYPE_BROADCAST = 200,
		MSG_TYPE_LOGOFF_ID_NAME = 201,
		MSG_TYPE_SRD_POSTION = 202
	} enMsgType;

	/*已知消息内容创建消息对象*/
	GameMsg(MSG_TYPE _type, google::protobuf::Message  * _pMsg);
	/*将字节流内容转换成消息结构*/
	GameMsg(MSG_TYPE _type, std::string _stream);

	/*序列化本消息*/
	std::string serialize();

	virtual ~GameMsg();
};
  • 一个消息类里应该要放多条请求,每个请求一条消息
class MultiMsg :public UserData {
public:
	std::list<GameMsg *> m_Msgs;
};

3.2.3 protoc消息的创建

protoc msg.proto --cpp_out=./
syntax="proto3";
package pb;

//无关选项,用于客户端
option csharp_namespace="Pb";

message SyncPid{
	int32 Pid=1;
	string Username=2;
}

message Player{
	int32 Pid=1;
	Position P=2;
	string Username=3;
}

message SyncPlayers{
	/*嵌套多个子消息类型Player的消息*/
	repeated Player ps=1;
}

message Position{
	float X=1;
	float Y=2;	
	float Z=3;	
	float V=4;
	int32 BloodValue=5;
}

message MovePackage{
	Position P=1;
	int32 ActionData=2;
}

message BroadCast{
	int32 Pid=1;
	int32 Tp=2;
	/*根据Tp不同,Broadcast消息会包含:
	  聊天内容(Content)或初始位置(P)或新位置P*/
	oneof Data{
		string Content=3;
		Position P=4;
		/*ActionData暂时预留*/
		int32 ActionData=5;
		}
	string Username=6;
}

message Talk{
	string Content=1;
}

3.2.4 消息对象的构造与解析

GameMsg::GameMsg(MSG_TYPE _type, std::string _stream) :enMsgType(_type)
{
	/*通过简单工厂构造具体的消息对象*/
	switch (_type)
	{
	case GameMsg::MSG_TYPE_LOGIN_ID_NAME:
		pMsg = new pb::SyncPid();
		break;
	case GameMsg::MSG_TYPE_CHAT_CONTENT:
		pMsg = new pb::Talk();
		break;
	case GameMsg::MSG_TYPE_NEW_POSTION:
		pMsg = new pb::Position();
		break;
	case GameMsg::MSG_TYPE_BROADCAST:
		pMsg = new pb::BroadCast();
		break;
	case GameMsg::MSG_TYPE_LOGOFF_ID_NAME:
		pMsg = new pb::SyncPid();
		break;
	case GameMsg::MSG_TYPE_SRD_POSTION:
		pMsg = new pb::SyncPlayers();
		break;
	default:
		break;
	}

	/*将参数解析成消息对象内容*/
	pMsg->ParseFromString(_stream);
}


std::string GameMsg::serialize()
{
	std::string ret;

	pMsg->SerializeToString(&ret);

	return ret;
}

3.2.5 代码测试-1

在这里插入图片描述

3.2.6 报文里的多条请求

//h
class MultiMsg :public UserData {
public:
	std::list<GameMsg*> m_Msgs; //注意此处要加命名空间
};
	MultiMsg* pRet = new MultiMsg(); //此时没有用户请求
	
	/*构造一条用户请求*/
	GameMsg* pMsg = new GameMsg((GameMsg::MSG_TYPE)id, szLast.substr(8, iLength)); // iLength是正文的长度
	pRet->m_Msgs.push_back(pMsg);

	//Debug打印每条请求
	for (auto single : pRet->m_Msgs)
	{
		cout << single->pMsg->Utf8DebugString() << endl;
	}

3.2.7 Tcp报文粘包的处理

添加数据头4+ID4+数据信息

UserData* GameProtocol::raw2request(std::string _szInput)
{
	MultiMsg* pRet = new MultiMsg(); //此时没有用户请求
	szLast.append(_szInput);

	while (1)
	{
		if (szLast.size() < 8)
		{
			break;
		}

		/*在前四个字节中读取消息内容长度*/
		int iLength = 0;
		iLength |= szLast[0] << 0;
		iLength |= szLast[1] << 8;
		iLength |= szLast[2] << 16;
		iLength |= szLast[3] << 24;
		/*中四个字节读类型id*/
		int id = 0;
		id |= szLast[4] << 0;
		id |= szLast[5] << 8;
		id |= szLast[6] << 16;
		id |= szLast[7] << 24;

		/*通过读到的长度判断后续报文是否合法*/
		if (szLast.size() - 8 < iLength)
		{
			/*本条报文还没够,啥都不干*/
			break;
		}

		/*构造一条用户请求*/
		GameMsg* pMsg = new GameMsg((GameMsg::MSG_TYPE)id, szLast.substr(8, iLength)); // iLength是正文的长度
		pRet->m_Msgs.push_back(pMsg);

		/*弹出已经处理成功的报文*/
		szLast.erase(0, 8 + iLength);
	}

	//Debug打印每条请求
	for (auto single : pRet->m_Msgs)
	{
		cout << single->pMsg->Utf8DebugString() << endl;
	}
	return pRet;
}


/*参数来自业务层,待发送的消息
返回值转换后的字节流*/
std::string * GameProtocol::response2raw(UserData & _oUserData)
{
	int iLength = 0;
	int id = 0;
	std::string MsgContent;

	GET_REF2DATA(GameMsg, oOutput, _oUserData);
	id = oOutput.enMsgType;
	MsgContent = oOutput.serialize();
	iLength = MsgContent.size();

	auto pret = new std::string();

	pret->push_back((iLength >> 0) & 0xff);
	pret->push_back((iLength >> 8) & 0xff);
	pret->push_back((iLength >> 16) & 0xff);
	pret->push_back((iLength >> 24) & 0xff);
	pret->push_back((id >> 0) & 0xff);
	pret->push_back((id >> 8) & 0xff);
	pret->push_back((id >> 16) & 0xff);
	pret->push_back((id >> 24) & 0xff);
	pret->append(MsgContent);

	return pret;
}

3.2.8 数据包测试

3.2.8.1 完整数据
08 00 00 00 01 00 00 00 08 01 12 04 74 65 73 74

08 00 00 00 - 前4个字节存储数据消息的长度,变量值是数据消息的长度为8个字节。
01 00 00 00 - 第5-8个字节存储的是用户的ID,变量值表示用户ID是1
08 01 12 04 74 65 73 74 - 末尾8个字节表示数据消息的全部内容
在这里插入图片描述

在这里插入图片描述

3.2.8.2 数据缺失和错误

收到数据以后,啥都不干

在这里插入图片描述

3.2.9 协议和通道相互绑定

3.2.9.1 循环引用的问题

GameChannel.h中引用了头文件"GameProtocol.h"

#pragma once
#include<ZinxTCP.h>
#include"GameProtocol.h"

class GameChannel :
    public ZinxTcpData
{
public:
    GameChannel(int _fd);
    virtual ~GameChannel();
    GameProtocol * m_proto = NULL; 

};

如果在GameProtocol.h中引用GameChannel.h,则会造成循环引用。
处理办法是,直接在前面声明相关的类。

#pragma once
#include <zinx.h>

class GameChannel;  //避免循环引用

class GameProtocol :
    public Iprotocol
{
    std::string szLast; //上次未来得及处理的报文
public:
    GameChannel* m_channel = NULL;
    GameProtocol() ;
    virtual ~GameProtocol();
};
3.2.9.1 相互绑定的实现

在这里插入图片描述

3.2.9.3 代码测试

收到数据

07 00 00 00 02 00 00 00 0A 05 68 65 6C 6C 6F

07 00 00 00 - 数据消息的长度是7个字节
02 00 00 00 - 消息ID是2
0A 05 68 65 6C 6C 6F - 转换成string代表"hello"

在这里插入图片描述
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1173402.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Agent模型1】MemGPT: Towards LLMs as Operating Systems

论文标题&#xff1a;MemGPT: Towards LLMs as Operating Systems 论文作者&#xff1a;Charles Packer, Vivian Fang, Shishir G. Patil, Kevin Lin, Sarah Wooders, Joseph E. Gonzalez (UC Berkeley) 论文原文&#xff1a;https://arxiv.org/abs/2310.08560 论文出处&#x…

分享86个工作总结PPT,总有一款适合您

分享86个工作总结PPT&#xff0c;总有一款适合您 PPT下载链接&#xff1a;https://pan.baidu.com/s/12aRTr5NKG5YTnMnwNbqOrQ?pwd8888 提取码&#xff1a;8888 Python采集代码下载链接&#xff1a;采集代码.zip - 蓝奏云 学习知识费力气&#xff0c;收集整理更不易。知…

delphi 监测某音新增评论以及解决x-bogus签名验证(2023-11-5)

一、工作原理及流程&#xff1a; 1、从aweme接口取得aweme评论总数量&#xff1b; 2、与之前的数量比较&#xff0c;如果有新增评论&#xff1b; 3、从comment评论接口统计评论以及评论回复数量&#xff0c;得出新增评论数量&#xff1b; 4、按时间排序评论&#x…

Oracle(13)Maintaining Data Integrity

目录 一、基础知识 1、Data Integrity 数据库的完整性 2、Types of Constraints 约束类型 3、Constraint States 约束状态 4、Guidelines for Constraints 约束准则 二、基础操作 1、Enabling Constraints 启用约束 2、命令方式创建约束 3、修改表创建的约束 4、删除约…

【移远QuecPython】EC800M物联网开发板调用网络API(使用SIM卡联网并调用高德地图API的定位坐标转换)

【移远QuecPython】EC800M物联网开发板调用网络API&#xff08;使用SIM卡联网并调用高德地图API的定位坐标转换&#xff09; 高德API使用方法&#xff1a; 文章目录 API相关配置SIM卡联网网络操作API调用 高德地图API产品介绍适用场景使用限制使用说明坐标转换 附录&#xff…

UUID 的 5 个版本

UUID 的全称是 Universally Unique Identifier&#xff0c;中文为通用唯一识别码。 在对 UUID 进行说明之前&#xff0c;我们来看一个标准的 UUID。 下面就是一个标准的 UUID&#xff0c;使用横杠分隔符来进行分隔&#xff1a; 123e4567-e89b-42d3-a456-556642440000 xxxxxxx…

几个浏览GitHub开源项目的高级技巧,你知道几个?

大家好&#xff0c;我是豆小匠。GitHub作为全球最大的开源社区&#xff0c;里面有大量优质的开源项目。为了让用户更好浏览这些开源项目&#xff0c;GitHub其实提供了一些高级的使用方式&#xff0c;这期就来看看有没有你不知道的~ 一、使用在线VS Code查看源码 GitHub官方提供…

Linux常用命令及主流服务部署大全

目录 Linux 系统目录 一、常用操作命令 1、目录操作 2、文件内容操作&#xff08;查看日志&#xff0c;更改配置文件&#xff09; 3、压缩和解压缩 4、更改文件权限 二、各服务部署命令 1、增加虚拟内存 2、JDK 2.1 删除系统自带的openjdk 2.2 安装jdk 2.3 删除jd…

2024最新免费的mac电脑清理垃圾的软件有哪些?

mac电脑是许多人喜爱的电子产品&#xff0c;它拥有优美的设计、流畅的操作系统和强大的性能。但是&#xff0c;随着使用时间的增长&#xff0c;mac电脑也会积累一些不必要的垃圾文件&#xff0c;这些文件会占用宝贵的存储空间&#xff0c;影响电脑的运行速度和稳定性。因此&…

项目部署文档

申请SSL证书 先申请,用免费的 下载证书 先将下载下来的保存起来 服务器安装JDK: 创建develop目录 mkdir /usr/local/develop/ 把JDK压缩包上传到/usr/local/develop/目录 解压安装包 并且将安装到指定目录 tar -zxvf /usr/local/develop/jdk-8u191-linux-x64.tar.gz -C /us…

JavaSpringbootMySQL高校实训管理平台01557-计算机毕业设计项目选题推荐(附源码)

目 录 摘要 1 绪论 1.1 研究背景 1.2 研究意义 1.3论文结构与章节安排 2 高校实训管理平台系统分析 2.1 可行性分析 2.2 系统流程分析 2.2.1 数据增加流程 2.2.2 数据修改流程 2.2.3 数据删除流程 2.3 系统功能分析 2.3.1 功能性分析 2.3.2 非功能性分析 2.4 系…

RT-Thread内核移植

目录 前言一、实验平台简介1.1 W601简介1.2 RT-Thread简介1.3 开发环境 二、W601的SDK移植三、RT-Thread内核移植四、下载验证4.1 串口下载程序4.2 ST-Link下载 前言 本文以正点原子W601开发板为基础&#xff0c;讲解如何移植RT-Thread&#xff0c;本文所用资料见附件资源 一、…

SQL数据库使用方法

首先打开sqlite3.exe所在文件夹&#xff0c;如图1 图1 在文件夹路径中将路径改为cmd&#xff0c;如图2所示 图2 在弹出的cmd窗口中输入如图3所示。 图3 sqlite3 tichiceliang.db 其中tichiceliang是数据库名称。然后按enter&#xff0c;再在cmd中输入.table,可以看到文件夹目…

java中:cmd界面输入javac后提示:找不到或无法加载主类,怎么解决

找不到或无法加载主类 检查环境变量cmd下用 java命令运行文件,提示找不到主类待续、更新中 检查环境变量 CLASSPATH 少写.;安装jdk过程有两部,一步为安装jdk文件夹,全部一致; 另一步为安装jre文件夹与jdk文件夹不一致(或者文件夹安装位置, 一路全部默认)path中将java变量移到顶…

(免费领源码)Java#Springboot#mysql高校实训管理平台01557-计算机毕业设计项目选题推荐

目 录 摘要 1 绪论 1.1 研究背景 1.2 研究意义 1.3论文结构与章节安排 2 高校实训管理平台系统分析 2.1 可行性分析 2.2 系统流程分析 2.2.1 数据增加流程 2.2.2 数据修改流程 2.2.3 数据删除流程 2.3 系统功能分析 2.3.1 功能性分析 2.3.2 非功能性分析 2.4 系…

unittest 通过TextTestRunner(verbosity=)展示执行结果

unittest.TextTestRunner的verbosity参数用于控制测试运行时的详细程度。它可以接受的值如下&#xff1a; 0: 静默模式&#xff0c;不输出任何信息。1: 默认模式&#xff0c;输出每个测试方法的简要摘要和总体摘要&#xff08;通过、失败、错误等&#xff09;。2: 详细模式&am…

LangChain+LLM实战---使用GPT4ALL和LangChain构建本地大模型应用

原文&#xff1a;Private LLMs on Your Local Machine and in the Cloud With LangChain, GPT4All, and Cerebrium 私有化大语言模型的想法肯定会引起我们的共鸣。其吸引力在于&#xff0c;我们可以查询信息并将信息传递给大语言模型&#xff0c;而无需我们的数据或响应通过第…

OpenGL_Learn06(纹理)

接着之前的OpenGL_Learn05&#xff08;纹理&#xff09;-CSDN博客 1. 修改片段着色器 修改片段着色器&#xff0c;仅让笑脸图案朝另一个方向看 >>>>> 纹理坐标的Y轴没有进行改变&#xff0c;需要改变的是X轴的纹理坐标 片段代码改写如下 #version 330 core out…

【IO多路转接】select编程模型

文章目录 1 :peach:五种IO模型:peach:1.1 :apple:阻塞IO:apple:1.2 :apple:非阻塞IO:apple:1.3 :apple:信号驱动IO:apple:1.4 :apple:IO多路转接:apple:1.5 :apple:异步IO:apple:1.6 :apple:同步通信&异步通信:apple:1.7 :apple:阻塞&非阻塞:apple:1.8 :apple:总结:app…

【LearnOpenGL基础入门——1】OpenGL简介

目录 一.OpenGL是什么 二.渲染模式 三.前置知识 四.写在最后 一.OpenGL是什么 我们首先先了解一下OpenGL到底是什么。一般我们认为是包含了一系列可以操作图形、图像的函数的一套API。然而&#xff0c;OpenGL本身并不是一个API&#xff0c;它仅仅是一个由Khronos组织制定并…