C uses Protocol Buffer of ProtoBuf for Socket communication in Unity

  • 2021-09-24 23:27:46
  • OfStack

First of all, let's talk about the functions to be realized by the examples in this article:

Serializing Objects Based on ProtoBuf Using Socket to realize time-to-time communication Encoding and decoding of data packets

Here's a look at the specific steps:

1. Use ProtoBuf in Unity

Import DLL into Unity,
Create a model class for network transport:


using System;
using ProtoBuf;

// Add a feature that indicates that it can be used by ProtoBuf Tool serialization 
[ProtoContract]
public class NetModel {
 // Add an attribute to indicate that the field can be serialized, 1 It can be understood as subscript 
 [ProtoMember(1)] 
 public int ID;
 [ProtoMember(2)]
 public string Commit;
 [ProtoMember(3)]
 public string Message;
}

using System;
using ProtoBuf;
 
// Add a feature that indicates that it can be used by ProtoBuf Tool serialization 
[ProtoContract]
public class NetModel {
 // Add an attribute to indicate that the field can be serialized, 1 It can be understood as subscript 
 [ProtoMember(1)] 
 public int ID;
 [ProtoMember(2)]
 public string Commit;
 [ProtoMember(3)]
 public string Message;
}

Add test scripts to Unity and introduce the use of ProtoBuf tools.


using System;
using System.IO;

public class Test : MonoBehaviour {

 void Start () {
  // Create an object 
  NetModel item = new NetModel(){ID = 1, Commit = "LanOu", Message = "Unity"};
  // Serialize object 
  byte[] temp = Serialize(item);
  //ProtoBuf Advantages of 1 : Small 
  Debug.Log(temp.Length);
  // Deserialize to an object 
  NetModel result = DeSerialize(temp);
  Debug.Log(result.Message);

 }

 //  Serialize a message into a 2 Binary method 
 // < param name="model"> Objects to serialize < /param>
 private byte[] Serialize(NetModel model)
 {
  try {
   // Involving format conversion, you need to use streams, and you will 2 Binary serialization into stream 
   using (MemoryStream ms = new MemoryStream()) {
    // Use ProtoBuf Serialization method of tool 
    ProtoBuf.Serializer.Serialize<NetModel> (ms, model);
    // Definition 2 Hierarchical array to save the serialized results 
    byte[] result = new byte[ms.Length];
    // Set the location of the stream to 0 , starting point 
    ms.Position = 0;
    // Read the contents of the stream to the 2 In a binary array 
    ms.Read (result, 0, result.Length);
    return result;
   }
  } catch (Exception ex) {
   Debug.Log (" Serialization failed : " + ex.ToString());
   return null;
  }
 }

 //  Deserializes a received message into an object 
 // < returns>The serialize.< /returns>
 // < param name="msg"> Messages received .</param>
 private NetModel DeSerialize(byte[] msg)
 {
  try {
   using (MemoryStream ms = new MemoryStream()) {
    // Writing a message to a stream 
    ms.Write (msg, 0, msg.Length);
    // Return the position of the stream to 0
    ms.Position = 0;
    // Deserializing Objects Using Tools 
    NetModel result = ProtoBuf.Serializer.Deserialize<NetModel> (ms);
    return result;
   }
  } catch (Exception ex) {  
    Debug.Log(" Deserialization failed : " + ex.ToString());
    return null;
  }
 }
}

using System;
using System.IO;
 
public class Test : MonoBehaviour {
 
 void Start () {
  // Create an object 
  NetModel item = new NetModel(){ID = 1, Commit = "LanOu", Message = "Unity"};
  // Serialize object 
  byte[] temp = Serialize(item);
  //ProtoBuf Advantages of 1 : Small 
  Debug.Log(temp.Length);
  // Deserialize to an object 
  NetModel result = DeSerialize(temp);
  Debug.Log(result.Message);
 
 }
 
 //  Serialize a message into a 2 Binary method 
 // < param name="model"> Objects to serialize < /param>
 private byte[] Serialize(NetModel model)
 {
  try {
   // Involving format conversion, you need to use streams, and you will 2 Binary serialization into stream 
   using (MemoryStream ms = new MemoryStream()) {
    // Use ProtoBuf Serialization method of tool 
    ProtoBuf.Serializer.Serialize<NetModel> (ms, model);
    // Definition 2 Hierarchical array to save the serialized results 
    byte[] result = new byte[ms.Length];
    // Set the location of the stream to 0 , starting point 
    ms.Position = 0;
    // Read the contents of the stream to the 2 In a binary array 
    ms.Read (result, 0, result.Length);
    return result;
   }
  } catch (Exception ex) {
   Debug.Log (" Serialization failed : " + ex.ToString());
   return null;
  }
 }
 
 //  Deserializes a received message into an object 
 // < returns>The serialize.< /returns>
 // < param name="msg"> Messages received .</param>
 private NetModel DeSerialize(byte[] msg)
 {
  try {
   using (MemoryStream ms = new MemoryStream()) {
    // Writing a message to a stream 
    ms.Write (msg, 0, msg.Length);
    // Return the position of the stream to 0
    ms.Position = 0;
    // Deserializing Objects Using Tools 
    NetModel result = ProtoBuf.Serializer.Deserialize<NetModel> (ms);
    return result;
   }
  } catch (Exception ex) {  
    Debug.Log(" Deserialization failed : " + ex.ToString());
    return null;
  }
 }
}

2. Using Socket in Unity to realize time-to-time communication

Functions that communication should achieve:

The server can listen to multiple clients from time to time The server can listen to a certain 1 client message from time to time The server can send messages to one client from time to time First we need to define a client object

using System;
using System.Net.Sockets;

//  Denote 1 Clients 
public class NetUserToken {
 // Connecting to the client's Socket
 public Socket socket;
 // Used for storing received data 
 public byte[] buffer;

 public NetUserToken()
 {
  buffer = new byte[1024];
 }

 //  Accept a message 
 // < param name="data">Data.< /param>
 public void Receive(byte[] data)
 {
  UnityEngine.Debug.Log(" Message received! ");
 }

 //  Send a message 
 //< param name="data">Data.< /param>
 public void Send(byte[] data)
 {  

 }
}

using System;
using System.Net.Sockets;
 
//  Denote 1 Clients 
public class NetUserToken {
 // Connecting to the client's Socket
 public Socket socket;
 // Used for storing received data 
 public byte[] buffer;
 
 public NetUserToken()
 {
  buffer = new byte[1024];
 }
 
 //  Accept a message 
 // < param name="data">Data.< /param>
 public void Receive(byte[] data)
 {
  UnityEngine.Debug.Log(" Message received! ");
 }
 
 //  Send a message 
 //< param name="data">Data.< /param>
 public void Send(byte[] data)
 {  
 
 }
}


Then implement our server code


using System.Collections;
using System.Collections.Generic;
using System.Net;
using System;
using System.Net.Sockets;

public class NetServer{
 // Singleton script 
 public static readonly NetServer Instance = new NetServer();
 // Definition tcp Server 
 private Socket server;
 private int maxClient = 10;
 // Defining a port 
 private int port = 35353;
 // User pool 
 private Stack<NetUserToken> pools;
 private NetServer()
 {
  // Initialization socket
  server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
  server.Bind(new IPEndPoint(IPAddress.Any, port));

 }

 // Start the server 
 public void Start()
 {
  server.Listen(maxClient);
  UnityEngine.Debug.Log("Server OK!");
  // Instantiate the user pool of the client 
  pools = new Stack<NetUserToken>(maxClient);
  for(int i = 0; i < maxClient; i++)
  {
   NetUserToken usertoken = new NetUserToken();
   pools.Push(usertoken);
  }
  // You can accept clients asynchronously , BeginAccept The first of a function 1 Parameters are callback functions, which are automatically called when there is a client connection 
  server.BeginAccept (AsyncAccept, null);
 }

 // Callback function,   This method is automatically called when there is a client connection 
 private void AsyncAccept(IAsyncResult result)
 {
  try {
   // End listening and get the client at the same time 
   Socket client = server.EndAccept(result);
   UnityEngine.Debug.Log(" There is a client connection ");
   // Coming 1 Clients 
   NetUserToken userToken = pools.Pop();
   userToken.socket = client;
   // After the client connects, it can accept client messages 
   BeginReceive(userToken);

   // Tail recursion, listening again to see if there are other clients connected 
   server.BeginAccept(AsyncAccept, null);
  } catch (Exception ex) {
   UnityEngine.Debug.Log(ex.ToString());
  }
 }

 // Listening for messages asynchronously 
 private void BeginReceive(NetUserToken userToken)
 {
  try {
   // Asynchronous method 
   userToken.socket.BeginReceive(userToken.buffer, 0, userToken.buffer.Length, SocketFlags.None,
           EndReceive, userToken);
  } catch (Exception ex) {
   UnityEngine.Debug.Log(ex.ToString());
  }
 }

 // Function called after listening for a message 
 private void EndReceive(IAsyncResult result)
 {
  try {
   // Take out the client 
   NetUserToken userToken = result.AsyncState as NetUserToken;
   // Gets the length of the message 
   int len = userToken.socket.EndReceive(result);
   if(len > 0)
   { 
    byte[] data = new byte[len];
    Buffer.BlockCopy(userToken.buffer, 0, data, 0, len);
    // User acceptance message 
    userToken.Receive(data);
    // Tail recursion, listening for client messages again 
    BeginReceive(userToken);
   }

  } catch (Exception ex) {
   UnityEngine.Debug.Log(ex.ToString());
  }
 }
}

using System.Collections;
using System.Collections.Generic;
using System.Net;
using System;
using System.Net.Sockets;
 
public class NetServer{
 // Singleton script 
 public static readonly NetServer Instance = new NetServer();
 // Definition tcp Server 
 private Socket server;
 private int maxClient = 10;
 // Defining a port 
 private int port = 35353;
 // User pool 
 private Stack<NetUserToken> pools;
 private NetServer()
 {
  // Initialization socket
  server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
  server.Bind(new IPEndPoint(IPAddress.Any, port));
 
 }
 
 // Start the server 
 public void Start()
 {
  server.Listen(maxClient);
  UnityEngine.Debug.Log("Server OK!");
  // Instantiate the user pool of the client 
  pools = new Stack<NetUserToken>(maxClient);
  for(int i = 0; i < maxClient; i++)
  {
   NetUserToken usertoken = new NetUserToken();
   pools.Push(usertoken);
  }
  // You can accept clients asynchronously , BeginAccept The first of a function 1 Parameters are callback functions, which are automatically called when there is a client connection 
  server.BeginAccept (AsyncAccept, null);
 }
 
 // Callback function,   This method is automatically called when there is a client connection 
 private void AsyncAccept(IAsyncResult result)
 {
  try {
   // End listening and get the client at the same time 
   Socket client = server.EndAccept(result);
   UnityEngine.Debug.Log(" There is a client connection ");
   // Coming 1 Clients 
   NetUserToken userToken = pools.Pop();
   userToken.socket = client;
   // After the client connects, it can accept client messages 
   BeginReceive(userToken);
 
   // Tail recursion, listening again to see if there are other clients connected 
   server.BeginAccept(AsyncAccept, null);
  } catch (Exception ex) {
   UnityEngine.Debug.Log(ex.ToString());
  }
 }
 
 // Listening for messages asynchronously 
 private void BeginReceive(NetUserToken userToken)
 {
  try {
   // Asynchronous method 
   userToken.socket.BeginReceive(userToken.buffer, 0, userToken.buffer.Length, SocketFlags.None,
           EndReceive, userToken);
  } catch (Exception ex) {
   UnityEngine.Debug.Log(ex.ToString());
  }
 }
 
 // Function called after listening for a message 
 private void EndReceive(IAsyncResult result)
 {
  try {
   // Take out the client 
   NetUserToken userToken = result.AsyncState as NetUserToken;
   // Gets the length of the message 
   int len = userToken.socket.EndReceive(result);
   if(len > 0)
   { 
    byte[] data = new byte[len];
    Buffer.BlockCopy(userToken.buffer, 0, data, 0, len);
    // User acceptance message 
    userToken.Receive(data);
    // Tail recursion, listening for client messages again 
    BeginReceive(userToken);
   }
 
  } catch (Exception ex) {
   UnityEngine.Debug.Log(ex.ToString());
  }
 }
}


Start the server in Unity, and use C # console to simulate client connection and send message operation. Test OK, Unity can always listen to messages.


using UnityEngine;
using System.Collections;

public class CreateServer : MonoBehaviour {

 void StartServer () {
  NetServer.Instance.Start();
 }

}

//C# Console engineering 

using System;
using System.Net;
using System.Net.Sockets;
using System.IO;
using System.Text;

namespace Temp
{
 class MainClass
 {
  public static void Main (string[] args)
  {
   TcpClient tc = new TcpClient();
   IPEndPoint ip = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 35353);
   tc.Connect(ip);

   if(tc.Connected)
   {
    while(true)
    {

     string msg = Console.ReadLine();
     byte[] result = Encoding.UTF8.GetBytes(msg);
     tc.GetStream().Write(result, 0, result.Length);
    }
   }
   Console.ReadLine();
  }
 }
}

using UnityEngine;
using System.Collections;
 
public class CreateServer : MonoBehaviour {
 
 void StartServer () {
  NetServer.Instance.Start();
 }
 
}
 
//C# Console engineering 
 
using System;
using System.Net;
using System.Net.Sockets;
using System.IO;
using System.Text;
 
namespace Temp
{
 class MainClass
 {
  public static void Main (string[] args)
  {
   TcpClient tc = new TcpClient();
   IPEndPoint ip = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 35353);
   tc.Connect(ip);
 
   if(tc.Connected)
   {
    while(true)
    {
 
     string msg = Console.ReadLine();
     byte[] result = Encoding.UTF8.GetBytes(msg);
     tc.GetStream().Write(result, 0, result.Length);
    }
   }
   Console.ReadLine();
  }
 }
}

3. Encoding and decoding of data packets

First of all, for example, this month, my credit card was maxed out by my daughter-in-law. Facing the pressure of mortgage and car loan, I can only choose to pay by installments. . .

So OK, now I want to ask 1, when the server sends too much data to the client, what should I do?

When the server needs to send a long piece of data to the client, it will also "pay by installments!" The server will divide a long piece of data into several small pieces of data and send them to the client several times.

However, there is another problem. How to parse multiple pieces of data after the client receives them?

This is actually the decoding of the client. server sends data 1 in the format of "length + content". After Client receives the data, it first extracts the length, and then judges whether the content is sent according to the length.

Once again, before sending serialized messages, users need to encode them before sending them; After receiving the message, the user needs to decode and then parse the data (deserialization).


using UnityEngine;
using System.Collections.Generic;
using System.IO;

//  Encoding and decoding 
public class NetEncode {

 //  Encode data   Length + Content 
 /// < param name="data"> Content < /param>
 public static byte[] Encode(byte[] data)
 {
  // Plastic accounting 4 Bytes, so declare 1 A +4 Array of 
  byte[] result = new byte[data.Length + 4];
  // Use streams to write the encoding to 2 Binary system 
  MemoryStream ms = new MemoryStream();
  BinaryWriter br = new BinaryWriter(ms);
  br.Write(data.Length);
  br.Write(data);
  // Copy the contents of the stream into an array 
  System.Buffer.BlockCopy(ms.ToArray(), 0, result, 0, (int)ms.Length);
  br.Close();
  ms.Close();
  return result;
 }

 //  Decode data 
 // < param name="cache"> Message queue < /param>
 public static byte[] Decode(ref List<byte> cache)
 {
  // First of all, we must obtain the length and shape 4 Bytes, if the number of bytes is insufficient 4 Bytes 
  if(cache.Count < 4)
  {
   return null;
  }
  // Read data 
  MemoryStream ms = new MemoryStream(cache.ToArray());
  BinaryReader br = new BinaryReader(ms);
  int len = br.ReadInt32();
  // According to the length, it is judged whether the content is delivered or not 
  if(len > ms.Length - ms.Position)
  {
   return null;
  }
  // Get data 
  byte[] result = br.ReadBytes(len);
  // Empty the message pool 
  cache.Clear();
  // Remaining unprocessed messages are stored in the message pool 
  cache.AddRange(br.ReadBytes((int)ms.Length - (int)ms.Position));

  return result;
 }
}

using UnityEngine;
using System.Collections.Generic;
using System.IO;
 
//  Encoding and decoding 
public class NetEncode {
 
 //  Encode data   Length + Content 
 /// < param name="data"> Content < /param>
 public static byte[] Encode(byte[] data)
 {
  // Plastic accounting 4 Bytes, so declare 1 A +4 Array of 
  byte[] result = new byte[data.Length + 4];
  // Use streams to write the encoding to 2 Binary system 
  MemoryStream ms = new MemoryStream();
  BinaryWriter br = new BinaryWriter(ms);
  br.Write(data.Length);
  br.Write(data);
  // Copy the contents of the stream into an array 
  System.Buffer.BlockCopy(ms.ToArray(), 0, result, 0, (int)ms.Length);
  br.Close();
  ms.Close();
  return result;
 }
 
 //  Decode data 
 // < param name="cache"> Message queue < /param>
 public static byte[] Decode(ref List<byte> cache)
 {
  // First of all, we must obtain the length and shape 4 Bytes, if the number of bytes is insufficient 4 Bytes 
  if(cache.Count < 4)
  {
   return null;
  }
  // Read data 
  MemoryStream ms = new MemoryStream(cache.ToArray());
  BinaryReader br = new BinaryReader(ms);
  int len = br.ReadInt32();
  // According to the length, it is judged whether the content is delivered or not 
  if(len > ms.Length - ms.Position)
  {
   return null;
  }
  // Get data 
  byte[] result = br.ReadBytes(len);
  // Empty the message pool 
  cache.Clear();
  // Remaining unprocessed messages are stored in the message pool 
  cache.AddRange(br.ReadBytes((int)ms.Length - (int)ms.Position));
 
  return result;
 }
}

The user accepted data code is as follows:


using System;
using System.Collections.Generic;
using System.Net.Sockets;

//  Denote 1 Clients 
public class NetUserToken {
 // Connecting to the client's Socket
 public Socket socket;
 // Used for storing received data 
 public byte[] buffer;
 // Size of data received and sent each time 
 private const int size = 1024;

 // Receive data pool 
 private List<byte> receiveCache;
 private bool isReceiving;
 // Send data pool 
 private Queue<byte[]> sendCache;
 private bool isSending;

 // Callback after receiving message 
 public Action<NetModel> receiveCallBack;


 public NetUserToken()
 {
  buffer = new byte[size];
  receiveCache = new List<byte>();
  sendCache = new Queue<byte[]>();
 }

 //  The server accepts the message sent by the client 
 // < param name="data">Data.< /param>
 public void Receive(byte[] data)
 {
  UnityEngine.Debug.Log(" Received data ");
  // Put the received data into the data pool 
  receiveCache.AddRange(data);
  // If you are not reading the data 
  if(!isReceiving)
  {
   isReceiving = true;
   ReadData();
  }
 }

 //  Read data 
 private void ReadData()
 {
  byte[] data = NetEncode.Decode(ref receiveCache);
  // Indicate that the data was saved successfully 
  if(data != null)
  {
   NetModel item = NetSerilizer.DeSerialize(data);
   UnityEngine.Debug.Log(item.Message);
   if(receiveCallBack != null)
   {
    receiveCallBack(item);
   }
   // Tail recursion, continue reading data 
   ReadData();
  }
  else
  {
   isReceiving = false;
  }
 }

 //  The server sends a message to the client 
 public void Send()
 {
  try {
   if (sendCache.Count == 0) {
    isSending = false;
    return; 
   }
   byte[] data = sendCache.Dequeue ();
   int count = data.Length / size;
   int len = size;
   for (int i = 0; i < count + 1; i++) {
    if (i == count) {
     len = data.Length - i * size;
    }
    socket.Send (data, i * size, len, SocketFlags.None);
   }
   UnityEngine.Debug.Log(" Send successful !");
   Send ();
  } catch (Exception ex) {
   UnityEngine.Debug.Log(ex.ToString());
  }
 }

 public void WriteSendDate(byte[] data){
  sendCache.Enqueue(data);
  if(!isSending)
  {
   isSending = true;
   Send();
  }
 }
}

using System;
using System.Collections.Generic;
using System.Net.Sockets;
 
//  Denote 1 Clients 
public class NetUserToken {
 // Connecting to the client's Socket
 public Socket socket;
 // Used for storing received data 
 public byte[] buffer;
 // Size of data received and sent each time 
 private const int size = 1024;
 
 // Receive data pool 
 private List<byte> receiveCache;
 private bool isReceiving;
 // Send data pool 
 private Queue<byte[]> sendCache;
 private bool isSending;
 
 // Callback after receiving message 
 public Action<NetModel> receiveCallBack;
 
 
 public NetUserToken()
 {
  buffer = new byte[size];
  receiveCache = new List<byte>();
  sendCache = new Queue<byte[]>();
 }
 
 //  The server accepts the message sent by the client 
 // < param name="data">Data.< /param>
 public void Receive(byte[] data)
 {
  UnityEngine.Debug.Log(" Received data ");
  // Put the received data into the data pool 
  receiveCache.AddRange(data);
  // If you are not reading the data 
  if(!isReceiving)
  {
   isReceiving = true;
   ReadData();
  }
 }
 
 //  Read data 
 private void ReadData()
 {
  byte[] data = NetEncode.Decode(ref receiveCache);
  // Indicate that the data was saved successfully 
  if(data != null)
  {
   NetModel item = NetSerilizer.DeSerialize(data);
   UnityEngine.Debug.Log(item.Message);
   if(receiveCallBack != null)
   {
    receiveCallBack(item);
   }
   // Tail recursion, continue reading data 
   ReadData();
  }
  else
  {
   isReceiving = false;
  }
 }
 
 //  The server sends a message to the client 
 public void Send()
 {
  try {
   if (sendCache.Count == 0) {
    isSending = false;
    return; 
   }
   byte[] data = sendCache.Dequeue ();
   int count = data.Length / size;
   int len = size;
   for (int i = 0; i < count + 1; i++) {
    if (i == count) {
     len = data.Length - i * size;
    }
    socket.Send (data, i * size, len, SocketFlags.None);
   }
   UnityEngine.Debug.Log(" Send successful !");
   Send ();
  } catch (Exception ex) {
   UnityEngine.Debug.Log(ex.ToString());
  }
 }
 
 public void WriteSendDate(byte[] data){
  sendCache.Enqueue(data);
  if(!isSending)
  {
   isSending = true;
   Send();
  }
 }
}

The ProtoBuf network transmission is complete here.


Related articles: