Unity版本: 5.3
使用語言: C#
寫在前面
ProtoBuf是Google公司推出的一種二進制序列化工具,適用于數據的網絡傳輸。 基于Socket實現時時通信,關于數據粘包的編碼和解碼處理是必不可少的。
實現功能:
1.基于ProtoBuf序列化對象
2.使用Socket實現時時通信
3.數據包的編碼和解碼
3.數據包的編碼和解碼
首先,舉個例子,這個月信用卡被媳婦刷爆了,面對房貸車貸的壓力,我只能選擇分期付款。。。
那么OK了,現在我想問一下,當服務器向客戶端發(fā)送的數據過大時怎么辦呢?
當服務器需要向客戶端發(fā)送一條很長的數據,也會“分期付款!”,服務器會把一條很長的數據分成若干條小數據,多次發(fā)送給客戶端。
可是,這樣就又有另外一個問題,客戶端接受到多條數據之后如何解析?
這里其實就是客戶端的解碼。server發(fā)數據一般采用“長度+內容”的格式,Client接收到數據之后,先提取出長度來,然后根據長度判斷內容是否發(fā)送完畢。
再次重申,用戶在發(fā)送序列化好的消息的前,需要先編碼后再發(fā)送消息;用戶在接受消息后,需要解碼之后再解析數據(反序列化)。
using UnityEngine;
using System.Collections.Generic;
using System.IO;
/// <summary>
/// 編碼和解碼
/// </summary>
public class NetEncode {
/// <summary>
/// 將數據編碼 長度+內容
/// </summary>
/// <param name="data">內容</param>
public static byte[] Encode(byte[] data)
{
//整形占四個字節(jié),所以聲明一個+4的數組
byte[] result = new byte[data.Length + 4];
//使用流將編碼寫二進制
MemoryStream ms = new MemoryStream();
BinaryWriter br = new BinaryWriter(ms);
br.Write(data.Length);
br.Write(data);
//將流中的內容復制到數組中
System.Buffer.BlockCopy(ms.ToArray(), 0, result, 0, (int)ms.Length);
br.Close();
ms.Close();
return result;
}
/// <summary>
/// 將數據解碼
/// </summary>
/// <param name="cache">消息隊列</param>
public static byte[] Decode(ref List<byte> cache)
{
//首先要獲取長度,整形4個字節(jié),如果字節(jié)數不足4個字節(jié)
if(cache.Count < 4)
{
return null;
}
//讀取數據
MemoryStream ms = new MemoryStream(cache.ToArray());
BinaryReader br = new BinaryReader(ms);
int len = br.ReadInt32();
//根據長度,判斷內容是否傳遞完畢
if(len > ms.Length - ms.Position)
{
return null;
}
//獲取數據
byte[] result = br.ReadBytes(len);
//清空消息池
cache.Clear();
//講剩余沒處理的消息存入消息池
cache.AddRange(br.ReadBytes((int)ms.Length - (int)ms.Position));
return result;
}
}
用戶接受數據代碼如下:
using System;
using System.Collections.Generic;
using System.Net.Sockets;
/// <summary>
/// 表示一個客戶端
/// </summary>
public class NetUserToken {
//連接客戶端的Socket
public Socket socket;
//用于存放接收數據
public byte[] buffer;
//每次接受和發(fā)送數據的大小
private const int size = 1024;
//接收數據池
private List<byte> receiveCache;
private bool isReceiving;
//發(fā)送數據池
private Queue<byte[]> sendCache;
private bool isSending;
//接收到消息之后的回調
public Action<NetModel> receiveCallBack;
public NetUserToken()
{
buffer = new byte[size];
receiveCache = new List<byte>();
sendCache = new Queue<byte[]>();
}
/// <summary>
/// 服務器接受客戶端發(fā)送的消息
/// </summary>
/// <param name="data">Data.</param>
public void Receive(byte[] data)
{
UnityEngine.Debug.Log("接收到數據");
//將接收到的數據放入數據池中
receiveCache.AddRange(data);
//如果沒在讀數據
if(!isReceiving)
{
isReceiving = true;
ReadData();
}
}
/// <summary>
/// 讀取數據
/// </summary>
private void ReadData()
{
byte[] data = NetEncode.Decode(ref receiveCache);
//說明數據保存成功
if(data != null)
{
NetModel item = NetSerilizer.DeSerialize(data);
UnityEngine.Debug.Log(item.Message);
if(receiveCallBack != null)
{
receiveCallBack(item);
}
//尾遞歸,繼續(xù)讀取數據
ReadData();
}
else
{
isReceiving = false;
}
}
/// <summary>
/// 服務器發(fā)送消息給客戶端
/// </summary>
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("發(fā)送成功!");
Send ();
} catch (Exception ex) {
UnityEngine.Debug.Log(ex.ToString());
}
}
public void WriteSendDate(byte[] data){
sendCache.Enqueue(data);
if(!isSending)
{
isSending = true;
Send();
}
}
}
寫在最后
#成功的道路沒有捷徑,代碼這條路更是如此,唯有敲才是王道。
|