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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM