Unity/DotNetty中集成Lidgren实现可靠UDP

 lidgren有几个优点:

  1. 分channel,每个channel都有单独的消息队列,不互相影响。
  2. 每个消息可以单独选择使用可靠/不可靠传输。
  3. 支持内网穿透
  4. 自带加密算法。

前端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

原文地址:https://www.cnblogs.com/drashnane/p/6415973.html