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 packetsHere'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.