基于大神的范例https://github.com/Maomao110/StarForce
GF做客户端,ET做服务端
通信流程
1、服务端启动,创建TCPService:
//Program.cs 添加外网消息组件
Game.Scene.AddComponent<NetOuterComponent, string>(outerConfig.Address);
//NetworkComponent.cs 创建TCPService
case NetworkProtocol.TCP:
ipEndPoint = NetworkHelper.ToIPEndPoint(address);
this.Service = new TService(packetSize, ipEndPoint, this.OnAccept) { Parent = this };
2、客户端连接
//ProcedureNetSample.cs 连接服务端
INetworkChannel nc = GameEntry.Network.CreateNetworkChannel("TC", netHelper);
nc.HeartBeatInterval = 0f;
nc.Connect(ip, 10002);
3、服务端收到客户端连接请求,创建TcpChannel
//TService.cs OnAcceptComplete
TChannel channel = new TChannel(e.AcceptSocket, this);
4、客户端连接成功回调,发送消息
//ProcedureNetSample.cs
channel.Send(new C2R_Login() { Account = "mdzz", Password = "zzdm" });
//NetworkManager.NetworkChannelBase.cs
//Send<T>(T packet)
m_SendPacketPool.Enqueue(packet);
//ProcessSend() 序列化消息
serializeResult = m_NetworkChannelHelper.Serialize(packet, m_SendState.Stream);
序列化消息的核心代码
public bool Serialize<T>(T packet,Stream destination) where T : Packet
{
...
//消息主体
memoryStream.Seek(PacketHeaderLength, SeekOrigin.Begin);
memoryStream.SetLength(PacketHeaderLength);
//写入消息
ProtobufHelper.ToStream(packet, memoryStream);
// 头部消息
ET_CSPacketHeader packetHeader = ReferencePool.Acquire<ET_CSPacketHeader>();
packetHeader.Flag = 0; //客户端发送的消息,默认flag为0,服务器会解析flag字段值
packetHeader.PacketLength = (int)memoryStream.Length - ETPackets.ET_PacketSizeLength; // 消息内容长度需要减去头部消息长度,只包含packetSize一个字段
packetHeader.Id = (ushort)packet.Id;
//写入头部消息
memoryStream.Position = 0;
this.byteses[0].WriteTo(0, (ushort)packetHeader.PacketLength);
this.byteses[1][0] = packetHeader.Flag;
this.byteses[2].WriteTo(0, packetHeader.Id);
int index = 0;
foreach(var bytes in this.byteses)
{
Array.Copy(bytes, 0, memoryStream.GetBuffer(), index, bytes.Length);
index += bytes.Length;
}
...
}
ProtobufHelper.ToStream(packet, memoryStream);调用对应消息的WriteTo
public void WriteTo(pb::CodedOutputStream output) {
if (Account.Length != 0) {
output.WriteRawTag(10);
output.WriteString(Account);
}
if (Password.Length != 0) {
output.WriteRawTag(18);
output.WriteString(Password);
}
if (RpcId != 0) {
output.WriteRawTag(208, 5);
output.WriteInt32(RpcId);
}
}
消息的全部字节信息如下:
1(第一个字节,下同)、2:消息长度,序列化全部消息后,可得出此字节的值
3:Flag = 0 => 0
4、5:消息ID,对应服务端Opcode,当前消息ID =10001
10001 = 00100111 00010001= 00100111(39) | 00010001(17) => 17,39(即39x256+17=10001)
6:Account FieldNumer = 1,WireType = 2
(field_number << 3) | wire_type = 00001010 => 10
7:Account = "mdzz",长度为4 => 4
8、9、10、11:Account = "mdzz",查询ASCII表,m、d、z、z => 109、100、122、122
12:Password FieldNumer = 2,WireType = 2 => 00010010 => 18
13:Password = "zzdm",长度为4 => 4
14、15、16、17:Password = "zzdm",查询ASCII表,z、z、d、m => 122、122、100、109
RpcId = 0,消息全部获取完毕,消息的字节数为17,不包含表示消息长度的两个字节,所以还要扣除两个字节,因此第1、2字节 => 15,0
根据以上计算得出消息的字节信息为
15,0,17,39,10,4,109,100,122,122,18,4,122,122,100,109
与实际结果相同
5、服务端接收消息
//TChannel.cs
//OnRecvComplete
//解析消息
this.parser.Parse()
...
//session回调 ReadCallback
this.OnRead(this.parser.GetPacket());
Parse()先获取消息长度,再将把除字节长度以外的消息写入,所以只有15个字节
在创建TcpChannel,同时也创建了Seesion,TcpChannel的ReadCallback调用Session.cs的OnRead() 、Run()
//NetworkComponent.cs
public void OnAccept(AChannel channel)
{
Session session = ComponentFactory.CreateWithParent<Session, AChannel>(this, channel);
this.sessions.Add(session.Id, session);
session.Start();
}
//Session.cs
public void Awake(AChannel aChannel)
{
this.channel = aChannel;
this.requestCallback.Clear();
long id = this.Id;
channel.ErrorCallback += (c, e) =>
{
this.Network.Remove(id);
};
channel.ReadCallback += this.OnRead;
}
6、服务端解析消息
//Session.cs
//OnRead() Run()
message = this.Network.MessagePacker.DeserializeFrom(instance, memoryStream);
//ProtobufHelper.cs
((Google.Protobuf.IMessage)message).MergeFrom(stream.GetBuffer(), (int)stream.Position, (int)stream.Length);
最终会调用对应消息的MergeFrom
public void MergeFrom(pb::CodedInputStream input) {
account_ = "";
password_ = "";
rpcId_ = 0;
uint tag;
while ((tag = input.ReadTag()) != 0) {
switch(tag) {
default:
input.SkipLastField();
break;
case 10: {
Account = input.ReadString();
break;
}
case 18: {
Password = input.ReadString();
break;
}
case 720: {
RpcId = input.ReadInt32();
break;
}
}
}
}
7、返回服务端R2C_XXX消息
//Session.cs
if ((flag & 0x01) == 0)
{
this.Network.MessageDispatcher.Dispatch(this, opcode, message);
return;
}
//OuterMessageDispatcher.cs
Game.Scene.GetComponent<MessageDispatcherComponent>().Handle(session, new MessageInfo(opcode, message));
//AMRpcHandler.cs
//Handler()
this.Run(session, request, response =>
{
// 等回调回来,session可以已经断开了,所以需要判断session InstanceId是否一样
if (session.InstanceId != instanceId)
{
return;
}
response.RpcId = rpcId;
session.Reply(response);
});
//C2R_LoginHandler.cs
private async ETVoid RunAsync(Session session, C2R_Login message, Action<R2C_Login> reply)
{
//创建R2C_XXX消息
R2C_Login response = new R2C_Login();
...
response.Address = outerAddress;
response.Key = g2RGetLoginKey.Key;
reply(response);
}
//Session.cs
public void Send(byte flag, ushort opcode, object message)
{
...
//序列化R2C_XXX
MemoryStream stream = this.Stream;
stream.Seek(Packet.MessageIndex, SeekOrigin.Begin);
stream.SetLength(Packet.MessageIndex);
this.Network.MessagePacker.SerializeTo(message, stream);
stream.Seek(0, SeekOrigin.Begin);
this.byteses[0][0] = flag;
this.byteses[1].WriteTo(0, opcode);
int index = 0;
foreach (var bytes in this.byteses)
{
Array.Copy(bytes, 0, stream.GetBuffer(), index, bytes.Length);
index += bytes.Length;
}
...
//发送消息
this.Send(stream);
}
//TChannel.cs
//加上头部的2个字节,表示消息的字节长度
Send()
8、客户端接收R2C_XXX消息
//NetworkManager.TcpNetworkChannel
//ReceiveCallback()
//解析包头、包体
if (m_ReceiveState.PacketHeader != null)
{
processSuccess = ProcessPacket();
}
else
{
processSuccess = ProcessPacketHeader();
}
//NetworkManager.NetworkChannelBase.cs
...
Packet packet = m_NetworkChannelHelper.DeserializePacket(m_ReceiveState.PacketHeader, m_ReceiveState.Stream, out customErrorData);
...
IPacketHeader packetHeader = m_NetworkChannelHelper.DeserializePacketHeader(m_ReceiveState.Stream, out customErrorData);
...
解析包体,调用对应消息的MergeFrom