lidgren有幾個優點:
- 分channel,每個channel都有單獨的消息隊列,不互相影響。
- 每個消息可以單獨選擇使用可靠/不可靠傳輸。
- 支持內網穿透
- 自帶加密算法。
前端Unity:
先貼一張前端使用的網絡框架圖:
Lidgren的Github地址:https://github.com/lidgren/lidgren-network-gen3
在Player Setting中,要加上宏定義UNITY
連接:
NetPeerConfiguration config = new NetPeerConfiguration (NetworkConfig.CONNECTION_IDENTIFIER);//參數是一個字符串標識,前后端一致。 config.EnableMessageType (NetIncomingMessageType.ConnectionLatencyUpdated);//監聽收發心跳的事件。 m_connection = new NetClient (config); m_connection.Start (); m_receiveCallBack = new SendOrPostCallback (OnReceiveMessage);//收發消息的回調 m_connection.RegisterReceivedCallback(m_receiveCallBack, new SynchronizationContext()); m_connection.Connect (ip, port);
斷開連接:
m_connection.Disconnect (""); m_connection.UnregisterReceivedCallback (m_receiveCallBack);
發送消息:
NetOutgoingMessage nm = connection.CreateMessage (bytesLength); nm.Write (bytes, 0, bytesLength); m_connection.SendMessage (nm, (NetDeliveryMethod)channelType, channel);
NetDeliveryMethod有以下幾類:
UnReliable | 不可靠傳輸,順序和丟包都不能保證 |
UnReliableSequence | 不可靠傳輸,按順序接收, 舊包直接丟棄 |
ReliableUnOrdered | 可靠傳輸,不保證順序,但是不會丟包 |
ReliableSequence | 可靠傳輸,和UnReliableSequence,但是有一個緩沖窗口,超過緩沖范圍的包會丟棄 |
ReliableOrdered | 可靠傳輸,不丟包,保證順序 |
接收消息:
void OnReceiveMessage(object state) { NetIncomingMessage im; while ((im = m_connection.ReadMessage()) != null) { switch (im.MessageType) { case NetIncomingMessageType.ErrorMessage: case NetIncomingMessageType.WarningMessage: //處理錯誤和警告 break; case NetIncomingMessageType.DebugMessage: //debug輸出 break; case NetIncomingMessageType.VerboseDebugMessage: //消息重發或丟棄的debug消息 break; case NetIncomingMessageType.StatusChanged: //網絡狀態變化 break; case NetIncomingMessageType.Data: //收到消息處理,ReceiveMessages (im.ReadBytes (im.LengthBytes), im.LengthBytes); break; case NetIncomingMessageType.ConnectionLatencyUpdated: //Lidgren收發心跳包后回調 break; default: break; } connection.Recycle(im); } }
后端DotNetty:
后端是參照TCPServerSocketChannel和TCPSocketChannel改的。
Server的UnSafe可以寫一個空類:
class LidgrenUdpServerUnsafeChannel : AbstractUnsafe { public LidgrenUdpServerUnsafeChannel(AbstractChannel channel) : base(channel) { } public override Task ConnectAsync(EndPoint remoteAddress, EndPoint localAddress) { return null; } public void FinishRead() { } }
再實現一個ServerChannel:
public class LidgrenUdpServerChannel : AbstractChannel, IServerChannel { public const string CONNECTION_IDENTIFIER = "xxxxx"; NetServer m_server; LidgrenChannelConfig m_config; public NetServer Server { get { return m_server; } } Dictionary<NetConnection, LidgrenUdpChannel> m_connectionList = new Dictionary<NetConnection, LidgrenUdpChannel>(); public LidgrenUdpServerChannel() : base(null) { m_config = new LidgrenChannelConfig(CONNECTION_IDENTIFIER); m_server = new NetServer(m_config.Config); } protected override IChannelUnsafe NewUnsafe() { return new LidgrenUdpServerUnsafeChannel(this); } ... }
開始監聽:
protected override void DoBind(EndPoint localAddress) { m_config.Config.Port = ((IPEndPoint)localAddress).Port; this.m_server.Start(); this.m_server.RegisterReceivedCallback(new System.Threading.SendOrPostCallback(OnRead), new System.Threading.SynchronizationContext()); }
OnRead類似客戶端的OnReceiveMessage:
建立/斷開連接:
case NetIncomingMessageType.StatusChanged: NetConnectionStatus ns = (NetConnectionStatus)im.ReadByte(); if (ns == NetConnectionStatus.Connected) { LidgrenUdpChannel udpChannel = new LidgrenUdpChannel(this, im.SenderConnection); Pipeline.FireChannelRead(udpChannel); lock (((System.Collections.ICollection)m_connectionList).SyncRoot) { m_connectionList.Add(im.SenderConnection, udpChannel); } } if (ns == NetConnectionStatus.Disconnected) { lock (((System.Collections.ICollection)m_connectionList).SyncRoot) { LidgrenUdpChannel channel = null; if (m_connectionList.TryGetValue(im.SenderConnection, out channel)) { channel.Pipeline.FireChannelInactive(); m_connectionList.Remove(im.SenderConnection); } } } break;
接收消息:
case NetIncomingMessageType.Data: LidgrenUdpChannel readChannel = null; lock (((System.Collections.ICollection)m_connectionList).SyncRoot) { if (m_connectionList.TryGetValue(im.SenderConnection, out readChannel)) { readChannel.ReadBytes(im.ReadBytes(im.LengthBytes)); } } break;
對於每一個客戶端,都有一個單獨的channel:
public class LidgrenUdpChannel : AbstractChannel
發送消息:
protected int DoWriteBytes(IByteBuffer buf) { if (!buf.HasArray) { throw new NotImplementedException("Only IByteBuffer implementations backed by array are supported."); } int sent = buf.ReadableBytes; NetOutgoingMessage msg = m_parentChannel.Server.CreateMessage(); msg.Write(buf.Array, buf.ArrayOffset + buf.ReaderIndex, buf.ReadableBytes); m_connection.SendMessage(msg, NetDeliveryMethod.ReliableOrdered, 0); if (sent > 0) { buf.SetReaderIndex(buf.ReaderIndex + sent); } return sent; } protected override void DoWrite(ChannelOutboundBuffer input) { while (true) { object msg = input.Current; if (msg == null) { // Wrote all messages. break; } if (msg is IByteBuffer) { IByteBuffer buf = (IByteBuffer)msg; int readableBytes = buf.ReadableBytes; if (readableBytes == 0) { input.Remove(); continue; } bool done = false; long flushedAmount = 0; int localFlushedAmount = this.DoWriteBytes(buf); flushedAmount += localFlushedAmount; if (!buf.IsReadable()) { done = true; } input.Progress(flushedAmount); buf.Release(); if (done) { input.Remove(); } else { throw new InvalidOperationException(); } } else { // Should not reach here. throw new InvalidOperationException(); } } }
接收消息:
public void ReadBytes(byte[] bytes) { if (!Open) return; this.EventLoop.Execute(()=> { ((LidgrenUdpUnsafe)Unsafe).ReadBytes(bytes); }); }
UnSafe中:
public void FinishRead() { channel.Read(); } public void ReadBytes(byte[] bytes) { IByteBufferAllocator allocator = channel.Allocator; IRecvByteBufAllocatorHandle allocHandle = RecvBufAllocHandle; IByteBuffer byteBuf = allocHandle.Allocate(allocator); byteBuf.WriteBytes(bytes); channel.Pipeline.FireChannelRead(byteBuf); channel.Pipeline.FireChannelReadComplete(); allocHandle.ReadComplete(); }
Lidgren不支持ipv6,修改方法參照這里https://github.com/SteveProXNA/UnityLidgrenIPv6/tree/master/IPv6