kizumi_header_banner_img

Hello! Beautiful Kizumi!

加载中

文章导读

Unity网络开发基础


avatar
Yuyas 2025年12月19日 15

OSI七层模型

  1. 应用层:为应用程序提供服务
  2. 表示层:数据格式转化,加密
  3. 会话层:建立,管理和维护对话
  4. 传输层:建立,管理和维护端到端的连接
  5. 网络层:IP选址和路由选择
  6. 数据链路层:分帧(数据包),确定Mac地址
  7. 物理层:物理设备传输数据

TCP/IP

将OSI七层模型进行设计,吧互联通信的过程抽象分成了四层

  1. 应用层
  2. 传输层
  3. 网络层
  4. 数据链路层

TCP/UDP

  • TCP:三次握手,四次挥手

    • 更可靠,保证了数据的稳定性和有序性
    • 适合对信息准确性要求高,效率要求低的场景

  • UDP:

    • 更效率,传输更快,资源消耗少
    • 适合对时效性要求高的场景

IP地址和端口类

  • 初始化IP信息IPAddress

    • IPAddress ip = IPAddress.Parse(“118.102.111.11”)
    • IPAddress ip = new IPAddress(0x79666F0B)
    • IPAddress ip = new IPAddress(new byte[]{118, 102, 111, 11})

  • 初始化IP和端口号组合IPEndPoint

    • IPEndPoint ipPoint = new IPEndPoint(0x79666F0B, 8080)
    • IPEndPoint ipPoint = new IPEndPoint(IPAddress, 8080)

  • 域名解析IPHostEntry(为保证在查询时不阻塞主线程,通常使用异步操作)

    • IPHostEntry entry = Dns.GetHostEntry(“www.baidu.com“);

      • entry.AddressList // IP地址
      • entry.Aliases // 主机别名
      • entry.HostName // 服务器名称

    • 异步操作

      • public async void FuncAsync()
        {
           Task<IPHostEntry> task = Dns.GetHostEntryAsync(“www.baidu.com”);
           await task;
           for(int i = 0;i < task.Result.AddressList.Length;++ i)
          {
               print(task.Result.AddressList[i]);
          }
           print(“服务器名称:” + task.Result.HostName);
        }

Socket套接字

  • Socket s = new Socket()

    • 参数一:AddressFamily 网络寻址 枚举类型,决定寻址方案 常用: 1.InterNetwork IPv4寻址 2.InterNetwork6 IPv6寻址 做了解: 1.UNIX UNIX本地到主机地址 2.ImpLink ARPANETIMP地址 3.Ipx IPX或SPX地址 4.Iso ISO协议的地址 5.Osi OSI协议的地址 7.NetBios NetBios地址 9.Atm 本机ATM服务地址
    • 参数二:SocketType 套接字枚举类型,决定使用的套接字类型 常用: 1.Dgram 支持数据报,最大长度固定的无连接、不可靠的消息(主要用于UDP通信) 2.Stream 支持可靠、双向、基于连接的字节流(主要用于TCP通信) 做了解: 1.Raw 支持对基础传输协议的访问 2.Rdm 支持无连接、面向消息、以可靠方式发送的消息 3.Seqpacket 提供排序字节流的面向连接且可靠的双向传输
    • 参数三:ProtocolType 协议类型枚举类型,决定套接字使用的通信协议 常用: 1.TCP TCP传输控制协议 2.UDP UDP用户数据报协议 做了解: 1.IP IP网际协议 2.Icmp Icmp网际消息控制协议 3.Igmp Igmp网际组管理协议 4.Ggp 网关到网关协议 5.IPv4 Internet协议版本4 6.Pup PARC通用数据包协议 7.Idp Internet数据报协议 8.Raw 原始IP数据包协议 9.Ipx Internet数据包交换协议 10.Spx 顺序包交换协议 11.IcmpV6 用于IPv6的Internet控制消息协议
    • 2、3参数的常用搭配: SocketType.Dgram + ProtocolType.Udp = UDP协议通信(常用,主要学习) SocketType.Stream + ProtocolType.Tcp = TCP协议通信(常用,主要学习) SocketType.Raw + ProtocolType.Icmp = Internet控制报文协议(了解) SocketType.Raw + ProtocolType.Raw = 简单的IP包通信(了解)

TCP通信

  • 服务端1.创建Socket对象
    socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    2.绑定IP端口
    socket.Bind(new IPEndPoint(IPAddress.Parse(IP), port));
    3.设置最大监听客户端数量
    socket.Listen(maxListener);
    4.连接接收客户端
    Socket nowSocket = socket.Accept();
    5.向客户端发送消息
    client.socket.Send(Encoding.UTF8.GetBytes(info));  
  • 客户端1.创建Socket对象
    socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    2.连接服务端
    socket.Connect(new IPEndPoint(IPAddress.Parse(IP), port));
    3.接收服务端消息
    int byteNum = socket.Receive(result);
    4.向服务端发送消息
    socket.Send(data.Writing());

TCP异步

  • Acceptpublic void FuncAcceptAsync()
    {
       SocketAsyncEventArgs e = new SocketAsyncEventArgs();
       e.Completed += (_socket, args) =>
      {
           if(e.SocketError == SocketError.Success)
          {
               Socket clientSocket = args.AcceptSocket;
               ClientSocket client = new ClientSocket(clientSocket);
               clientDic.Add(client.clientID, client);

               client.FuncSendAsync(Encoding.UTF8.GetBytes(“欢迎连入服务端”));
          }
           else
          {
               Console.WriteLine(“Accept Fault:” + e.SocketError);
          }
      };
       socket.AcceptAsync(e);
    }
  • Receivepublic void FuncReceiveAsync()
    {
       receiveE.SetBuffer(new byte[1024 * 1024], 0, 1024 * 1024);
       receiveE.Completed += (_socket, args) =>
      {
           if (args.SocketError == SocketError.Success)
          {
               byte[] result = args.Buffer;
               int byteNum = args.BytesTransferred;
               // 后续处理信息
               int index = 0;
               int msgID = BitConverter.ToInt32(result, index);
               index += 4;
               int length = BitConverter.ToInt32(result, index);
               index += 4;
               switch (msgID)
              {
                   case 1001:
                       break;
                   case 1002:
                       Console.WriteLine(“收到心跳”);
                       break;
                   default:
                       Console.WriteLine(Encoding.UTF8.GetString(result, index, length));
                       break;
              }

               args.SetBuffer(0, args.Buffer.Length);
              (_socket as Socket).ReceiveAsync(args);
          }
           else
          {
               Console.WriteLine(“Receive Fault:” + args.SocketError);
          }
      };
       socket.ReceiveAsync(receiveE);
    }
  • Sendpublic void FuncSendAsync(byte[] bytes)
    {
       sendE.SetBuffer(bytes, 0, bytes.Length);
       socket.SendAsync(sendE);
    }

UDP通信

相较于TCP,UDP服务端不需要设置listener,客户端不需要connect,关键字为ReceiveFrom,SendTo

  • 服务端
  • 客户端

UDP异步

  • 服务端public Socket socket;
    public bool isClosed;

    public Dictionary<string, UDPClient> clientDic = new Dictionary<string, UDPClient>();

    public SocketAsyncEventArgs sendE = new SocketAsyncEventArgs();

    public void Init(string IP, int port)
    {
       Console.WriteLine(“服务端开启”);
       socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
       socket.Bind(new IPEndPoint(IPAddress.Parse(IP), port));
       Console.WriteLine(“正在等待接收消息…”);

       FuncReceiveAsync();
    }

    public void FuncReceiveAsync()
    {
       SocketAsyncEventArgs receiveE = new SocketAsyncEventArgs();
       //IPEndPoint clientIP = new IPEndPoint(IPAddress.Any, 0);
       receiveE.RemoteEndPoint = new IPEndPoint(IPAddress.Any, 0);

       receiveE.SetBuffer(new byte[500], 0, 500);
       receiveE.Completed += (_socket, args) =>
      {
           IPEndPoint iPEndPoint = args.RemoteEndPoint as IPEndPoint;
           string s = iPEndPoint.Address.ToString() + iPEndPoint.Port.ToString();

           UDPClient client = null;
           if (!clientDic.ContainsKey(s))
          {
               client = new UDPClient(iPEndPoint);
               clientDic.Add(s, client);
          }
           else client = clientDic[s];

           byte[] result = args.Buffer;
           int length = args.BytesTransferred;

           args.SetBuffer(result, 0, length);
           client.HandleReceiveMsg(result);
           
          (_socket as Socket).ReceiveFromAsync(receiveE);
      };

       socket.ReceiveFromAsync(receiveE);
    }

    public void BroadCast(byte[] bytes)
    {
       foreach (UDPClient client in clientDic.Values)
      {
           socket.SendTo(bytes, client.iPEndPoint);
      }
    }
  • 客户端private static AsyncUDPNetMgr instance;
    public static AsyncUDPNetMgr Instance => instance;

    private IPEndPoint serverIP = new IPEndPoint(IPAddress.Parse(“127.0.0.1”), 8080);
    private Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);

    private void Awake()
    {
       instance = this;
       FuncReceiveAsync();
    }

    public void SetServerIPEndPoint(string IP, int port)
    {
       serverIP.Address = IPAddress.Parse(IP);
       serverIP.Port = port;
    }

    public void FuncReceiveAsync()
    {
       SocketAsyncEventArgs receiveE = new SocketAsyncEventArgs();
       receiveE.RemoteEndPoint = new IPEndPoint(IPAddress.Any, 0);
       receiveE.SetBuffer(new byte[500], 0, 500);
       receiveE.Completed += (_socket, args) =>
      {
           if(args.SocketError == SocketError.Success)
          {
               args.SetBuffer(args.Buffer, 0, args.BytesTransferred);
               HandleMsg(args.Buffer);

              (_socket as Socket).ReceiveFromAsync(args);
          }
           else
          {
               print(“接收错误:” + args.SocketError);
          }
      };
       socket.ReceiveFromAsync(receiveE);
    }

    public void SendMsg(byte[] bytes)
    {
       socket.SendTo(bytes, serverIP);
    }

    public void HandleMsg(byte[] result)
    {
       string info = Encoding.UTF8.GetString(result);
       print(“收到服务端发来消息:” + info);
    }

文件传输FTP

  • 本质是TCP
  • 两种传输模式:主动模式Port,被动模式Passive
  • 两种传输方式:ASCII和二进制
  • 需要用户名和密码登录(服务器允许时也可匿名登录)
  • 重要类

    • 通信凭证类 NetworkCredential

      • 构造参数为string账号密码

    • Create创建新的WebRequest

      • FtpWebRequest req = FtpWebRequest.Create(new Uri(“ftp://127.0.0.1/test.txt”)) as FtpWebRequest

    • GetRequestStream获取上传流

      • Stream s = req.GetRequestStream()

    • GetResponse 返回FTP服务器响应

      • FtpWebResponse res = req.GetResponse () as FtpWebResponse

  • 重要成员//1.Credentials 通信凭证,设置为NetworkCredential对象
    req.Credentials = n;
    //2.KeepAlive bool值,当完成请求时是否关闭到FTP服务器的控制连接(默认为true,不关闭)
    req.KeepAlive = false;

    //3.Method 操作命令设置
    req.Method
    // WebRequestMethods.Ftp类中的操作命令属性
    // DeleteFile 删除文件
    // DownloadFile 下载文件
    // ListDirectory 获取文件简短列表
    // ListDirectoryDetails 获取文件详细列表
    // MakeDirectory 创建目录
    // RemoveDirectory 删除目录
    // UploadFile 上传文件
    req.Method = WebRequestMethods.Ftp.DownloadFile;
    这些方法会在req.GetResponse()的时候执行

    //4.UseBinary 是否使用2进制传输
    req.UseBinary = true;

    //5.RenameTo 重命名
    //req.RenameTo = “myTest.txt”;

    知识点三 FtpWebResponse类
    //命名空间:System.Net
    //它是用于封装FTP服务器对请求的响应
    //它提供操作状态以及从服务器下载数据
    //我们可以通过FtpWebRequest对象中的GetResponse()方法获取
    //当使用完毕时,要使用Close释放
    //通过它来真正的从服务器获取内容
    FtpWebResponse res = req.GetResponse() as FtpWebResponse;

    //重要方法:
    //1.Close:释放所有资源
    res.Close();
    //2.GetResponseStream:返回从FTP服务器下载数据的流
    Stream stream = res.GetResponseStream();

    //重要成员:
    //1.ContentLength:接受到数据的长度
    print(res.ContentLength);
    //2.ContentType:接受数据的类型
    print(res.ContentType);
    //3.StatusCode:FTP服务器下发的最新状态码
    print(res.StatusCode);
    //4.StatusDescription:FTP服务器下发的状态代码的文本
    print(res.StatusDescription);
    //5.BannerMessage:登录前建立连接时FTP服务器发送的消息
    print(res.BannerMessage);
    //6.ExitMessage:FTP会话结束时服务器发送的消息
    //7.LastModified:FTP服务器上的文件的上次修改日期和时间

超文本传输HTTP

  • 重要类

    • 通信凭证类 NetworkCredential
    • 请求类HttpWebRequest
    • 响应类HttpWebResponse

  • 常用属性

    • req.Method = WebRequestMethods.Http.Get; // Get通常为下载,通常Post为上传
    • req.Timeout = 2000; // 当2000ms后未响应,则会认为超时,不成功
    • req.ContentType = ” ” // Post需要

  • Post上传示例知识点二 Post如何携带额外参数
    //关键点:将Content-Type设置为 application/x-www-form-urlencoded 键值对类型
    HttpWebRequest req = HttpWebRequest.Create(“http://192.168.50.109:8000/Http_Server/”) as HttpWebRequest;
    req.Method = WebRequestMethods.Http.Post;
    req.Timeout = 2000;
    //设置上传的内容的类型
    req.ContentType = “application/x-www-form-urlencoded”;

    //我们要上传的数据
    string str = “Name=MrTang&ID=2”;
    byte[] bytes = Encoding.UTF8.GetBytes(str);
    //我们在上传之前一定要设置内容的长度
    req.ContentLength = bytes.Length;
    //上传数据
    Stream stream = req.GetRequestStream();
    stream.Write(bytes, 0, bytes.Length);
    stream.Close();
    //发送数据 得到响应结果
    HttpWebResponse res = req.GetResponse() as HttpWebResponse;
    print(res.StatusCode);

    • 其中ContentType有//ContentType的构成:
      //内容类型;charset=编码格式;boundary=边界字符串
      //text/html;charset=utf-8;boundary=自定义字符串

      //其中内容类型有:
      //文本类型text:
      //text/plain 没有特定子类型就是它(重要)
      //text/html
      //text/css
      //text/javascript

      //图片类型image:
      //image/gif
      //image/png
      //image/jpeg
      //image/bm
      //image/webp
      //image/x-icon
      //image/vnd.microsoft.icon

      //音频类型audio:
      //audio/midi
      //audio/mpeg
      //audio/webm
      //audio/ogg
      //audio/wav

      //视频类型video:
      //video/webm
      //video/ogg

      //二进制类型application:
      //application/octet-stream 没有特定子类型就是它(重要)
      //application/x-www-form-urlencoded 传递参数时使用键值对形式(重要)
      //application/pkcs12
      //application/xhtml+xml
      //application/xml
      //application/pdf
      //application/vnd.mspowerpoint

      //复合内容multipart:
      //multipart/form-data 复合内容,有多种内容组合(重要)
      //multipart/byteranges 特殊的复合文件

    • ContentType中对于我们来说重要的类型

      • 1.通用2进制类型 application/octet-stream
      • 2.通用文本类型 text/plain
      • 3.键值对参数 application/x-www-form-urlencoded
      • 4.复合类型(传递的信息有多种类型组成,比如有键值对参数,有文件信息等等,上传资源服务器时需要用该类型) multipart/form-data

  • WWW类

    • 三种地址

      • Http — http://
      • Ftp — ftp://
      • 本地 — file://

    • 创建方式为WWW www = new WWW(“file://” + Path);
    • 配合协程使用,yield return www等待创建好后再进行后面逻辑
    • 直接通过属性来获得加载好的文件

      • www.texture
      • www.text
      • www.assetBundle
      • www.GetAudioClip()

  • WWWForm类

    • 可以作为new WWW(path, data)的第二参数,传递数据
    • 用法:WWWForm data = new WWWFrom( )

      • data.AddField(字段名,字段值)
      • data.AddBinaryData(字段名,byte[ ])

  • UnityWebRequest类

    • 示例获取Text(Get只是配置连接,真正的连接需要SendWebRequest( ))IEnumerator LoadText()
      {
      UnityWebRequest req = UnityWebRequest.Get(“http://192.168.50.109:8000/Http_Server/test.txt”);
      //就会等待 服务器端响应后 断开连接后 再继续执行后面的内容
      yield return req.SendWebRequest();

      //如果处理成功 结果就是成功枚举
      if(req.result == UnityWebRequest.Result.Success)
      {
      //文本 字符串
      print(req.downloadHandler.text);
      //字节数组
      byte[] bytes = req.downloadHandler.data;
      print(“字节数组长度” + bytes.Length);
      }
      else
      {
      print(“获取失败:” + req.result + req.error + req.responseCode);
      }
      }

    • 1.获取文本或二进制数据时 使用UnityWebRequest.Get(path) 以及req.downloadHandler.data
    • 2.获取纹理图片数据时 使用UnityWebRequestTexture.GetTexture(path) 以及DownloadHandlerTexture.GetContent(req)
    • 3.获取AB包数据时 使用UnityWebRequestAssetBundle.GetAssetBundle(path) 以及DownloadHandlerAssetBundle.GetContent(req)

  • 上传相关数据类

    • 需要将要上传的数据都存储到 List dataList

      • Add两种父类MultipartFormDataSection和MultipartFormFileSection

    • 其中Add的方式有以下
    • //子类数据
      //MultipartFormDataSection
      //1.二进制字节数组
      dataList.Add(new MultipartFormDataSection(Encoding.UTF8.GetBytes(“123123123123123”)));
      //2.字符串
      dataList.Add(new MultipartFormDataSection(“12312312312312312dsfasdf”));
      //3.参数名,参数值(字节数组,字符串),编码类型,资源类型(常用)
      dataList.Add(new MultipartFormDataSection(“Name”, “MrTang”, Encoding.UTF8, “application/….”));
      dataList.Add(new MultipartFormDataSection(“Msg”, new byte[1024], “appl…..”));

      //MultipartFormFileSection
      //1.字节数组
      dataList.Add(new MultipartFormFileSection(File.ReadAllBytes(Application.streamingAssetsPath + “/test.png”)));

      //2.文件名,字节数组(常用)
      dataList.Add(new MultipartFormFileSection(“上传的文件.png”, File.ReadAllBytes(Application.streamingAssetsPath + “/test.png”)));
      //3.字符串数据,文件名(常用)
      dataList.Add(new MultipartFormFileSection(“12312313212312”, “test.txt”));
      //4.字符串数据,编码格式,文件名(常用)
      dataList.Add(new MultipartFormFileSection(“12312313212312”, Encoding.UTF8, “test.txt”));

      //5.表单名,字节数组,文件名,文件类型
      dataList.Add(new MultipartFormFileSection(“file”, new byte[1024], “test.txt”, “”));
      //6.表单名,字符串数据,编码格式,文件名
      dataList.Add(new MultipartFormFileSection(“file”, “123123123”, Encoding.UTF8, “test.txt”));

    • 上传的方式有两种(Post,Put)

    • 上传示例private IEnumerator UploadResAysnc(string fileName, string localPath, UnityAction<UnityWebRequest.Result> callBack)
      {
      List<IMultipartFormSection> data = new List<IMultipartFormSection>();
      data.Add(new MultipartFormFileSection(fileName, File.ReadAllBytes(localPath)));

      UnityWebRequest req = UnityWebRequest.Post(“http://10.4.108.124:8080/UServer/”, data);
      yield return req.SendWebRequest();

      callBack?.Invoke(req.result);
      }

  • UnityWebRequest高级操作

    • 在获取对应类对象时不再需要把req的Get替换成对应的方法,例如UnityWebRequestTexture.GetTexture(path)这种,而是手动去创建req.downloadHandler对应的下载器
    • new DownloadHandlerTexture();
    • new DownloadHandlerAssetBundle(req.url, 0);UnityWebRequest req = UnityWebRequest.Get(path);
      req.downloadHandler = new DownloadHandlerTexture(true); //这里灵活性更高

      几乎等价于

      UnityWebRequest req = UnityWebRequestTexture.GetTexture(path)

    • 存在自定义下载类,继承DownloadHandlerScriptpublic class CustomDownLoadFileHandler:DownloadHandlerScript
      {
      //用于保存 本地存储时的路径
      private string savePath;

      //用于缓存收到的数据的容器
      private byte[] cacheBytes;
      //这是当前已收到的数据长度
      private int index = 0;

      public CustomDownLoadFileHandler():base(){ }

      public CustomDownLoadFileHandler(byte[] bytes) :base(bytes){ }

      public CustomDownLoadFileHandler(string path) : base()
      {
      savePath = path;
      }

      protected override byte[] GetData()
      {
      //返回字节数组
      return cacheBytes;
      }

      /// 从网络收到数据后 每帧会调用的方法 会自动调用的方法
      protected override bool ReceiveData(byte[] data, int dataLength)
      {
      Debug.Log(“收到数据长度:” + data.Length);
      Debug.Log(“收到数据长度dataLength:” + dataLength);
      data.CopyTo(cacheBytes, index);
      index += dataLength;
      return true;
      }

      /// 从服务器收到 COntent-Length标头时 会自动调用的方法
      protected override void ReceiveContentLengthHeader(ulong contentLength)
      {
      //base.ReceiveContentLengthHeader(contentLength);
      Debug.Log(“收到数据长度:” + contentLength);
      //根据收到的标头 决定字节数组容器的大小
      cacheBytes = new byte[contentLength];
      }

      /// 当消息收完了 会自动调用的方法
      protected override void CompleteContent()
      {
      Debug.Log(“消息收完”);
      //把收到的字节数组 进行自定义处理 我们在这 处理成 存储到本地
      File.WriteAllBytes(savePath, cacheBytes);
      }

      }

    • 还有上传的高级操作,但是不如常用操作好用,故不记叙

第三方协议工具Protobuf

  • .proto文件配置规则syntax = “proto3”;//决定了proto文档的版本号
    //规则二:版本号

    //规则一:注释方式
    //注释方式一
    /*注释方式二*/

    //规则11:导入定义
    import “test2.proto”;

    //规则三:命名空间
    package GamePlayerTest;//这决定了命名空间

    //规则四:消息类
    message TestMsg{
    //规则五:成员类型 和 唯一编号

    //浮点数
    // = 1 不代表默认值 而是代表唯一编号 方便我们进行序列化和反序列化的处理
    //required 必须赋值的字段
    required float testF = 1; //C# – float
    //optional 可以不赋值的字段
    optional double testD = 2; //C# – double

    //变长编码
    //所谓变长 就是会根据 数字的大小 来使用对应的字节数来存储 1 2 4
    //Protobuf帮助我们优化的部分 可以尽量少的使用字节数 来存储内容
    int32 testInt32 = 3; //C# – int 它不太适用于来表示负数 请使用sint32
    //1 2 4 8
    int64 testInt64 = 4; //C# – long 它不太适用于来表示负数 请使用sint64

    //更实用与表示负数类型的整数
    sint32 testSInt32 = 5; //C# – int 适用于来表示负数的整数
    sint64 testSInt64 = 6; //C# – long 适用于来表示负数的整数

    //无符号 变长编码
    //1 2 4
    uint32 testUInt = 7; //C# – uint 变长的编码
    uint64 testULong = 8; //C# – ulong 变长的编码

    //固定字节数的类型
    fixed32 testFixed32 = 9; //C# -uint 它通常用来表示大于2的28次方的数 ,比uint32更有效 始终是4个字节
    fixed64 testFixed64 = 10; //C# -ulong 它通常用来表示大于2的56次方的数 ,比uint64更有效 始终是8个字节

    sfixed32 testSFixed32 = 11; //C# – int 始终4个字节
    sfixed64 testSFixed64 = 12; //C# – long 始终8个字节

    //其它类型
    bool testBool = 13; //C# – bool
    string testStr = 14; //C# – string
    bytes testBytes = 15; //C# – BytesString 字节字符串

    //数组List
    repeated int32 listInt = 16; // C# – 类似List<int>的使用
    //字典Dictionary
    map<int32, string> testMap = 17; // C# – 类似Dictionary<int, string> 的使用

    //枚举成员变量的声明 需要唯一编码
    TestEnum testEnum = 18;

    //声明自定义类对象 需要唯一编码
    //默认值是null
    TestMsg2 testMsg2 = 19;

    //规则9:允许嵌套
    //嵌套一个类在另一个类当中 相当于是内部类
    message TestMsg3{
    int32 testInt32 = 1;
    }

    TestMsg3 testMsg3 = 20;

    //规则9:允许嵌套
    enum TestEnum2{
    NORMAL = 0; //第一个常量必须映射到0
    BOSS = 1;
    }

    TestEnum2 testEnum2 = 21;

    //int32 testInt3233333 = 22;

    bool testBool2123123 = 23;

    GameSystemTest.HeartMsg testHeart = 24;

    //告诉编译器 22 被占用 不准用户使用
    //之所以有这个功能 是为了在版本不匹配时 反序列化时 不会出现结构不统一
    //解析错误的问题
    reserved 22;
    reserved “testInt3233333”;
    }

    //枚举的声明
    enum TestEnum{
    NORMAL = 0; //第一个常量必须映射到0
    BOSS = 5;
    }

    message TestMsg2{
    int32 testInt32 = 1;
    }

  • 将Proto转化为C#文件

    • protoc.exe -I=配置路径 –csharp_out=输出路径 配置文件名

  • Proto生成的CS类的序列化和反序列化

    • 使用FileStream操作文件

      • 序列化并生成文件using (FileStream fs = File.Create(Application.persistentDataPath + “/TestMsg.tang”))
        {
        msg.WriteTo(fs);
        }
      • 反序列化本地文件using (FileStream fs = File.OpenRead(Application.persistentDataPath + “/TestMsg.tang”))
        {
        TestMsg msg2 = null;
        msg2 = TestMsg.Parser.ParseFrom(fs);
        }

    • 使用MemoryStream操作字节数组

      • 序列化为字节数组using (MemoryStream ms = new MemoryStream())
        {
        msg.WriteTo(ms);
        bytes = ms.ToArray();
        }
        // 推荐使用该类内置的方法
        bytes = msg.ToByteArray();
      • 反序列化字节数组using (MemoryStream ms = new MemoryStream(bytes))
        {
        TestMsg msg2 = TestMsg.Parser.ParseFrom(ms);
        }

    • 生成的CS文件,基类为IMessage,相当于原本的BaseMessage,主要是作为形参
    • 自定义生成工具public static class NetTool
      {
      public static byte[] GetProtoBytes( IMessage msg )
      {
      //拓展方法、里氏替换、接口 这些知识点 都在 C#相关的内容当中

      //基础写法 基于上节课学习的知识点
      //byte[] bytes = null;
      //using (MemoryStream ms = new MemoryStream())
      //{
      // msg.WriteTo(ms);
      // bytes = ms.ToArray();
      //}
      //return bytes;

      //通过该拓展方法 就可以直接获取对应对象的 字节数组了
      return msg.ToByteArray();
      }

      public static T GetProtoMsg<T>(byte[] bytes) where T:class, IMessage
      {
      //得到对应消息的类型 通过反射得到内部的静态成员 然后得到其中的 对应方法
      Type type = typeof(T);
      //通过反射 得到对应的 静态成员属性对象
      PropertyInfo pInfo = type.GetProperty(“Parser”);
      object parserObj = pInfo.GetValue(null, null);
      //已经得到了对象 那么可以得到该对象中的 对应方法
      Type parserType = parserObj.GetType();
      //这是指定得到某一个重载函数
      MethodInfo mInfo = parserType.GetMethod(“ParseFrom”, new Type[] { typeof(byte[]) });
      //调用对应的方法 反序列化为指定的对象
      object msg = mInfo.Invoke(parserObj, new object[] { bytes });
      return msg as T;
      }
      }

大小端模式

  • 从左向右读的是大端模式,反之是小端模式
  • 小端语言:C#,C++
  • 大端语言:Java,Erlang,AS3
  • 大小端相互通信时需要对二进制消息进行转化
  • Array.Reverse(bytes)
  • 使用Protobuf不用考虑大小端,内部已经处理好



评论(0)

查看评论列表

暂无评论


发表评论

表情 颜文字
插入代码

个人信息

avatar

24
文章
1
评论
1
用户

近期文章