csharp/a11s/kcp_csserver/KcpServer/KcpClient/UdpClient.cs

UdpClient.cs
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using Utilities;

namespace KcpClient
{
    /// 
    /// 先弄个简单能用效率低的,调好了服务器再回来优化客户端
    /// 
    public clast UdpClient
    {
        protected ConcurrentQueue Incoming;
        protected ConcurrentQueue Outgoing;
        Socket udp;
        IPEndPoint remote_ipep;
        //IPEndPoint local_ipep;
        ServerPackBuilderEx defpb;
        Thread ioThread;
        byte[] applicationData;
        byte[] heartbeatData = new byte[0];

        bool _connected = false;
        public UdpClient(byte a, byte b, byte c, byte d, int sid, byte[] appData)
        {
            defpb = new ServerPackBuilderEx(a, b, c, d, sid);
            SessionId = sid;
            applicationData = appData;
        }

        public UdpClient(byte[] arr, int sid, byte[] appData)
        {
            defpb = new ServerPackBuilderEx(arr, sid);
            SessionId = sid;
            applicationData = appData;
        }

        public static Action debug = (s) =>
        {
#if DEBUG
            Console.WriteLine(s);
#endif
        };

        public Action OnError = (e) =>
        {
            debug?.Invoke($"{nameof(OnError)}:{e}");
        };
        public Action OnDisconnect = () =>
        {
            debug?.Invoke($"{nameof(OnDisconnect)},server lost");
        };
        public Action OnOperationResponse = (b) =>
        {
            debug?.Invoke("data arrival");
        };


        public Action OnConnected = (sid) =>
        {
            debug?.Invoke($"{nameof(OnConnected)} {sid}");
        };

        public int SessionId { get; private set; }

        public virtual void SendOperationRequest(byte[] buff)
        {
            if (!Connected)
            {
                throw new InvalidOperationException("not connected");
            }
            ProcessOutgoingData(buff, 0, buff.Length);
            
        }



        public void Service()
        {
            if (udp == null) return;
            while (Incoming != null && Incoming.TryDequeue(out var ibuff))
            {
                OnOperationResponse?.Invoke(ibuff);
            }
            AfterService();
        }

        protected virtual void AfterService()
        {
            //for derived Clast
        }

        static HashSet IOThreads = new HashSet();

        #region IOControl
        const uint IOC_VOID = 0x20000000;/* no parameters */
        const uint IOC_OUT = 0x40000000; /* copy out parameters */
        const uint IOC_IN = 0x80000000;/* copy in parameters */
        const uint IOC_UNIX = 0x00000000;
        const uint IOC_WS2 = 0x08000000;
        const uint IOC_PROTOCOL = 0x10000000;
        const uint IOC_VENDOR = 0x18000000;
        uint _WSAIO(uint x, uint y) => (IOC_VOID | x | y);
        uint _WSAIOR(uint x, uint y) => (IOC_OUT | (x) | (y));
        uint _WSAIOW(uint x, uint y) => (IOC_IN | (x) | (y));
        //uint _WSAIORW(uint x, uint y) => (IOC_INOUT | (x) | (y));
        uint SIO_UDP_CONNRESET => _WSAIOW(IOC_VENDOR, 12);

        #endregion
        public bool Connected { get => _connected; private set => _connected = value; }

        public void Connect(string ip, int port, bool CreateBackgroundThread)
        {
            Connect(new IPEndPoint(IPAddress.Parse(ip), port), CreateBackgroundThread);
        }

        public void Connect(IPEndPoint ipep, bool CreateBackgroundThread)
        {
            IOThreads.Clear();
            lastHandshakeTime = DateTime.MinValue;
            Incoming = new ConcurrentQueue();
            Outgoing = new ConcurrentQueue();
            udp = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            if (Environment.OSVersion.Platform == PlatformID.Win32NT)
            {
                bool bNewBehavior = false;
                byte[] dwBytesReturned = new byte[4];
                udp.IOControl((int)SIO_UDP_CONNRESET, BitConverter.GetBytes(bNewBehavior), dwBytesReturned);
            }
            defpb = new ServerPackBuilderEx(defpb.GetSysIdBuf(), SessionId);
            remote_ipep = ipep;
            udp.Connect(remote_ipep);
            if (CreateBackgroundThread)
            {
                ioThread = new Thread(ioLoop);
                ioThread.IsBackground = true;
                ioThread.Name = $"{nameof(ioThread)}";
                IOThreads.Add(ioThread.ManagedThreadId);
                ioThread.Start();
            }
            debug?.Invoke($"start connect");
        }

        public virtual void Close()
        {
            IOThreads.Clear();
            Incoming = null;
            Outgoing = null;
            defpb = null;
            udp.Close();
        }

        DateTime lastHandshakeTime = DateTime.Now;
        protected void Handshake()
        {
            if (DateTime.Now.Subtract(lastHandshakeTime).TotalSeconds < 1)
            {
                return;
            }
            byte[] hsbuff = defpb.GetSysIdBuf();
            byte[] appdata = applicationData;
            var tspb = new ServerPackBuilderEx(hsbuff, 0);
            byte[] sendbuff = new byte[PackSettings.HEADER_LEN + appdata.Length];
            tspb.Write(sendbuff, appdata, 0, appdata.Length);
            udp.SendTo(sendbuff, remote_ipep);
            lastHandshakeTime = DateTime.Now;
            debug?.Invoke($"begin {nameof(Handshake)}");
            _connected = true;
        }

        DateTime lastHartbeatTime = DateTime.Now;
        protected void Heartbeat()
        {
            if (!Connected)
            {
                return;
            }
            if (DateTime.Now.Subtract(lastHartbeatTime).TotalSeconds < 1)
            {
                return;
            }

            byte[] sendbuff = new byte[PackSettings.HEADER_LEN + heartbeatData.Length];
            defpb.Write(sendbuff, heartbeatData, 0, heartbeatData.Length);
            udp.SendTo(sendbuff, remote_ipep);
            lastHartbeatTime = DateTime.Now;
        }

        EndPoint remoteIpep = new IPEndPoint(0, 0);
        void ioLoop()
        {
            var tid = Thread.CurrentThread.ManagedThreadId;
            debug?.Invoke($"Thread {tid} start");
            SpinWait sw = new SpinWait();
            while (IOThreads.Contains(tid))
            {
                DoWork();
                sw.SpinOnce();
            }
            debug?.Invoke($"Thread {tid} exit");
        }

        public void DoWork()
        {
            while (Incoming != null && udp.Available > 0)
            {
                byte[] buff = new byte[udp.Available];
                var cnt = udp.ReceiveFrom(buff, ref remoteIpep);
                var datasize = defpb.Read(buff, out var data, out int sid, out var sysbuff);
                if (datasize > 0)
                {
                    //data arrival
                    if (sid == 0)
                    {
                        //握手协议
                        var tmp = BitConverter.ToInt32(data, 0);
                        if (tmp > 0)
                        {
                            this.SessionId = tmp;
                            defpb = new ServerPackBuilderEx(defpb.GetSysIdBuf(), this.SessionId);
                            debug?.Invoke($"{nameof(Handshake)}:{nameof(SessionId)}={SessionId}");
                            OnHandShake();

                        }
                        else
                        {
                            //error code                                
                            var errcode = BitConverter.ToInt32(data, 0);
                            InternalError(errcode);

                        }
                    }
                    else
                    {
                        ProcessIncomingData(data, 0, data.Length);

                    }
                }
                else if (datasize == 0)
                {
                    //heartbeat from server, reserved
                }
                else
                {
                    //error
                    InternalError(datasize);
                }
            }
            while (Outgoing != null && Outgoing.TryDequeue(out var sbuff))
            {
                var sndbuf = new byte[PackSettings.HEADER_LEN + sbuff.Length];
                defpb.Write(sndbuf, sbuff, 0, sbuff.Length);
                udp.SendTo(sndbuf, remote_ipep);
#if PRINTPACK
                    Console.WriteLine($"realsend:{sndbuf.Length}");
#endif
            }
            if (this.SessionId == 0)
            {
                Handshake();
            }
            else
            {
                Heartbeat();
            }
        }

        protected virtual void OnHandShake()
        {
            OnConnected?.Invoke(this.SessionId);

        }

        protected virtual void ProcessIncomingData(byte[] data, int start, int len)
        {
            if (start == 0)
            {
                Incoming.Enqueue(data);
            }
            else
            {
                var buf = new byte[len];
                Array.Copy(data, start, buf, 0, len);
                Incoming.Enqueue(buf);
            }
        }
        protected virtual void ProcessOutgoingData(byte[] data, int start, int len)
        {
            if (start == 0)
            {
                Outgoing.Enqueue(data);
            }
            else
            {
                var buf = new byte[len];
                Array.Copy(data, start, buf, 0, len);
                Outgoing.Enqueue(buf);
            }
        }

        private void InternalError(int datasize)
        {
            Connected = false;
            var cec = (ClientErrorCode)datasize;
            switch (cec)
            {

                case ClientErrorCode.SERVER_TIMEOUT:
                    Console.WriteLine("server think you timeout");
                    Close();
                    OnDisconnect();
                    break;
                default:
                    OnError.Invoke(cec);
                    break;
            }
        }
    }
}