发送数据

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
/// <summary>
/// 发送数据
/// </summary>
/// <param name="client">发送给的客户端</param>
/// <param name="data">消息</param>
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;//使用Int32
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个字节。

接收数据

修改ServerSocketOnReceiveData方法

我们的协议内容都是由三部分组成的,所以接收数据的时候也是将数据分成三部分解析。

先把数据包开头的长度信息解析出来,有了长度信息,就可以判断此次数据包是不是规定的长度,接收到的长度比信息中记录的小,就可以确定是个分包了,等待下一次传输来完整的数据(配合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
/// <summary>
/// 处理接收的客户端数据
/// </summary>
/// <param name="clientSocket">客户端</param>
void OnReceiveData(ClientSocket clientSocket)
{
ByteArray readBuff = clientSocket.ReadBuff;
//判断消息长度
if (readBuff.Length <= 4 || readBuff.ReadIdx < 0)//小于4连基本的协议头都没有,舍弃
{
return;
}
int readIdx = readBuff.ReadIdx;
byte[]? bytes = readBuff.Bytes;
if (bytes != null)
{
int bodyLength = BitConverter.ToInt32(bytes, readIdx);//这个API只读取指定位置后的4个byte
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;//协议本体的byte长度
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;

}

分发消息

服务器接收来的消息,包含了ClientSocketProtocolEnumMsgBase。我们根据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
{
/// <summary>
/// 分发私钥的请求命令
/// </summary>
/// <param name="client">请求的客户端</param>
/// <param name="msg">请求的msg</param>
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内都是静态方法,方便反射调用。

ServerSocketOnReceiveData中添加分发消息的逻辑

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
/// <summary>
/// 处理接收的客户端数据
/// </summary>
/// <param name="clientSocket">客户端</param>
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。