protobuf转字节流
代码语言:javascript复制[ProtoContract]
public class TestProto
{
[ProtoMember(1)]
public long accountId;
[ProtoMember(2)]
public string password;
}
/// <summary>
/// 序列化pb数据
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="t"></param>
/// <returns></returns>
public static byte[] NSerialize<T>(T t)
{
byte[] buffer = null;
using (MemoryStream m = new MemoryStream())
{
Serializer.Serialize<T>(m, t);
m.Position = 0;
int length = (int)m.Length;
buffer = new byte[length];
m.Read(buffer, 0, length);
}
return buffer;
}
协议包组成
由包头信息,内容字节流流组成,内容直接流即protobuf转字节流 其中,包头
代码语言:javascript复制//包头信息
public class ProtocolHead
{
public int packetLength = 0; //整个包的长度:长度字节4个 modelid字节2个 cmd字节2个 内容长度
public short moduleId = 0; //和cmd组成一条协议的id
public short cmd = 0;
序列化一条协议
先序列化头
代码语言:javascript复制public NetBuffer Serialize(NetBuffer buffer)
{
buffer.WriteInt(packetLength);
buffer.WriteShort(moduleId);
buffer.WriteShort(cmd);
return buffer;
}
其中序列化int ,short,发送的是大端模式
代码语言:javascript复制public int WriteShort(short value, int writePos = -1)
{
int pos = UpdateLenAndGetWritePos(writePos, 2);
m_buff[pos 0] = (byte)(value >> 8 & 0xFF);
m_buff[pos 1] = (byte)(value >> 0 & 0xFF);
return pos 2;
}
public int WriteInt(int value, int writePos = -1)
{
int pos = UpdateLenAndGetWritePos(writePos, 4);
m_buff[pos 0] = (byte)(value >> 24 & 0xFF);
m_buff[pos 1] = (byte)(value >> 16 & 0xFF);
m_buff[pos 2] = (byte)(value >> 08 & 0xFF);
m_buff[pos 3] = (byte)(value >> 00 & 0xFF);
return pos 4;
}
int占4个直接,用m_buff字节数组里4位表示,按照高位在前,低位在后顺序 再把内容字节流copy进入m_buff
代码语言:javascript复制public int WriteBytes(byte[] src, int srcOffset, int count, int writePos = -1)
{
int pos = UpdateLenAndGetWritePos(writePos, count);
Buffer.BlockCopy(src, srcOffset, m_buff, pos, count);
return pos count;
}
网络发送
代码语言:javascript复制byte[] m_sendBuf = new byte[4096];
public void SendMsg(NetMessage netMsg, EnSocket type = EnSocket.Game)
{
//byte[] tmp = null;
int len = netMsg.Serialize(out m_sendBuf);
//byte[] buf1 = new byte[len];
//Array.Copy(tmp, buf1, len);
clientSocket.BeginSend(m_sendBuf, 0, len, SocketFlags.None, new AsyncCallback(_onSendMsg), clientSocket);
}
避免反复new字节流产生GC,使用m_sendBuf缓存
多线程接收
代码语言:javascript复制private void _onConnect_Sucess(IAsyncResult iar)
{
try
{
Socket client = (Socket)iar.AsyncState;
client.EndConnect(iar);
receiveThread = new Thread(new ThreadStart(_onReceiveSocket));
receiveThread.IsBackground = true;
receiveThread.Start();
_isConnected = true;
m_isContecting = false;
拆包黏包处理
主要思想:
- 网络接收到数据,往待处理字节流数组a保存;
- 多了,a会扩容;
- 每次处理完一条完整协议b,a截取掉前面b所有的字节流数据后,尾部的未处理字节流又组成新的_buff即a 多线程频繁调用,可以Thread.Sleep(100);进行定时获取缓冲中网络数据
int receiveLength = clientSocket.Receive(_tmpReceiveBuff); //每次只要有数据来了,就写入到_tmpReceiveBuff中,返回接收到的长度
if (receiveLength > 0)
{
_databuffer.AddBuffer(_tmpReceiveBuff, receiveLength);//将收到的数据添加到缓存器中
while (_databuffer.GetData(out _socketData))//取出一条完整数据
{
sEvent_NetMessageData tmpNetMessageData = new sEvent_NetMessageData();
tmpNetMessageData._eventType = _socketData._protocallType;
tmpNetMessageData._eventData = _socketData._data;
tmpNetMessageData.m_key = _socketData.key;
//锁死消息中心消息队列,并添加数据
lock (MessageCenter.Instance._netMessageDataQueue)
{
//Debug.Log("Get Server:" tmpNetMessageData.m_key);
MessageCenter.Instance._netMessageDataQueue.Enqueue(tmpNetMessageData);
}
}
}
}
每次数据来了,塞到包缓冲器里 这里包缓冲字节流: private byte[] _buff; //待处理字节流:网络接收到数据,往这里塞;多了,会扩容;每次处理完一条完整协议a,截取掉前面a所有的数据后,尾部的未处理直接流又组成新的_buff
代码语言:javascript复制/// <summary>
/// 添加缓存数据
/// </summary>
/// <param name="_data"></param>
/// <param name="_dataLen"></param>
public void AddBuffer(byte[] _data, int _dataLen)
{
if (_dataLen > _buff.Length - _curBuffPosition)//接收的长度,要塞入_buff中,_buff剩余容量不够,扩容
{
byte[] _tmpBuff = new byte[_curBuffPosition _dataLen];
Array.Copy(_buff, 0, _tmpBuff, 0, _curBuffPosition);
Array.Copy(_data, 0, _tmpBuff, _curBuffPosition, _dataLen);
_buff = _tmpBuff; //生成新的扩容后_buff
_tmpBuff = null;
}
else //剩余空间还够,直接塞入
{
Array.Copy(_data, 0, _buff, _curBuffPosition, _dataLen);
}
_curBuffPosition = _dataLen;//修改当前数据标记
}
如果包缓冲器能完整取出一条协议进行处理
代码语言:javascript复制/// <summary>
/// 获取一条可用数据,返回值标记是否有数据
/// </summary>
/// <param name="_tmpSocketData"></param>
/// <returns></returns>
public bool GetData(out sSocketData _tmpSocketData)
{
_tmpSocketData = new sSocketData();
//_buffLength如果没提取过为 0 ,提取一次,取全包长(4 2 2 内容字节流),使用后又重置为 0
if (_buffLength <= 0)
{
UpdateDataLength();
}
if (_buffLength > 0 && _curBuffPosition >= _buffLength)
{
_tmpSocketData._buffLength = _buffLength;
_tmpSocketData._dataLength = _dataLength;
_tmpSocketData._protocallType = (eProtocalCommand)_protocalType;
_tmpSocketData.key = m_key;
_tmpSocketData._data = new byte[_dataLength];
Array.Copy(_buff, Constants.HEAD_LEN, _tmpSocketData._data, 0, _dataLength); //_buff 中从 (4 2 2)开始,复制给内容字节流
_curBuffPosition -= _buffLength; //当前接收到一条网络数据流里还未处理完的字节流 长度 = 总长度(当前长度) - _buffLength(一条完整数据长度)
byte[] _tmpBuff = new byte[_curBuffPosition < _minBuffLen ? _minBuffLen : _curBuffPosition];
Array.Copy(_buff, _buffLength, _tmpBuff, 0, _curBuffPosition);
_buff = _tmpBuff; //重新复制新的待处理字节流
_buffLength = 0;
_dataLength = 0;
_protocalType = 0;
return true;
}
return false;
}
从缓冲字节流里解析出一条完整协议
代码语言:javascript复制/// <summary>
/// 更新数据长度
/// </summary>
public void UpdateDataLength()
{
if (_dataLength == 0 && _curBuffPosition >= Constants.HEAD_LEN)
{
//从0号位提取4位包长字节流
byte[] tmpDataLen = new byte[Constants.HEAD_DATA_LEN];
Array.Copy(_buff, 0, tmpDataLen, 0, Constants.HEAD_DATA_LEN);
//小端接收,要转换下,转换位包长int
_buffLength = BitConverter.ToInt32(NetBuffer.ReverseOrder(tmpDataLen), 0) 4; //得到包长度
//提取moudleID
byte[] tmpProtocalType = new byte[Constants.HEAD_TYPE_LEN];
Array.Copy(_buff, Constants.HEAD_DATA_LEN, tmpProtocalType, 0, Constants.HEAD_TYPE_LEN);
ushort module = BitConverter.ToUInt16(NetBuffer.ReverseOrder(tmpProtocalType), 0);
//提取cmdID
byte[] tmpCmd = new byte[Constants.HEAD_TYPE_LEN];
Array.Copy(_buff, Constants.HEAD_DATA_LEN Constants.HEAD_TYPE_LEN, tmpCmd, 0, Constants.HEAD_TYPE_LEN);
ushort cmd = BitConverter.ToUInt16(NetBuffer.ReverseOrder(tmpCmd), 0);
m_key = module.ToString() "," cmd.ToString();
//内容字节流为全长度 - (4 2 2)
_dataLength = _buffLength - Constants.HEAD_LEN;
}
}
网络协议派发
主要功能: 1.消息放入队列 2.协议底层解析好数据,通过委托,被多个object调取 网络接收到一条完整消息,放入到消息队列中
代码语言:javascript复制//锁死消息中心消息队列,并添加数据
lock (MessageCenter.Instance._netMessageDataQueue)
{
//Debug.Log("Get Server:" tmpNetMessageData.m_key);
MessageCenter.Instance._netMessageDataQueue.Enqueue(tmpNetMessageData);
}
在mono的fixupdate中按照先进先出原则派发消息
代码语言:javascript复制while (_netMessageDataQueue.Count > 0)
{
lock (_netMessageDataQueue)
{
sEvent_NetMessageData tmpNetMessageData = _netMessageDataQueue.Dequeue();
if (tmpNetMessageData.m_key != MsgIdDefine.RspPlayerSync)
{
Debug.Log("Get Server:" tmpNetMessageData.m_key);
}
NetEventMgr.Instance.DispatchEvent(tmpNetMessageData.m_key, tmpNetMessageData._eventData);
}
}
监听者注册消息,同时把该消息id对应的解析类型注册进入,如果多个object注册同个msgID,进行委托合并Delegate.Combine
代码语言:javascript复制class ListenerHelper
{
public Type TMsg = null;
public Delegate onMsg;
}
public void AddListener<TMsg>(string cmd, Action<TMsg> onMsg)
{
if (m_dicMsgListener.ContainsKey(cmd) == false)
{
ListenerHelper helper = new ListenerHelper()
{
TMsg = typeof(TMsg),
onMsg = onMsg
};
m_dicMsgListener.Add(cmd, helper);
}
else
{
m_dicMsgListener[cmd].onMsg = Delegate.Combine(m_dicMsgListener[cmd].onMsg, onMsg);
}
}
派发消息,在底层解析出数据,可以通过打网络log,方便查看具体数据
代码语言:javascript复制public void DispatchEvent(string cmd, byte[] buf)
{
try
{
if (m_dicMsgListener.ContainsKey(cmd))
{
var helper = m_dicMsgListener[cmd];
if (helper != null)
{
if (helper.TMsg != null)
{
object obj = PBSerializer.NDeserialize(buf, helper.TMsg);
if (obj != null)
{
if (DataMgr.m_isNetLog == true)
{
string log = JsonConvert.SerializeObject(obj);
if (cmd != MsgIdDefine.RspPlayerSync /*&& cmd != MsgIdDefine.RspMechanism*/)
{
Debug.Log("NetRecv-->Key:" cmd "-->" log);
}
}
helper.onMsg.DynamicInvoke(obj);
}
}
else
{
if (DataMgr.m_isNetLog == true)
{
if (cmd != MsgIdDefine.RspPlayerSync/* && cmd != MsgIdDefine.RspMechanism*/)
{
Debug.Log("NetRecv-->Key:" cmd);
}
}
helper.onMsg.DynamicInvoke();
}
}
}
}
catch (Exception e)
{
Debug.Log("DispatchEvent:(" cmd ")--" e);
}
}
大端小端模式
C#大端模式和小端模式。 小端(little-endian)模式:低地址上存放低字节,高地址上存放高字节。 如0x11223344→ byte[] numBytes = new byte[]{ 0x44,0x33,0x22,0x11}; numBytes[0] = 0x44; //低地址存放低字节 numBytes[3] = 0x11; //高地址存放高字节 反之,高字节在前,低字节在后,则为大端模式。 反转示例: short num = 12; byte[] bytes = BitConverter.GetBytes(s); Array.Reverse(bytes); //bytes转换为倒序(反转),可实现大端小端的转换 大端模式下int转字节流
代码语言:javascript复制 m_buff[pos 0] = (byte)(value >> 24 & 0xFF);
m_buff[pos 1] = (byte)(value >> 16 & 0xFF);
m_buff[pos 2] = (byte)(value >> 08 & 0xFF);
m_buff[pos 3] = (byte)(value >> 00 & 0xFF);
确定服务器采用的是大小端模式,在客户端收发时进行大端小端处理
字节流压缩
使用GZip
代码语言:javascript复制 public static byte[] Compress(byte[] binary)
{
MemoryStream ms = new MemoryStream();
GZipOutputStream gzip = new GZipOutputStream(ms);
//gzip.SetLevel(-1);
//Debug.Log("gzip.GetLevel()" gzip.GetLevel());
gzip.Write(binary, 0, binary.Length);
gzip.Close();
byte[] press = ms.ToArray();
return press;
}
public static byte[] DeCompress(byte[] press)
{
GZipInputStream gzi = new GZipInputStream(new MemoryStream(press));
MemoryStream re = new MemoryStream();
int count = 0;
int len = press.Length;
byte[] data = new byte[len];
while ((count = gzi.Read(data, 0, data.Length)) != 0)
{
re.Write(data, 0, count);
}
byte[] depress = re.ToArray();
return depress;
}
加密
最简单的是字节流异或加密 异或规则 同为0,异为1; 一个数和另外一个数进行两次异或后,是原数本身。如下例 a -01100001 3 -00000011 01100010 3 -00000011 01100001 或者对关键字段进行非对称加密
全部源码
https://github.com/luoyikun/VirtualCity