任务2:向服务端发送一条文本消息,并得到返回消息的实现
友情提示:
从本节开始,所有的项目开发操作,只要你能明白并完成操作实现目标,就算是有一些类与概念不太理解,也可以继续。随着项目的推进,或者多向老师同学提问,前期只要能学会写法,并应用到自己的项目中就行。用多了,会得映证,逐渐明白。
上节课,我们准备好了斗地主前后端的起始项目,最后做一个向服务端发送一条文本消息,并得到返回消息的测试。就用了两行代码:
//测试发送给服务端一条文本消息 Session session = Game.Scene.GetComponent<NetOuterComponent>().Create(GlobalConfigComponent.Instance.GlobalProto.Address); G2C_TestMessage g2CTestMessage = (G2C_TestMessage) await session.Call(new C2G_TestMessage() { Info = "==>>服务端的朋友,你好!收到请回答" });
实际上肯定没这么简单,我们就从这两行代码来做 ETCore 通信的初步接触。
首先要构建一个Session,在ET的通信中,所有消息都是通过session发送的,如果不需要返回呢就是session.Send()方法,如果有返回就是session.Call()方法。暂时不细说这个,应该照着这个都会用了。
重点来介绍一下消息指令,消息类型,消息体,还有服务端收到消息后要做点什么处理的Handler。
C2G_TestMessage 就是向服务端请求的消息类型
G2C_TestMessage 就是返回到前端的消息类型
创建HotfixOpcode.cs HotfixMessage,cs
\Assets\ET.Core\Module\Message\ 在这个目录下,或者直接在下载的 Landlords_Client01 项目找到他们,练习时,可以删除这些,自己写一遍。
在HotfixOpcode.cs文件中定义好消息指令,消息类型。
//测试发送给服务端一条文本消息的[消息类型定义] [Message(HotfixOpcode.C2G_TestMessage)] public partial class C2G_TestMessage : IRequest {} [Message(HotfixOpcode.G2C_TestMessage)] public partial class G2C_TestMessage : IResponse {} /** public static partial class HotfixOpcode **/ //测试发送给服务端一条文本消息的[指令定义] public const ushort C2G_TestMessage = 10011; public const ushort G2C_TestMessage = 10012;
因为是一个有有返回的请求,所以消息类型继承了IRequest,IResponse。这两接口当然有固定属性的要求,这个以后再学习。
在HotfixMessage.cs文件中定义好消息体
//测试发送给服务端一条文本消息的[消息体定义] public partial class C2G_TestMessage : pb::IMessage { private static readonly pb::MessageParser<C2G_TestMessage> _parser = new pb::MessageParser<C2G_TestMessage>(() => (C2G_TestMessage)MessagePool.Instance.Fetch(typeof(C2G_TestMessage))); public static pb::MessageParser<C2G_TestMessage> Parser { get { return _parser; } } private int rpcId_; public int RpcId { get { return rpcId_; } set { rpcId_ = value; } } private string info_ = ""; public string Info { get { return info_; } set { info_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); } } public void WriteTo(pb::CodedOutputStream output) { if (Info.Length != 0) { output.WriteRawTag(10); output.WriteString(Info); } if (RpcId != 0) { output.WriteRawTag(208, 5); output.WriteInt32(RpcId); } } public int CalculateSize() { int size = 0; if (Info.Length != 0) { size += 1 + pb::CodedOutputStream.ComputeStringSize(Info); } if (RpcId != 0) { size += 2 + pb::CodedOutputStream.ComputeInt32Size(RpcId); } return size; } public void MergeFrom(pb::CodedInputStream input) { info_ = ""; rpcId_ = 0; uint tag; while ((tag = input.ReadTag()) != 0) { switch(tag) { default: input.SkipLastField(); break; case 10: { Info = input.ReadString(); break; } case 720: { RpcId = input.ReadInt32(); break; } } } } } public partial class G2C_TestMessage : pb::IMessage { private static readonly pb::MessageParser<G2C_TestMessage> _parser = new pb::MessageParser<G2C_TestMessage>(() => (G2C_TestMessage)MessagePool.Instance.Fetch(typeof(G2C_TestMessage))); public static pb::MessageParser<G2C_TestMessage> Parser { get { return _parser; } } private int rpcId_; public int RpcId { get { return rpcId_; } set { rpcId_ = value; } } private int error_; public int Error { get { return error_; } set { error_ = value; } } private string message_ = ""; public string Message { get { return message_; } set { message_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); } } public void WriteTo(pb::CodedOutputStream output) { if (RpcId != 0) { output.WriteRawTag(208, 5); output.WriteInt32(RpcId); } if (Error != 0) { output.WriteRawTag(216, 5); output.WriteInt32(Error); } if (Message.Length != 0) { output.WriteRawTag(226, 5); output.WriteString(Message); } } public int CalculateSize() { int size = 0; if (RpcId != 0) { size += 2 + pb::CodedOutputStream.ComputeInt32Size(RpcId); } if (Error != 0) { size += 2 + pb::CodedOutputStream.ComputeInt32Size(Error); } if (Message.Length != 0) { size += 2 + pb::CodedOutputStream.ComputeStringSize(Message); } return size; } public void MergeFrom(pb::CodedInputStream input) { rpcId_ = 0; error_ = 0; message_ = ""; uint tag; while ((tag = input.ReadTag()) != 0) { switch(tag) { default: input.SkipLastField(); break; case 720: { RpcId = input.ReadInt32(); break; } case 728: { Error = input.ReadInt32(); break; } case 738: { Message = input.ReadString(); break; } } } } }
(自己练习时,消息体复制就好了,不用自己写)
目前大家还不熟悉这种消息体是怎么创建的,只需要了解一下:
我们是通过新建这些消息类的实例,属性中带上值,经过序列化-> 传送 ->反序列化->在另一端也创建这些消息类的实例,属性中带着你要传送的值。数据就是这样传递的。
class C2G_TestMessage里面定义了
private int rpcId_;
private string info_ = ""; //通过这一属性传递消息内容给服务端
class G2C_TestMessage里面定义了
private int rpcId_;
private int error_;
private string message_ = ""; //通过这一属性把消息内容返回给前端
以上就对消息体有了核心的认识了,你可以按照这个写出自己想定义的消息体,包含你想要传递的消息属性,不要写太多,后面会讲用脚本自动生成消息体,自己定义几个消息类,只当熟悉。
在服务端的\Server\ET.Core\Module\Message\ 同样需要这两个文件HotfixOpcode.cs HotfixMessage,cs ,或者直接用下载的 Landlords_Server01 项目找到他们,练习时,可以删除这些,自己写一遍。
只要是前后端通信的消息类型,那里面消息指令,消息类型,消息体是同样的(既然前后端要通信,当然要有一致的指令,消息类型,消息体了)。
在服务端的Hotfix项目下Handler目录中 创建 C2G_TestMessageHandler
\Server\Hotfix\Handler\C2G_TestMessageHandler.cs
using System; using System.Net; using ETModel; namespace ETHotfix { [MessageHandler(AppType.Gate)] public class C2G_TestMessageHandler : AMRpcHandler<C2G_TestMessage, G2C_TestMessage> { protected override async ETTask Run(Session session, C2G_TestMessage request, G2C_TestMessage response, Action reply) { // do something ... response.Message = "前端的朋友,消息收到了<<==="; reply(); } } }
并不是所有的消息传递都需要一个对应的Handler处理方法,这个以后再多了解。明显这个是要在服务端对前端的请求做些处理再返回一个消息给前端。
扩展了解:
[MessageHandler(AppType.Gate)] 这是会自动扫描的特性,通过MessageHandler 通信框架就知道这是一个消息处理方法,并和对应的指令一起存起来。通过AppType.Gate 通信框架就知道这是一个要去请求Gate服务器并作处理的方法。
(我们的这个练习是Gate,Realm,Map等都放在一台物理主机的设置,现在还不明白这些是什么,请求哪一个的区别是什么,留待以后)
完成以上步骤,上节课最后的测试“向服务端发送一条文本消息,并得到返回消息”就实现了,需要把服务端中的Hotfix项目单独编译一下,然后运行服务端,再在Unity中Play运行,就可以看到之前的效果了。