发送数据
在ServerSocket
中添加发送数据的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
|
public static void Send(ClientSocket client, MsgBase msg) { if (client.Socket == null) return; if (client == null || !client.Socket.Connected) return; try { byte[] nameBytes = MsgBase.EncodeName(msg); byte[] bodyBytes = MsgBase.Encode(msg); int len = nameBytes.Length + bodyBytes.Length; byte[] byteHead = BitConverter.GetBytes(len); byte[] sendBytes = new byte[byteHead.Length + len]; Array.Copy(byteHead,0,sendBytes, 0, byteHead.Length); Array.Copy(nameBytes,0,sendBytes,byteHead.Length, nameBytes.Length); Array.Copy(bodyBytes,0,sendBytes,byteHead.Length + nameBytes.Length, bodyBytes.Length); try { client.Socket.BeginSend(sendBytes, 0, sendBytes.Length, SocketFlags.None, null, null); } catch (SocketException se) { Debug.LogError("Socket BeginSend Error: " + se); throw; } } catch (SocketException se) { Debug.LogError("Socket发送数据失败:" + se); return; } }
|
就像上一节说的那样,一段通信的内容分为三个部分,协议内容长度,协议头和协议具体内容。
注意,这里的协议内容长度使用的是Int32,占用4个字节。
接收数据
修改ServerSocket
的OnReceiveData
方法
我们的协议内容都是由三部分组成的,所以接收数据的时候也是将数据分成三部分解析。
先把数据包开头的长度信息解析出来,有了长度信息,就可以判断此次数据包是不是规定的长度,接收到的长度比信息中记录的小,就可以确定是个分包了,等待下一次传输来完整的数据(配合ServerSocket.ReadClient
方法,下一次会自动扩充byte数组)。此时ReadIdx进位4
然后解析协议名,协议名解析出来就能分发处理具体消息了。此时ReadIdx进位nameCount,我们之前MsgBase.DecodeName
方法给出了协议名所占用的长度。
最后解析协议内容,注意我们在分发消息之前,做了readBuff.CheckAndMoveBytes();
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
|
void OnReceiveData(ClientSocket clientSocket) { ByteArray readBuff = clientSocket.ReadBuff; if (readBuff.Length <= 4 || readBuff.ReadIdx < 0) { return; } int readIdx = readBuff.ReadIdx; byte[]? bytes = readBuff.Bytes; if (bytes != null) { int bodyLength = BitConverter.ToInt32(bytes, readIdx); if(readBuff.Length< bodyLength + 4) { return; } readBuff.ReadIdx += 4; int nameCount = 0; ProtocolEnum proto = ProtocolEnum.None; try { proto = MsgBase.DecodeName(bytes,readBuff.ReadIdx, out nameCount); } catch (Exception e) { Debug.LogError("解析协议名出错:" + e); CloseClient(clientSocket); return; } if( proto == ProtocolEnum.None ) { Debug.LogError("OnReceiveData MsgBase.DecodeName fail"); CloseClient(clientSocket); return; } readBuff.ReadIdx += nameCount; int bodyCount = bodyLength - nameCount; MsgBase? msgBase = null; try { msgBase = MsgBase.Decode(proto,bytes,readBuff.ReadIdx, bodyCount); if(msgBase == null) { Debug.LogError("{0}协议内容解析错误", proto.ToString()); CloseClient(clientSocket); return; } } catch ( Exception e) { Debug.LogError("接收协议数据内容解析错误" + e); CloseClient(clientSocket); return; }
readBuff.ReadIdx += bodyCount; readBuff.CheckAndMoveBytes();
if(readBuff.Length > 4 ) { OnReceiveData(clientSocket); } } else return; }
|
分发消息
服务器接收来的消息,包含了ClientSocket
、ProtocolEnum
和MsgBase
。我们根据Protocol写的类型分发消息,并且使用反射。使用反射是为了方便,分发时不用写一长串switch case了。
在Net文件夹中新建MsgHandler
文件,MsgHandler
是用来处理分发消息的总类,这个类里面包含了所有的分发函数,我们把它声明为partial类,这样在各种具体的分发逻辑cs文件里面都能添加上这个类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| namespace SimpleServer.Net { public partial class MsgHandler { public static void MsgSecret(ClientSocket client, MsgBase msg) { if (msg == null) { Debug.LogError(client.Socket?.RemoteEndPoint?.ToString() + "MsgSecret协议接收错误!"); } else { MsgSecret msgSecret = (MsgSecret)msg; msgSecret.Secret = ServerSocket.SecretKey; ServerSocket.Send(client, msgSecret); } } } }
|
注意:MsgHander
的方法名和Protocol内声明的协议名以及类名都要一致
MsgHander
内都是静态方法,方便反射调用。
在ServerSocket
的OnReceiveData
中添加分发消息的逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
|
void OnReceiveData(ClientSocket clientSocket) { if (bytes != null) { MethodInfo? methodInfo = typeof(MsgHandler).GetMethod(proto.ToString()); if(methodInfo != null) { object[] param = {clientSocket, msgBase}; methodInfo.Invoke(null, param); } else { Debug.LogError("OnReceiveData Reflection Fail:" + proto.ToString()); } } else return; }
|
可以看到,这里服务器和客户端使用相同的消息协议,不需要分成Request和Response。