天津网站建设普斯泰,python在线编程题库,树苗网站源码,临汾尚世互联网站建设文章目录 1 项目总体架构2 项目需求2.1 服务器职责2.2 消息的格式和定义 3 基于Tcp连接的通信方式3.1 通道层实现GameChannel类3.1.1 TcpChannel类3.1.2 Tcp工厂类3.1.3 创建主函数#xff0c;添加Tcp的监听套接字3.1.4 代码测试 3.2 消息类的结构设计和实现3.2.1 消息的定义3… 文章目录 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 代码测试-13.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个字节低字节在前| 消息ID4个字节低字节在前| 消息内容 | 消息以及其处理方式已经在客户端实现本项目要实现的是服务器端的相关处理
详细定义如下
消息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.hint 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::listGameMsg * m_Msgs;
};
3.2.3 protoc消息的创建
protoc msg.proto --cpp_out./syntaxproto3;
package pb;//无关选项用于客户端
option csharp_namespacePb;message SyncPid{int32 Pid1;string Username2;
}message Player{int32 Pid1;Position P2;string Username3;
}message SyncPlayers{/*嵌套多个子消息类型Player的消息*/repeated Player ps1;
}message Position{float X1;float Y2; float Z3; float V4;int32 BloodValue5;
}message MovePackage{Position P1;int32 ActionData2;
}message BroadCast{int32 Pid1;int32 Tp2;/*根据Tp不同Broadcast消息会包含聊天内容(Content)或初始位置(P)或新位置P*/oneof Data{string Content3;Position P4;/*ActionData暂时预留*/int32 ActionData5;}string Username6;
}message Talk{string Content1;
}
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::listGameMsg* 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报文粘包的处理
添加数据头4ID4数据信息
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 7408 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
#includeZinxTCP.h
#includeGameProtocol.hclass GameChannel :public ZinxTcpData
{
public:GameChannel(int _fd);virtual ~GameChannel();GameProtocol * m_proto NULL; };如果在GameProtocol.h中引用GameChannel.h则会造成循环引用。 处理办法是直接在前面声明相关的类。
#pragma once
#include zinx.hclass 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 6F07 00 00 00 - 数据消息的长度是7个字节 02 00 00 00 - 消息ID是2 0A 05 68 65 6C 6C 6F - 转换成string代表hello