Unity几种socket通信协议比较

 

引言

做Unity项目 使用C#的Socket与服务器进行通信时,消息的解析有多种方案,比如protobuffer、Marshal、或BindaryReader/BindaryWriter等,但各有优缺点,再考虑到加/解密、压缩与解压缩、跨平台,最终还要考虑性能,其实最合适的方案只有一个。

 

Socket的处理

         C#提供了两种方式的连接、收发数据的方式,一种是同步处理,一种是异步处理。

  1. 同步处理方式,需要自己使用多线程进行处理。连接时需要启动一个线程,发消息时启动一个线程,收消息启动一个线程。这样更可控,但如果经验不足,也容易出Bug,再者Unity(Mono)对线程的支持并不那么完美(可能出现线程挂起后,无法恢复),导致致命Bug。
  2. 异步处理,可以减少对线程的依赖,建议使用这种方式。

 

关于连接的处理,这里暂时不细述。

 

基于ProtoBuffer的协议

Protobuffer是个很好的方式,提供了一套完整的解决方案,有描述文件,有转换工具,多语言支持,使用简单,易于维护。

但protobuf性能损耗比较高,对于通信量大且频繁的游戏,就不太适用。

而且处理加密/解密、压缩/解压时,还要做额外的处理,并且累加了性能消耗,使性能降到不可接受的范围。

基于Marshal与C#序列化的协议

c/c++在序列化或反序列化内存二进制数据时,相对比较容易,大部门情况下,只需对内存块和Struct进行操作即可,无需编码太多解析代码。使用Marshal就是类型这种方,反序列过程是从托管内存传Byte[]与Struct进去,返回反序列好的Struct对像;序列化过程是把Struct对象传入非托管内存,转化为Byte[]。

         具体实现方式这里不再陈述,百度上随便一搜就可以看到很多例子。

         这种方式,实现起来也相对比较容易,解析性能比较高,特别是对比较大且复杂的消息。但托管内存与非托管内存的转换过程,会产生相应大小的临时对象,在Unity中会触发GC,导致性能折半。

         一个致命的问题,如果结构中含有数组,会触发JIT,iOS系统只允许AOT,所以一但需要处理数组(非值类型的字段)在iOS上就会直接Crash。

         因此,这种方式并不是一个好的选择,更不是通用的选择。

 

基于MemoryStream的协议解决

         在C#的System.IO命名空间下,有BinaryReader与BinaryWriter两个类,分别处理对MemeoryStream的读写。

         我们从Socket中接收到Byte[]后,通过种方式按读出每个字段,实现反序列化:

 

                 const int MAX_BUFFER_SIZE = 1024;

            Byte[] dataFromSocket = new Byte[MAX_BUFFER_SIZE];

 

            MemoryStream ms = new MemoryStream();

            BinaryReader br = new BinaryReader(ms);

            ms.Write(dataFromSocket, 0, MAX_BUFFER_SIZE);

 

            br.ReadInt16();

            br.ReadBoolean();

            br.ReadChars(10);

            ....

        

 

把C#对象,按顺序写入字段的值,实现序列化生成Byte[],并传给Socket发送出去:

                      MemoryStream bwms = new MemoryStream(MAX_BUFFER_SIZE);

            BinaryWriter bw = new BinaryWriter(bwms);

 

            bw.Write(0);//有很多重载,可以支持很多数据类型的写入

            ...

            bwms.ToArray();//转化为Byte[],通过Socket发送

 

    这种方式需要按照字段的顺序进行读取或写入,如果是通过程序员手动去写,工作量相当大的,面且很维护,增/删字段简直会让人崩溃,面且非常容易出错。

然而,我们可以通过编写一个工具,把protobuffer的消息定义文件(或别的定义方式)转化为有序的读/写代码,就像protobuffer生成的类文件一样。

这种方式,相比前两种,性能是最好,不但读/写方便,而且可以控制中间临时内存的创建数量(可使用对象池),大大减少Unity的GC。

 

目前我现在的项目主要使用这种方式,并开发了相应的工具。

 

Lua中使用的消息解析

         Lua也有多种方式来解析Socket二进制消息,比如逐字段解析、或使用protobuffer(protoc-gen-lua)等。

         逐字段解析需要自己开发工具,并编写解析代码,目前没采用。

        

目前推荐使用protoc-gen-lua结合pb.c来处理,方案比较成熟,可参考网上的教程。

 

 

结语

         对与unity的socket通信,还有非常多的细节,这里暂未提及

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要在Unity中编写Socket通信,您可以使用C#中的Socket类。以下是一个简单的示例代码,用于在Unity中建立TCP连接并发送和接收数据: ```csharp using System; using System.Net; using System.Net.Sockets; using System.Text; using UnityEngine; public class SocketClient : MonoBehaviour { private Socket _clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); private byte[] _receiveBuffer = new byte[1024]; private void Start() { _clientSocket.BeginConnect(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8080), ConnectCallback, null); } private void ConnectCallback(IAsyncResult ar) { _clientSocket.EndConnect(ar); Debug.Log("Connected to server!"); // Send test message SendData("Hello from Unity!"); // Start receiving data _clientSocket.BeginReceive(_receiveBuffer, 0, _receiveBuffer.Length, SocketFlags.None, ReceiveCallback, null); } private void ReceiveCallback(IAsyncResult ar) { int bytesRead = _clientSocket.EndReceive(ar); if (bytesRead > 0) { string receivedData = Encoding.ASCII.GetString(_receiveBuffer, 0, bytesRead); Debug.Log("Received data: " + receivedData); // Continue receiving data _clientSocket.BeginReceive(_receiveBuffer, 0, _receiveBuffer.Length, SocketFlags.None, ReceiveCallback, null); } else { Debug.Log("Disconnected from server!"); } } private void SendData(string data) { byte[] dataBytes = Encoding.ASCII.GetBytes(data); _clientSocket.BeginSend(dataBytes, 0, dataBytes.Length, SocketFlags.None, SendCallback, null); } private void SendCallback(IAsyncResult ar) { _clientSocket.EndSend(ar); Debug.Log("Data sent to server!"); } private void OnDestroy() { _clientSocket.Close(); } } ``` 在上面的代码中,我们首先创建了一个名为“SocketClient”的MonoBehaviour类,该类使用Socket类进行TCP通信。在Start方法中,我们开始连接到服务器,并在连接成功后发送一条测试消息。然后,我们开始接收来自服务器的数据,并将其输出到Unity控制台。最后,在OnDestroy方法中,我们关闭了客户端套接字。 请注意,上述代码中的IP地址和端口号应该替换为您自己的服务器地址和端口号。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值