Socket的使用
项目中如果需要快发开发Socket可以直接SuperSocket或者FastSocket开源框架,开发过程中只需要关注应用层方面的代码即可。当然为了更加自由方便的扩展自己的开发需求,还是有必要从零开始搭建一个自己的网络框架。
开发前的基本理论知识可以去百度搜索一下,这边就直接专注于使用和封装Socket。
public class NetWorkSocket : SingletonMono<NetWorkSocket>
{
/// <summary>
/// 客户端Socket
/// </summary>
private Socket m_Client;
// private byte[] buffer = new byte[10240];
/// <summary>
/// 压缩数组的长度界限
/// </summary>
private const int m_CompressLen = 200;
#region 发送消息变量
/// <summary>
/// 发送消息队列
/// </summary>
private Queue<byte[]> m_SendQueue = new Queue<byte[]>();
/// <summary>
/// 检查队列委托
/// </summary>
private Action m_CheckSendQueue;
#endregion
#region 接收数据所需变量
/// <summary>
/// 接受数据包字节缓存区
/// </summary>
private byte[] m_ReceiveBuffer = new byte[2048 * 10];
/// <summary>
/// 接收数据包的缓存数据流
/// </summary>
private MemoryStreamBuffer m_ReceiveMS = new MemoryStreamBuffer();
/// <summary>
/// 接受消息的队列
/// </summary>
private Queue<byte[]> m_ReceiveQueue = new Queue<byte[]>();
private int m_ReceiveCount = 0;
#endregion
public Action OnConectOk;
protected override void OnAwake()
{
base.OnAwake();
}
protected override void OnStart()
{
base.OnStart();
}
protected override void BeforeOnDestroy()
{
base.BeforeOnDestroy();
DisConnected();
}
public void DisConnected()
{
if (m_Client != null && m_Client.Connected)
{
m_Client.Shutdown(SocketShutdown.Both);
m_Client.Close();
}
}
}
使用继承Mono的单例模式,简单的处理一下NetWorkSocket 的生命周期。
数据发送
发送数据到服务器时把数据添加到消息队列中,并通过一个委托检测消息队列,如果队列长度大于0,数据出栈后发送数据后继续检测队列,完成循环发送数据。
public void SendMsg(byte[] data)
{
--------------------------------------
//string result = string.Empty; //StringBuilder more batter
//for (int i = 0; i < data.Length; i++)
//{
// result += data[i].ToString("X2") + " ";
//}
//Debug.Log(result);
//Debug.Log("发送data---------");
//--------------------------------------
//数据 加密
byte[] sendBuffer = MakeData(data);
lock (m_SendQueue)
{
m_SendQueue.Enqueue(sendBuffer);
m_CheckSendQueue.BeginInvoke(null, null);
}
}
#endregion
#region Send真发送数据包的服务器
/// <summary>
/// 真发送数据包的服务器
/// </summary>
/// <param name="buffer"></param>
private void Send(byte[] buffer)
{
m_Client.BeginSend(buffer, 0, buffer.Length, SocketFlags.None, SendCallBack, m_Client);
}
private void SendCallBack(IAsyncResult ar)
{
m_Client.EndSend(ar);
OnCheckSendQueueCallBack();
}
#endregion
/// <summary>
/// 检查队列委托的回调
/// </summary>
private void OnCheckSendQueueCallBack()
{
//如果队列中有数据包 则发送数据包
if (m_SendQueue.Count > 0)
{
Send(m_SendQueue.Dequeue());
}
}
发送数据的数据是二进制数组,这个数据通过是采用Protobuf或者Json等常用网络格式序列化生成的,然后通过组合的方式将 数据包长度 + 数据包体 组成形成 数据包。中间也可以加入一些字段标识字段,例如发送消息ID,服务器根据ID响应,避免一些外挂发送假数据。当然 数据包体 也是需要加密的。
提供一个常用加密方式:
- 判断是否压缩数据
- 数据进行异或运行
- crc校验
- 压缩 flag 和 CRC 校验值 写入数据包
数据接收
数据接收过程中常出现数据粘包,所以需要将数据放入到缓存流中循环拆包
private void ReceiveCallBack(IAsyncResult ar)
{
try
{
int len = m_Client.EndReceive(ar);
if (len > 0)
{
//已经接收到数据
//把接受到数据 写入缓冲数据流尾部
m_ReceiveMS.Position = m_ReceiveMS.Length;
//把指定长度的字节写入数据流
m_ReceiveMS.Write(m_ReceiveBuffer, 0, len);
//如果缓存>2说明至少有一个不完整的包发送过来了,客户端定义Ushort就是2
if (m_ReceiveMS.Length > 2)
{
//循环 拆分包
while (true)
{
//把数据流指针位置放在0
m_ReceiveMS.Position = 0;
//包体的长度
int currMsgLen = m_ReceiveMS.ReadUShort();
//总包的长度
int currFullMsgLen = 2 + currMsgLen;
//如果缓存流的数据》=整包,说明至少接收有一个完整
if (m_ReceiveMS.Length >= currFullMsgLen)
{
//定义包体的数组
byte[] buffer = new byte[currMsgLen];
//把数据流指针位置放在2,也就是包体的位置
m_ReceiveMS.Position = 2;
//把数据流读到数组里buffer也就是我们要的数据
m_ReceiveMS.Read(buffer, 0, currMsgLen);
lock (m_ReceiveQueue)
{
m_ReceiveQueue.Enqueue(buffer);
}
//===========================处理剩余字节====================================
//剩余字节
int reMainLen = (int)m_ReceiveMS.Length - currFullMsgLen;
if (reMainLen > 0)
{
m_ReceiveMS.Position = currFullMsgLen;
byte[] reMainBuffer = new byte[reMainLen];
m_ReceiveMS.Read(reMainBuffer, 0, reMainLen);
//清空数据流
m_ReceiveMS.Position = 0;
m_ReceiveMS.SetLength(0);
//把剩余字节重新写入数据流
m_ReceiveMS.Write(reMainBuffer, 0, reMainBuffer.Length);
reMainBuffer = null;
}
else
{
//清空数据流
m_ReceiveMS.Position = 0;
m_ReceiveMS.SetLength(0);
break;
}
}
else
{
//还没有收到完整的包
break;
}
System.Threading.Thread.Sleep(1);
}
}
ReceiveNsg();
}
else
{
//客户端断开
Debug.Log(string.Format("服务器{0}断开连接", m_Client.RemoteEndPoint.ToString()));
}
}
catch
{
Debug.Log(string.Format("服务器{0}断开连接", m_Client.RemoteEndPoint.ToString()));
}
}
在Update中将数据包解密后通过消息处理中心分发
int newCrc = Crc16.CalculateCrc16(newBuffer);
//传过来的crc是否=新包的crc
if (newCrc == crc)
{
//异或原始数据
newBuffer = SecurityUtil.Xor(newBuffer);
if (isCompress)
{
newBuffer = ZlibHelper.DeCompressBytes(newBuffer);
}
//协议编号
ushort protoCode = 0;
byte[] protoConent = new byte[buffer.Length - 2];
using (MemoryStreamBuffer ms = new MemoryStreamBuffer(newBuffer))
{
protoCode = ms.ReadUShort();
ms.Read(protoConent, 0, protoConent.Length);
SocketDispatcher.Instance.Dispatch(protoCode, protoConent);
}
}
这边也分享一个数据流的封装代码
public class MemoryStreamBuffer : MemoryStream
{
public MemoryStreamBuffer()
{
}
public MemoryStreamBuffer(byte[] buffer) : base(buffer)
{
}
#region Short
/// <summary>
/// 从流中读取一个short数据
/// </summary>
/// <returns></returns>
public short ReadShort()
{
byte[] arr = new byte[2];
base.Read(arr, 0, 2);
return BitConverter.ToInt16(arr, 0);
}
/// <summary>
/// 把一个short数据写入流
/// </summary>
/// <param name="value"></param>
public void WriteShort(short value)
{
byte[] arr = BitConverter.GetBytes(value);
base.Write(arr, 0, arr.Length);
}
#endregion
#region UShort
/// <summary>
/// 从流中读取一个ushort数据
/// </summary>
/// <returns></returns>
public ushort ReadUShort()
{
byte[] arr = new byte[2];
base.Read(arr, 0, 2);
return BitConverter.ToUInt16(arr, 0);
}
/// <summary>
/// 把一个ushort数据写入流
/// </summary>
/// <param name="value"></param>
public void WriteUShort(ushort value)
{
byte[] arr = BitConverter.GetBytes(value);
base.Write(arr, 0, arr.Length);
}
#endregion
#region Int
/// <summary>
/// 从流中读取一个int数据
/// </summary>
/// <returns></returns>
public int ReadInt()
{
byte[] arr = new byte[4];
base.Read(arr, 0, 4);
return BitConverter.ToInt32(arr, 0);
}
/// <summary>
/// 把一个int数据写入流
/// </summary>
/// <param name="value"></param>
public void WriteInt(int value)
{
byte[] arr = BitConverter.GetBytes(value);
base.Write(arr, 0, arr.Length);
}
#endregion
#region UInt
/// <summary>
/// 从流中读取一个uint数据
/// </summary>
/// <returns></returns>
public uint ReadUInt()
{
byte[] arr = new byte[4];
base.Read(arr, 0, 4);
return BitConverter.ToUInt32(arr, 0);
}
/// <summary>
/// 把一个uint数据写入流
/// </summary>
/// <param name="value"></param>
public void WriteUInt(uint value)
{
byte[] arr = BitConverter.GetBytes(value);
base.Write(arr, 0, arr.Length);
}
#endregion
#region Long
/// <summary>
/// 从流中读取一个long数据
/// </summary>
/// <returns></returns>
public long ReadLong()
{
byte[] arr = new byte[8];
base.Read(arr, 0, 8);
return BitConverter.ToInt64(arr, 0);
}
/// <summary>
/// 把一个long数据写入流
/// </summary>
/// <param name="value"></param>
public void WriteLong(long value)
{
byte[] arr = BitConverter.GetBytes(value);
base.Write(arr, 0, arr.Length);
}
#endregion
#region ULong
/// <summary>
/// 从流中读取一个ulong数据
/// </summary>
/// <returns></returns>
public ulong ReadULong()
{
byte[] arr = new byte[8];
base.Read(arr, 0, 8);
return BitConverter.ToUInt64(arr, 0);
}
/// <summary>
/// 把一个ulong数据写入流
/// </summary>
/// <param name="value"></param>
public void WriteULong(ulong value)
{
byte[] arr = BitConverter.GetBytes(value);
base.Write(arr, 0, arr.Length);
}
#endregion
#region Float
/// <summary>
/// 从流中读取一个float数据
/// </summary>
/// <returns></returns>
public float ReadFloat()
{
byte[] arr = new byte[4];
base.Read(arr, 0, 4);
return BitConverter.ToSingle(arr, 0);
}
/// <summary>
/// 把一个float数据写入流
/// </summary>
/// <param name="value"></param>
public void WriteFloat(float value)
{
byte[] arr = BitConverter.GetBytes(value);
base.Write(arr, 0, arr.Length);
}
#endregion
#region Double
/// <summary>
/// 从流中读取一个double数据
/// </summary>
/// <returns></returns>
public double ReadDouble()
{
byte[] arr = new byte[8];
base.Read(arr, 0, 8);
return BitConverter.ToDouble(arr, 0);
}
/// <summary>
/// 把一个double数据写入流
/// </summary>
/// <param name="value"></param>
public void WriteDouble(double value)
{
byte[] arr = BitConverter.GetBytes(value);
base.Write(arr, 0, arr.Length);
}
#endregion
#region Bool
/// <summary>
/// 从流中读取一个bool数据
/// </summary>
/// <returns></returns>
public bool ReadBool()
{
return base.ReadByte() == 1;
}
/// <summary>
/// 把一个bool数据写入流
/// </summary>
/// <param name="value"></param>
public void WriteBool(bool value)
{
base.WriteByte((byte)(value == true ? 1 : 0));
}
#endregion
#region UTF8String
/// <summary>
/// 从流中读取一个sting数组(读数据前先写数据,写了之后才能去读,写的时候写的长度是知道的,把长度和数组写进去分成两部分)
/// </summary>
/// <returns></returns>
public string ReadUTF8String()
{
//数据的长度
ushort len = this.ReadUShort();
byte[] arr = new byte[len];
base.Read(arr, 0, len);
return Encoding.UTF8.GetString(arr);
}
/// <summary>
/// 把一个string数据写入流
/// </summary>
/// <param name="str"></param>
public void WriteUTF8String(string str)
{
byte[] arr = Encoding.UTF8.GetBytes(str);
if (arr.Length > 65535)
{
throw new InvalidCastException("字符串超出范围");
}
//长度
WriteUShort((ushort)arr.Length);
base.Write(arr, 0, arr.Length);
}
#endregion
#region ByteArray
/// <summary>
/// 从流中读取一个sting数组
/// </summary>
/// <returns></returns>
public byte[] ReadByteArray(out ushort length)
{
length = this.ReadUShort();
byte[] arr = new byte[length];
base.Read(arr, 0, length);
return arr;
}
/// <summary>
/// 把一个string数据写入流
/// </summary>
/// <param name="str"></param>
public void WriteByteArray(byte[] arr, ushort length)
{
if (arr.Length > 65535)
{
throw new InvalidCastException("字符串超出范围");
}
WriteUShort(length);
base.Write(arr, 0, length);
}
#endregion
}
以上Unity客户端关于Socket的使用就OK了,实现双向发送和接收消息。