天天看點

Gameframework(Network初探篇)前言 

前言 

初探篇講解Socket基礎知識,和GF架構如何實作Tcp網絡管道的,後面文章會對接ET架構進行網絡測試。

1.Network常用接口

程式員必須掌握的技能:算法、架構、網絡,我說的...(小聲bb:雖然這三個我也不會),接下來學習架構這麼實作網絡子產品的,首先給出常用的接口表格:

網絡子產品 接口說明
HasNetworkChannel 檢查是否存在網絡頻道
GetNetworkChannel 擷取網絡頻道
GetAllNetworkChannels 擷取所有網絡頻道
CreateNetworkChannel 建立網絡頻道
DestroyNetworkChannel 銷毀網絡頻道
網絡管道 接口說明
Connect 連接配接到遠端主機
Send 發送消息包到遠端主機

其實網絡管道相當于Socket進階封裝,架構主要實作消息接收(通過隊列和消息池),消息包的序列化和反序列化需要開發者繼承INetworkChannelHelper實作,後面文章會講述如何實作對接ET架構的類,這裡簡單講述網絡子產品的架構實作原理。

2.架構實作了什麼?

先從NetworkChannelBase腳本入手,它存在以下這些變量,具體代碼如下:

private const float DefaultHeartBeatInterval = 30;
private readonly string m_Name;
protected readonly Queue<Packet> m_SendPacketPool;
protected readonly EventPool<Packet> m_ReceivePacketPool;
protected readonly INetworkChannelHelper m_NetworkChannelHelper;
protected AddressFamily m_AddressFamily;
protected bool m_ResetHeartBeatElapseSecondsWhenReceivePacket;
protected float m_HeartBeatInterval;
protected Socket m_Socket;
protected readonly SendState m_SendState;
protected readonly ReceiveState m_ReceiveState;
protected readonly HeartBeatState m_HeartBeatState;
protected int m_SentPacketCount;
protected int m_ReceivedPacketCount;
protected bool m_Active;
private bool m_Disposed;
           

詳細代碼自行查閱GameFramework,通過變量分析後,可以得到以下幾點認知:

1、網絡子產品内置心跳包功能,但是具體發送心跳包的函數是使用INetworkChannelHelper發送,也就是使用者自定義發送方式。

2、發送和接受消息都有隊列,唯一不同點是接受消息隊列使用EventPool,這樣可以確定消息接受時觸發對應函數。

3、發送、接受、心跳都封裝到對應State類裡,都内置MemoryStream對象和封裝對應函數。

TcpNetworkChannel繼承它,連結、發送、接受消息都是通過異步函數執行的。接下來給出異步函數表格:

BeginAccept 開始一個異步操作來接受一個傳入的連接配接嘗試
BeginConnect 開始一個對遠端主機連接配接的異步請求
BeginDisconnect 開始異步請求從遠端終結點斷開連接配接
EndAccept  異步接受傳入的連接配接嘗試
EndConnect  結束挂起的異步連接配接請求
EndDisconnect  結束挂起的異步斷開連接配接請求
BeginReceive  開始從連接配接的Socket中異步接收資料
BeginReceiveFrom  開始從指定網絡裝置中異步接收資料
BeginReceiveMessageFrom 開始使用指定的SocketFlags将指定位元組數的資料異步接收到資料緩沖區的指定位置,然後存儲終結點和資料包資訊
BeginSend 将資料異步發送到連接配接的Socket
BeginSendFile 将檔案異步發送到連接配接的Socket對象
BeginSendTo 向特定遠端主機異步發送資料

發送消息入棧發送隊列,出棧通過BeginSend将消息發送出去,發送成功後調用m_SendCallback函數。至于接受隊列如何觸發對應句柄呢,如果了解事件池原理的,就跳過以下内容。需要調用RegisterHandler将回調消息函數注冊到事件池中,在INetworkChannelHelper通過反射将所有繼承IPacketHandler進行注冊,至于繼承IPacketHandler如何進行實作呢?通過心跳包回調句柄分析實作。

namespace GameMain
{
	public partial class TestMessageHandle : IPacketHandler
	{
		public override int Id => OuterOpcode.R2C_Ping;

        public override void Handle(AChannel channel, R2C_Ping message)
		{
			//TODO   發送C2R_Ping
		}
	}
}
           

3.對接ET架構

ET版本5.0講解Tcp消息標頭,首先展示出ET消息包的格式。

Gameframework(Network初探篇)前言 

是以GF架構去實作INetworkChannelHelper也需要對應上格式,具體代碼如下:

public IMessage DeserializeMessage(IMHeader header, MemoryStream source, out object customErrorData)
        {
            customErrorData = null;
            THeader tcpheader = header as THeader;
            if (tcpheader == null)
            {
                Log.Warning("tcp header is invalid.");
                return null;
            }

            IMessage message = null;
            if (tcpheader.IsValid)
            {
                message = m_Responses[header.Id];
                if (message != null)
                {
                    ProtobufHelper.FromStream(message, source);
                }
                else
                {
                    Log.Warning("Can not deserialize message for message id '{0}'.", header.Id.ToString());
                }
            }
            else
            {
                Log.Warning("tcp header is invalid.");
            }
            ReferencePool.Release(tcpheader);
            return message;
        }

        public IMHeader DeserializeMessageHeader(MemoryStream source, out object customErrorData)
        {
            customErrorData = null;
            THeader header = ReferencePool.Acquire<THeader>();
            if (source != null)
            {
                byte[] bytes = source.GetBuffer();
                header.Len = BitConverter.ToInt32(bytes, 0) - 2;
                header.Id = BitConverter.ToUInt16(bytes, NetworkParam.TcpOpcodeIndex);
                return header;
            }

            return null;
        }


        public bool Serialize<T>(T message, MemoryStream destination) where T : IMessage
        {
            memoryStream.Seek(HeaderLen, SeekOrigin.Begin);
            memoryStream.SetLength(HeaderLen);
            ProtobufHelper.ToStream(message, memoryStream);

            THeader header = ReferencePool.Acquire<THeader>();
            header.Id = message.Id;
            header.Len = (int)(memoryStream.Length - NetworkParam.TcpOpcodeIndex);

            memoryStream.Position = 0;
            this.cache.WriteTo(0, header.Len);
            this.cache.WriteTo(4, header.Id);
            Array.Copy(cache, 0, memoryStream.GetBuffer(), 0, cache.Length);
            memoryStream.WriteTo(destination);

            ReferencePool.Release(header);
            return true;
        }
           

序列化消息時長度需要減去4位元組,反序列化消息時需要指定大小,不然GoogleProtobuf無法成功解析。

繼續閱讀