Unity/DotNetty中集成Lidgren实现可靠UDP
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