现在游戏协议的数据格式基本上都是用protobuf协议格式,而protobuf最后会转换为二进制,所以这个例子实现的逻辑的也是二进制的处理。
处理粘包拆包的逻辑主要是在DecodePackage方法中。
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using Google.Protobuf;
using Msg;
using UnityEngine;
using UnityEngine.Events;
namespace Billiard.Server
{
public class SocketMgr : SingleMgr<SocketMgr>
{
private bool m_nYesInit = false;
private Queue<byte[]> m_SendQueue = new Queue<byte[]>();
private Queue<byte[]> m_ReceiveQueue = new Queue<byte[]>();
private MemoryStream m_ReceiveStream = null;
private bool m_IsConnected = false;
private string m_IP = string.Empty;
private int m_Port = int.MaxValue;
private Socket m_Socket = null;
private IPEndPoint endPoint;
private bool sendCompletedAsync = true;
private bool receiveCompletedAsync = true;
private readonly object IncomingMessageLock = new object();
private readonly object OutcomingMessageLock = new object();
private byte[] tempBufferArr = new byte[0];
private Thread sendThread;
private Thread receiveThread;
private SocketAsyncEventArgsPool m_readWritePool;
public void Init()
{
if (!m_nYesInit)
{
OnMsg<object>((int) ENetworkMessage.Net_SyncEntityID, OnSyncEntityID);
OnMsg<object>((int) ENetworkMessage.Net_HeartBeat, OnHeartBeat);
OnMsg<object>((int) ENetworkMessage.Net_HadConnect, OnNetHadConnect);
}
m_nYesInit = true;
}
public void Connect(string ip, int port)
{
m_IP = ip;
m_Port = port;
m_Socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
endPoint = new IPEndPoint(IPAddress.Parse(m_IP), m_Port);
try
{
SocketAsyncEventArgs args = new SocketAsyncEventArgs(); //创建连接参数对象
args.RemoteEndPoint = endPoint;
args.Completed += OnConnectedCompleted; //添加连接创建成功监听
m_Socket.ConnectAsync(args);
m_ReceiveStream = new MemoryStream();
}
catch (Exception e)
{
Debug.LogWarning("连接错误! " + e.Message);
CloseSocket();
}
}
private void OnConnectedCompleted(object sender, SocketAsyncEventArgs args)
{
try
{
if (args.SocketError != SocketError.Success)
{
CloseSocket();
}
else
{
Debug.Log(
$"连接服务器成功! ip:{args.ConnectSocket.LocalEndPoint} ThreadId:{Thread.CurrentThread.ManagedThreadId.ToString()}");
m_IsConnected = true;
m_readWritePool = new SocketAsyncEventArgsPool(10, IoCompleted);
sendThread = new Thread(SendMsgThread);
sendThread.IsBackground = true;
sendThread.Start();
receiveThread = new Thread(ReceiveMsgThread);
receiveThread.IsBackground = true;
receiveThread.Start();
doSyncEntityID();
}
}
catch (Exception e)
{
Debug.Log("开启接收数据异常" + e.Message);
CloseSocket();
}
}
private void ReceiveMsgThread(object obj)
{
while (m_IsConnected)
{
if (m_Socket.Available > 0)
{
if (!receiveCompletedAsync )
{
Debug.LogWarning("---<<< 异步任务还未处理完成!");
return;
}
try
{
SocketAsyncEventArgs e = m_readWritePool.Pop();
byte[] buffer = new byte[8192];
e.SetBuffer(buffer, 0, buffer.Length);
receiveCompletedAsync = m_Socket.ReceiveAsync(e);
}
catch (Exception e)
{
Debug.LogWarning($"<<< error! {e} | 当前容量:{m_readWritePool.Count}");
}
}
}
}
public void ReceiveCallback(SocketAsyncEventArgs e)
{
if (e.BytesTransferred > 0 && e.SocketError == SocketError.Success)
{
if (e.BytesTransferred <= 0)
{
Debug.Log("---<<< 空数据不处理!");
return;
}
byte[] bytes = new byte[e.BytesTransferred];
Buffer.BlockCopy(e.Buffer, 0, bytes, 0, bytes.Length);
lock (IncomingMessageLock)
{
m_ReceiveQueue.Enqueue(bytes);
}
}
else
{
//TODO
}
}
private void SendMsgThread(object obj)
{
while (m_IsConnected)
{
//如果消息队列中有消息,则发送消息
if (m_SendQueue.Count > 0)
{
if (!sendCompletedAsync)
{
Debug.LogWarning("--->>> 异步发送io还未处理完成!");
return;
}
lock (OutcomingMessageLock)
{
try
{
byte[] sendBuffer = m_SendQueue.Dequeue();
SocketAsyncEventArgs e = m_readWritePool.Pop();
e.SetBuffer(sendBuffer, 0, sendBuffer.Length);
sendCompletedAsync = m_Socket.SendAsync(e);
}
catch (Exception e)
{
Debug.LogWarning($">>> error! {e.Message}");
}
}
}
}
}
private void SendCallback(SocketAsyncEventArgs e)
{
if (!m_Socket.Connected)
{
OnClose();
return;
}
if (e.SocketError == SocketError.Success)
{
sendCompletedAsync = true;
}
else
{
Debug.LogWarning($"--->>> 发送数据失败!");
}
}
void IoCompleted(object sender, SocketAsyncEventArgs e)
{
switch (e.LastOperation)
{
case SocketAsyncOperation.Receive:
ReceiveCallback(e);
m_readWritePool.Push(e);
break;
case SocketAsyncOperation.Send:
SendCallback(e);
m_readWritePool.Push(e);
break;
default:
throw new ArgumentException("The last operation completed on the socket was not a receive or send");
}
}
public void MsgUpdate()
{
if (m_ReceiveQueue.Count > 0)
{
lock (IncomingMessageLock)
{
DecodePackage(m_ReceiveQueue.Dequeue());
}
}
}
private void DecodePackage(byte[] buffer)
{
int length = buffer.Length;
//处理拆包、粘包
if (length >= OFFSET_SIZE + BYTE_SIZE + BYTE_SIZE)
{
if (tempBufferArr.Length > 0)
{
buffer = tempBufferArr.Concat(buffer).ToArray();
length = buffer.Length;
//Array.Clear(temgBufferArr,0,temgBufferArr.Length);
tempBufferArr = new byte[0];
}
m_ReceiveStream.Write(buffer, 0, length);
m_ReceiveStream.Position = 0;
while (m_ReceiveStream.Length != 0)
{
int oneMsgLength = (int) BitConverter.ToUInt32(buffer,
(int) m_ReceiveStream.Position + OFFSET_SIZE + BYTE_SIZE);
if (length >= OFFSET_SIZE + BYTE_SIZE + BYTE_SIZE + oneMsgLength)
{
int oneBufferLength = OFFSET_SIZE + BYTE_SIZE + BYTE_SIZE + oneMsgLength;
byte[] oneMsgBuffer = new byte[oneBufferLength];
m_ReceiveStream.Read(oneMsgBuffer, 0, oneBufferLength);
try
{
ReceivePacket(oneMsgBuffer);
}
catch (Exception e)
{
//do nothing 不处理业务层异常逻辑
}
}
else
{
//Debug.LogWarning($"---<<< 本次消息包长度不够,暂不处理!");
int leftLength = length - (int) m_ReceiveStream.Position;
tempBufferArr = new byte[leftLength];
Buffer.BlockCopy(buffer, (int) m_ReceiveStream.Position, tempBufferArr, 0, leftLength);
break;
}
if (m_ReceiveStream.Position == m_ReceiveStream.Length)
{
break;
}
}
}
else
{
Debug.LogWarning($" 包大小 : {length} 消息号 : {BitConverter.ToUInt32(buffer, OFFSET_SIZE)}");
//TODO
}
m_ReceiveStream.SetLength(0);
m_ReceiveStream.Seek(0, SeekOrigin.Begin);
receiveCompletedAsync = true;
}
private const int OFFSET_SIZE = 8;
private const int BYTE_SIZE = 4;
private byte[] msgIDBytes = new byte[BYTE_SIZE];
private byte[] msgLenBytes = new byte[BYTE_SIZE];
public void ReceivePacket(byte[] readBuff)
{
for (int i = 0; i < BYTE_SIZE; i++)
{
msgIDBytes[i] = readBuff[OFFSET_SIZE + i];
}
uint msgID = BitConverter.ToUInt32(msgIDBytes, 0);
for (int i = 0; i < BYTE_SIZE; i++)
{
msgLenBytes[i] = readBuff[OFFSET_SIZE + BYTE_SIZE + i];
}
uint msgLen = BitConverter.ToUInt32(msgLenBytes, 0);
if (msgID != 100006)
Debug.Log("接收消息 :" + msgID + " 消息 :" + msgLen);
IMessage rspPacket = null;
if (msgID == (uint) ENetworkMessage.Net_SyncEntityID
|| msgID == (uint) ENetworkMessage.Net_HeartBeat
|| msgID == (uint) ENetworkMessage.Net_HadConnect)
{
//特殊处理,不处理回包仅转发
}
else
{
int StartIdx = OFFSET_SIZE + BYTE_SIZE + BYTE_SIZE;
int PacketSize = readBuff.Length - StartIdx;
// 长度校验
if (StartIdx + msgLen != readBuff.Length)
{
Debug.LogError(
$"pack size error! msgID : {msgID} StartIdx : {StartIdx} msgLen : {msgLen} BuffLegnth : {readBuff.Length}");
return;
}
rspPacket = GameServerMgr.DecodeMsg((MsgTile) msgID, StartIdx, PacketSize, readBuff);
if (rspPacket == null)
{
Debug.LogWarning(" Unpack Error! MsgID :" + msgID);
return;
}
}
DispatchMsgData<object>((int) msgID, rspPacket);
}
public void SendMsg(MsgTile networkMessage, byte[] packet)
{
if (m_Socket == null)
{
return;
}
//判断Websocket状态
if (!m_Socket.Connected)
{
return;
}
byte[] offset1 = MiniConverter.IntToBytes(2 + 2 + 4 + 4 + packet.Length);
byte[] offset2 = MiniConverter.IntToBytes(1);
byte[] offset3 = MiniConverter.IntToBytes(2);
byte[] networkMessageBytes = MiniConverter.IntToBytes((int) networkMessage);
byte[] offset4 = networkMessageBytes;
byte[] offset5 = MiniConverter.IntToBytes(networkMessageBytes.Length);
byte[] sendBuffer = new byte[4 + 2 + 2 + 4 + 4 + (int) packet.Length];
Array.Copy(offset1, 0, sendBuffer, 0, 4);
Array.Copy(offset2, 0, sendBuffer, 4, 2);
Array.Copy(offset3, 0, sendBuffer, 6, 2);
Array.Copy(offset4, 0, sendBuffer, 8, 4);
Array.Copy(offset5, 0, sendBuffer, 12, 4);
Array.Copy(packet, 0, sendBuffer, 16, packet.Length);
Debug.Log("发送消息 : " + (int) networkMessage + " 包长度 :" + packet.Length);
lock (OutcomingMessageLock)
{
m_SendQueue.Enqueue(sendBuffer);
}
}
private Dictionary<int, IEventInfo> m_msgMap = new Dictionary<int, IEventInfo>(); //消息集合
public void OnMsg<T>(int msgkey, UnityAction<T> callbackFun)
{
if (m_msgMap.ContainsKey(msgkey))
{
(m_msgMap[msgkey] as EventInfo<T>).actions += callbackFun;
}
else
{
m_msgMap.Add(msgkey, new EventInfo<T>(callbackFun));
}
}
public void OnRemoveMsg<T>(int msgkey, UnityAction<T> callbackFun)
{
if (m_msgMap.ContainsKey(msgkey))
{
(m_msgMap[msgkey] as EventInfo<T>).actions -= callbackFun;
}
}
public void DispatchMsgData<T>(int msgID, T info)
{
if (m_msgMap.ContainsKey(msgID))
{
(m_msgMap[msgID] as EventInfo<T>).actions?.Invoke(info);
}
else
{
if (msgID != 100006)
Debug.LogWarning(" No msg regiter ! id :" + msgID);
}
}
public void doSyncEntityID()
{
byte[] b = MiniConverter.IntToBytes(PlayerData.EntityID);
byte[] sendBuffer = new byte[4];
Array.Copy(b, 0, sendBuffer, 0, b.Length);
SendMsg((MsgTile) ENetworkMessage.Net_SyncEntityID, sendBuffer);
}
public void OnSyncEntityID(object obj)
{
EventMgr.GetInstance().DispatchEvent(WSEvent.HandShake);
}
//发送心跳包
public void OnHeartBeat(object obj)
{
byte[] b = MiniConverter.IntToBytes(PlayerData.EntityID);
byte[] sendBuffer = new byte[4];
Array.Copy(b, 0, sendBuffer, 0, b.Length);
SendMsg((MsgTile) ENetworkMessage.Net_HeartBeat, sendBuffer);
}
//该用户已经在登录中
public void OnNetHadConnect(object obj)
{
UICommon.GetInstance().ShowDialogYes("提示", "该用户正在登录中!");
}
public void OnClose()
{
UICommon.GetInstance().ShowDialogYesAndNo("提示", "服务器已经断开连接!",
() => { SceneMgr.GetInstance().EnterLoginScene(); },
() => { SceneMgr.GetInstance().EnterLoginScene(); },
null,
null);
}
public void CloseSocket()
{
if (m_Socket != null && m_Socket.Connected)
{
m_IsConnected = false;
m_ReceiveQueue.Clear();
m_ReceiveQueue.Clear();
m_Socket?.Shutdown(SocketShutdown.Both);
m_Socket?.Close();
m_Socket?.Dispose();
m_ReceiveStream?.Close();
m_ReceiveStream?.Dispose();
sendThread.Abort();
receiveThread.Abort();
sendCompletedAsync = true;
receiveCompletedAsync = true;
m_readWritePool.Clear();
}
}
}
}