socket

HPSocket

    public class TcpBaseMethod
    {
        ///// <summary>
        ///// 线程池
        ///// </summary>
        //private static readonly HPSocket.Thread.ThreadPool ThreadPool = new HPSocket.Thread.ThreadPool();

        public event Action<string> ReciveEvent;

        private ITcpServer<string> server;

        private ConcurrentDictionary<IntPtr, IntPtr> connList = new ConcurrentDictionary<IntPtr, IntPtr>();

        public TcpBaseMethod(string ip, ushort port)
        {
            // TcpServer-TestEcho-Adapter 适配 TcpClient-TestEcho 和 TcpAgent-TestEcho, 可以使用这两个客户端连接到当前服务端

            IPGlobalProperties ipProperties = IPGlobalProperties.GetIPGlobalProperties();
            IPEndPoint[] ipEndPoints = ipProperties.GetActiveTcpListeners();
            foreach (var item in ipEndPoints)
            {
                if (item.Port == port)
                {
                    //端口已有监听,不需要再次打开                
                    return;
                }
            }

            server = new TcpServer<string>
            {
                Address = ip,
                Port = port,
                // 指定数据接收适配器
                DataReceiveAdapter = new PacketDataReceiveAdapter(),
            };
            {
                // 准备监听事件
                server.OnPrepareListen += Server_OnPrepareListen;

                // 连接进入
                server.OnAccept += Server_OnAccept;

                // 不需要在绑定OnReceive事件
                // 这里解析请求体事件的数据,就是PacketDataReceiveAdapter.ParseRequestBody()返回的数据
                server.OnParseRequestBody += Server_OnParseRequestBody;

                // 连接关闭
                server.OnClose += Server_OnClose;

                // 启动服务
                server.Start();

                //// 等待线程池任务全部结束
                //ThreadPool.Wait();

                //// 等待服务结束
                //server.Wait();

                //timer.Stop();
                //timer.Close();
            }
        }

        private HandleResult Server_OnClose(IServer sender, IntPtr connId, SocketOperation socketOperation, int errorCode)
        {
            Console.WriteLine($"OnClose({connId}) -> operation: {socketOperation}, code: {errorCode}");
            IntPtr ptr;
            connList.TryRemove(connId, out ptr);
            return HandleResult.Ok;
        }

        private HandleResult Server_OnParseRequestBody(ITcpServer sender, IntPtr connId, string obj)
        {
            return ProcessPacket(sender, connId, obj);
        }

        private HandleResult Server_OnPrepareListen(IServer sender, IntPtr listen)
        {
            Console.WriteLine($"OnPrepareListen({listen})");
            return HandleResult.Ok;
        }

        private HandleResult Server_OnAccept(IServer sender, IntPtr connId, IntPtr client)
        {
            Console.WriteLine($"OnAccept({connId}) -> socket handle: {client}");
            //add by hyq 
            if (connList.Count(c => c.Key == connId) == 0)
                connList.TryAdd(connId, client);
            return HandleResult.Ok;
        }

        //private HandleResult Server_OnAccept(IServer sender, IntPtr connId, IntPtr client)
        //{
        //    Console.WriteLine($"OnAccept({connId}) -> socket handle: {client}");
        //    //add by hyq 
        //    if (connList.Count(c => c.Value == client) == 0)
        //        connList.TryAdd(connId, client);
        //    return HandleResult.Ok;
        //}

        private static readonly object objLock = new object();
        /// <summary>
        /// packet对象转含包头的完整bytes
        /// </summary>
        /// <param name="packet"></param>
        /// <returns></returns>
        public bool ToSendBytes(object packet) 
        {

            // 对象转json
            var json = JsonConvert.SerializeObject(packet);

            ////记录日志(测试用)
            //string filepath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"logs\\socketLog_{DateTime.Now.ToString("yyyyMMdd")}.log");
            ////using (System.IO.FileStream fs = new System.IO.FileStream(filepath, System.IO.FileMode.OpenOrCreate))
            ////{
            //    using (System.IO.StreamWriter br = new System.IO.StreamWriter(filepath,true))
            //    {

            //        br.WriteLine(string.Format("[{0}{1}]", DateTime.Now.ToString("HH:mm:ss"), json));
            //        br.Flush();
            //    }

            ////}
           
            Console.WriteLine(string.Format("[{0}]send:{1}", DateTime.Now, json));

            // json到bytes
            var data = Encoding.UTF8.GetBytes(json);

            // 包头转字节数组 int32为4字节,最大2147483647
            if (data.Length > 2147483647)
            {
                // 过滤超过2147483647的数据,包头会大于4字节
                return false;
            }

            var header = BitConverter.GetBytes(data.Length);
            // 两端字节序要保持一致
            // 如果当前环境不是小端字节序
            if (!BitConverter.IsLittleEndian)
            {
                // 翻转字节数组, 变为小端字节序
                Array.Reverse(header);
            }

            //var bytesList = new List<byte>();
            //// 加入包头
            //bytesList.AddRange(header);
            //// 加入真实数据
            //bytesList.AddRange(data);

            //// 包头+数据
            var bytes = header.Concat(data).ToArray();
            //string tempSendFile = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "socketSendTemp.dat");
            //System.IO.File.WriteAllBytes(tempSendFile, bytes);

            ////var bytes = bytesList.ToArray();


            lock (objLock)
            {
                //向所有发送
                foreach (var obj in connList)
                {
                    // 断开连接的跳过
                    if (!server.IsConnected(obj.Key)){
                        continue;
                    }

                    //6K一段的发送
                    int size = 6144;
                    int nums = ((bytes.Length - 1) / size) + 1;

                    for (var n = 0; n < nums; n++)
                    {
                        int start = n * size;
                        var buffer = bytes.Skip(start).Take(size).ToArray();

                        if (!server.Send(obj.Key, buffer, buffer.Length))
                        {
                            return false;
                        }
                    }

                    //if(!server.Send(obj.Key, bytes, bytes.Length)) {
                    //    return false;
                    //}
                }
            }

            //using (System.IO.FileStream fs = new System.IO.FileStream(@"C:\job\bin.bin", System.IO.FileMode.CreateNew))
            //{               
            //    using (System.IO.BinaryWriter br = new System.IO.BinaryWriter(fs))
            //    {

            //        br.Write(bytes);
            //        br.Flush();
            //    }
            //}

            return true;

        }

        /// <summary>
        /// 处理接收到的数据包
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="id"></param>
        /// <param name="packet"></param>
        /// <returns></returns>
        private HandleResult ProcessPacket(IServer sender, IntPtr id, string packet)
        {

            try
            {
                //记录日志(测试用)
                string filepath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"logs\\socketLog_{DateTime.Now.ToString("yyyyMMdd")}.log");
                //using (System.IO.FileStream fs = new System.IO.FileStream(filepath, System.IO.FileMode.OpenOrCreate))
                //{
                using (System.IO.StreamWriter br = new System.IO.StreamWriter(filepath, true))
                {

                    br.WriteLine(string.Format("[{0}{1}]", DateTime.Now.ToString("HH:mm:ss"), packet));
                    br.Flush();
                }

            }
            catch { }

            var result = HandleResult.Ok;
            //Console.WriteLine(packet);
            ReciveEvent(packet);
            //switch (packet.Type)
            //{
            //    case PacketType.Echo: // 假如回显是一个非耗时操作, 在这处理
            //        {
            //            // 回显数据
            //            var data = PacketHelper.ToSendBytes(packet);
            //            result = sender.Send(id, data, data.Length) ? HandleResult.Ok : HandleResult.Error;
            //            break;
            //        }
            //    case PacketType.Time: // 假如获取服务器时间是耗时操作, 将该操作放入队列
            //        {
            //            // 向线程池提交任务
            //            if (!ThreadPool.Submit(ThreadProc, new { sender, id, packet })) // 线程池传参, 这里使用的是匿名对象
            //            {
            //                result = HandleResult.Error;
            //            }

            //            break;
            //        }
            //    default:
            //        result = HandleResult.Error;
            //        break;
            //}
            return result;
        }

        /// <summary>
        /// 是否启动
        /// </summary>
        /// <returns></returns>
        public bool IsOpen()
        {
            return server != null && server.HasStarted ? true : false;
        }

        /// <summary>
        /// 关闭连接
        /// </summary>
        /// <returns></returns>
        public void Close()
        {
            // 先关闭事件,避免资源无法释放
            server.OnPrepareListen -= Server_OnPrepareListen;
            server.OnAccept -= Server_OnAccept;
            server.OnParseRequestBody -= Server_OnParseRequestBody;
            server.OnClose -= Server_OnClose;
            server.Stop();
            server.Dispose();
        }
    }
View Code

 

    /// <summary>
    /// 固定包头数据接收适配器
    /// </summary>
    public class PacketDataReceiveAdapter : FixedHeaderDataReceiveAdapter<string>
    {
        /// <summary>
        /// 调用父类构造函数,指定固定包头的长度及最大封包长度
        /// </summary>
        public PacketDataReceiveAdapter() : base(
                headerSize: 4,        // 这里指定4字节包头
                maxPacketSize: 0 // 这里指定最大封包长度不能超过4K
                )
        {

        }

        /// <summary>
        /// 获取请求体长度
        /// <remarks>子类必须覆盖此方法</remarks>
        /// </summary>
        /// <param name="header">包头,header的长度是构造函数里指定的长度,当接收到了指定长度的包头再调用此方法</param>
        /// <returns>返回包体长度</returns>
        protected override int GetBodySize(byte[] header)
        {
            // 根据业务场景来适配字节序, 两端字节序要保持一致

            // 如果当前环境不是小端字节序
            if (!BitConverter.IsLittleEndian)
            {
                // 转换为小端字节序
                Array.Reverse(header);
            }

            // 因为包头是4字节,所以直接转int并返回
            return BitConverter.ToInt32(header, 0);
        }

        /// <summary>
        /// 解析请求体
        /// <remarks>子类必须覆盖此方法</remarks>
        /// </summary>
        /// <param name="header">包头</param>
        /// <param name="data">包体</param>
        /// <returns></returns>
        protected override string ParseRequestBody(byte[] header, byte[] data)
        {
            // 将data反序列化为对象
            // 这里是Packet类的对象
            return Encoding.UTF8.GetString(data);//JsonConvert.DeserializeObject<Packet>(Encoding.UTF8.GetString(data));
        }
    }
PacketDataReceiveAdapter

服务端处理业务:

        TcpDevices.TcpHelper.TcpBaseMethod tcpServer;


                tcpServer = new TcpDevices.TcpHelper.TcpBaseMethod(dyIp, (ushort)dyPort);
                tcpServer.ReciveEvent += TcpServer_ReciveEvent;

                statusTimer = new System.Timers.Timer();
                statusTimer.Elapsed += StatusTimer_Elapsed;
                statusTimer.Interval = 3 * 1000;//3S
                statusTimer.AutoReset = true;

//处理业务


        private void TcpServer_ReciveEvent(string obj)
        {
            Console.WriteLine(string.Format("[{0}]rev:{1}", DateTime.Now, Newtonsoft.Json.JsonConvert.SerializeObject(obj)));

            var res = Newtonsoft.Json.JsonConvert.DeserializeObject<CMDResultModel>(obj);

            if (res.cmd == FaceAntiSpoofingCMD.FA_WebCamera_OneToOne)
            {
                cmdQueue.TryAdd(FaceAntiSpoofingCMD.FA_WebCamera_OneToOne + "_" + res.data.TaskID, res);
            }
            else if (res.cmd == FaceAntiSpoofingCMD.FA_WebCamera_GetStatus) { _IsReady = res.code == 0; lastHeartbeatTime = DateTime.Now; }
        }
View Code

客户端类似:TcpClient

===============================================

以下位另外的例子:TcpClient可以配合TcpServer,TcpServer-AsyncQueue,TcpServer-Adapter 3种使用

TcpServer-Adapter

    /// <summary>
    /// 固定包头长度数据适配器示例
    /// </summary>
    class Program
    {
        /// <summary>
        /// 线程池
        /// </summary>
        private static readonly HPSocket.Thread.ThreadPool ThreadPool = new HPSocket.Thread.ThreadPool();

        static void Main(string[] args)
        {
            Console.Title = "TcpServer-TestEcho-Adapter";


            // TcpServer-TestEcho-Adapter 适配 TcpClient-TestEcho 和 TcpAgent-TestEcho, 可以使用这两个客户端连接到当前服务端

            using (ITcpServer<Packet> server = new TcpServer<Packet>
            {
                Address = "0.0.0.0",
                Port = 5555,
                // 指定数据接收适配器
                DataReceiveAdapter = new PacketDataReceiveAdapter(),
            })
            {
                // 定时器
                var timer = new Timer(5000)
                {
                    AutoReset = true,
                };

                // 定时输出线程池任务数
                timer.Elapsed += (sender, eventArgs) =>
                {
                    if (ThreadPool.HasStarted)
                    {
                        Console.WriteLine($"线程池当前在执行的任务数: {ThreadPool.TaskCount}, 任务队列数: {ThreadPool.QueueSize}");
                    }
                };
                timer.Start();

                // 准备监听事件
                server.OnPrepareListen += (sender, listen) =>
                {
                    Console.WriteLine($"OnPrepareListen({listen})");
                    return HandleResult.Ok;
                };

                // 连接进入
                server.OnAccept += (sender, id, client) =>
                {
                    Console.WriteLine($"OnAccept({id}) -> socket handle: {client}");

                    return HandleResult.Ok;
                };

                // 不需要在绑定OnReceive事件
                // 这里解析请求体事件的packet就是PacketDataReceiveAdapter.ParseRequestBody()返回的数据
                server.OnParseRequestBody += (sender, id, packet) =>
                {
                    Console.WriteLine($"OnParseRequestBody({id}) -> type: {packet.Type}, data: {packet.Data}");

                    // 处理业务逻辑
                    return ProcessPacket(sender, id, packet);
                };

                // 连接关闭
                server.OnClose += (sender, id, operation, code) =>
                {
                    Console.WriteLine($"OnClose({id}) -> operation: {operation}, code: {code}");

                    return HandleResult.Ok;
                };

                // 启动线程池
                ThreadPool.Start(2, HPSocket.Thread.RejectedPolicy.WaitFor);

                // 启动服务
                server.Start();

                // 等待线程池任务全部结束
                ThreadPool.Wait();

                // 等待服务结束
                server.Wait();

                timer.Stop();
                timer.Close();
            }
        }

        private static HandleResult ProcessPacket(IServer sender, IntPtr id, Packet packet)
        {
            var result = HandleResult.Ignore;
            switch (packet.Type)
            {
                case PacketType.Echo: // 假如回显是一个非耗时操作, 在这处理
                {
                    // 回显数据
                    var data = PacketHelper.ToSendBytes(packet);
                    result = sender.Send(id, data, data.Length) ? HandleResult.Ok : HandleResult.Error;
                    break;
                }
                case PacketType.Time: // 假如获取服务器时间是耗时操作, 将该操作放入队列
                {
                    // 向线程池提交任务
                    if (!ThreadPool.Submit(ThreadProc, new { sender, id, packet })) // 线程池传参, 这里使用的是匿名对象
                    {
                        result = HandleResult.Error;
                    }

                    break;
                }
                default:
                    result = HandleResult.Error;
                    break;
            }
            return result;
        }

        /// <summary>
        /// 线程池回调函数
        /// </summary>
        /// <param name="obj"></param>
        static void ThreadProc(object obj)
        {
            // 解构动态对象
            dynamic dyObj = obj;
            IServer sender = dyObj.sender;
            IntPtr id = dyObj.id;
            Packet packet = dyObj.packet;

            // 如果连接已经断开了(可能被踢了)
            // 它的任务就不做了(根据自己业务需求来, 也许你的任务就是要完成每个连接的所有任务, 每个包都要处理, 不管连接断开与否, 就不要写这个判断, 但是你回发包的时候要判断是否有效连接)
            if (!sender.IsConnected(id))
            {
                return;
            }

            if (packet.Type != PacketType.Time)
            {
                return;
            }

            // 模拟耗时操作
            System.Threading.Thread.Sleep(6000);

            // 组织回包
            packet = new Packet
            {
                Type = PacketType.Time,
                Data = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
            };

            // 发送数据
            var data = PacketHelper.ToSendBytes(packet);
            if (!sender.Send(id, data, data.Length))
            {
                // 发送失败断开连接
                sender.Disconnect(id);
            }
        }
    }
TcpServer-Adapter

 

    /// <summary>
    /// 固定包头数据接收适配器
    /// </summary>
    public class PacketDataReceiveAdapter : FixedHeaderDataReceiveAdapter<Packet>
    {
        /// <summary>
        /// 构造函数调用父类构造, 指定固定包头的长度及最大封包长度
        /// </summary>
        public PacketDataReceiveAdapter()
            : base(
                headerSize: 4,// 这里指定4字节包头
                maxPacketSize: 0x1000 // 这里指定最大封包长度不能超过4K
                )
        {

        }

        /// <summary>
        /// 获取请求体长度方法
        /// <remarks>子类必须覆盖此方法</remarks>
        /// </summary>
        /// <param name="header">包头, header的长度是构造函数里指定的长度, 构造函数里指定包头长度多少, 父类就等到了指定长度在调用此方法</param>
        /// <returns>返回真实长度</returns>
        protected override int GetBodySize(byte[] header)
        {
            // 适配大小端请根据自己的业务场景来, 两端字节序要保持一致

            // 如果当前环境不是小端字节序
            if (!BitConverter.IsLittleEndian)
            {
                // 翻转字节数组, 变为小端字节序
                Array.Reverse(header);
            }

            // 因为我是4字节包头, 所以直接转int并返回
            return BitConverter.ToInt32(header, 0);
        }

        /// <summary>
        /// 解析请求体
        /// <remarks>子类必须覆盖此方法</remarks>
        /// </summary>
        /// <param name="header">包头</param>
        /// <param name="data">包体</param>
        /// <returns></returns>
        protected override Packet ParseRequestBody(byte[] header, byte[] data)
        {
            // 将data转换为泛型传入的类型对象
            // 我这里是Packet类的对象
            return JsonConvert.DeserializeObject<Packet>(Encoding.UTF8.GetString(data));
        }
    }
PacketDataReceiveAdapter

 

model

    /// <summary>
    /// 封包
    /// </summary>
    public class Packet
    {
        /// <summary>
        /// 封包类型
        /// </summary>
        public PacketType Type { get; set; }

        /// <summary>
        /// 数据
        /// </summary>
        public string Data { get; set; }
    }
Packet

======

TcpServer-AsyncQueue

    public partial class FormServer : Form
    {
        delegate void AddLogHandler(string log);

#pragma warning disable IDE0069 // 应释放可释放的字段

        private readonly ITcpServer _server = new TcpServer();

        /// <summary>
        /// 异步消费者队列
        /// </summary>
        private AsyncQueue<TaskInfo> _asyncQueue;

        /// <summary>
        /// 定时器
        /// </summary>
        private readonly Timer _timer = new Timer(5000)
        {
            AutoReset = true,
        };

#pragma warning restore IDE0069 // 应释放可释放的字段

        /// <summary>
        /// 最大封包长度
        /// </summary>
        private const int MaxPacketSize = 4096;

        public FormServer()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            // 演示设置server属性

            // 缓冲区大小应根据实际业务设置, 并发数和包大小都是考虑条件
            // 都是小包的情况下4K合适, 刚好是一个内存分页(在非托管内存, 相关知识查百度)
            // 大包比较多的情况下, 应根据并发数设置比较大但是又不会爆内存的值
            _server.SocketBufferSize = 4096; // 4K

            // server绑定地址和端口
            _server.Address = "0.0.0.0";
            _server.Port = 5555;

            // 演示绑定事件
            _server.OnPrepareListen += OnPrepareListen;
            _server.OnAccept += OnAccept;
            _server.OnReceive += OnReceive;
            _server.OnClose += OnClose;
            _server.OnShutdown += OnShutdown;

            _asyncQueue = new AsyncQueue<TaskInfo>(2, TaskTaskProc);

            // 定时输出线程池任务数
            _timer.Elapsed += (_, args) =>
            {
                if (_server.HasStarted)
                {
                    AddLog($"异步消费者队列的消费者数: {_asyncQueue.ConsumerCount}, 未执行任务队列数: {_asyncQueue.Count}");
                }
            };

            _timer.Start();
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (_server.HasStarted)
            {
                MessageBox.Show(@"请先停止服务", @"服务正在运行:", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                e.Cancel = true;
                return;
            }

            // 停止消费并释放资源
            _asyncQueue.Dispose();


            // 停止并释放定时器
            _timer.Stop();
            _timer.Close();

            // 停止释放服务器
            _server.Dispose();
            e.Cancel = false;
        }

        // ReSharper disable once InconsistentNaming
        private HandleResult OnPrepareListen(IServer sender, IntPtr listen)
        {
            AddLog($"OnPrepareListen({sender.Address}:{sender.Port}), listen: {listen}, hp-socket version: {sender.Version}");

            return HandleResult.Ok;
        }

        // ReSharper disable once InconsistentNaming
        private HandleResult OnAccept(IServer sender, IntPtr connId, IntPtr client)
        {
            // 获取客户端地址
            if (!sender.GetRemoteAddress(connId, out var ip, out var port))
            {
                return HandleResult.Error;
            }

            AddLog($"OnAccept({connId}), ip: {ip}, port: {port}");

            // 设置附加数据, 用来做粘包处理
            sender.SetExtra(connId, new ClientInfo
            {
                ConnId = connId,
                PacketData = new List<byte>(),
            });

            return HandleResult.Ok;
        }

        // ReSharper disable once InconsistentNaming
        private HandleResult OnReceive(IServer sender, IntPtr connId, byte[] data)
        {
            AddLog($"OnReceive({connId}), data length: {data.Length}");

            var client = sender.GetExtra<ClientInfo>(connId);
            if (client == null)
            {
                return HandleResult.Error;
            }

            client.PacketData.AddRange(data);

            // 总长度小于包头
            if (client.PacketData.Count < sizeof(int))
            {
                return HandleResult.Ok;
            }

            HandleResult result;
            const int headerLength = sizeof(int);
            do
            {
                // 取头部字节得到包头
                var packetHeader = client.PacketData.GetRange(0, headerLength).ToArray();

                // 两端字节序要保持一致
                // 如果当前环境不是小端字节序
                if (!BitConverter.IsLittleEndian)
                {
                    // 翻转字节数组, 变为小端字节序
                    Array.Reverse(packetHeader);
                }

                // 得到包头指向的数据长度
                var dataLength = BitConverter.ToInt32(packetHeader, 0);

                // 完整的包长度(含包头和完整数据的大小)
                var fullLength = dataLength + headerLength;

                if (dataLength < 0 || fullLength > MaxPacketSize)
                {
                    result = HandleResult.Error;
                    break;
                }

                // 如果来的数据小于一个完整的包
                if (client.PacketData.Count < fullLength)
                {
                    // 下次数据到达处理
                    result = HandleResult.Ignore;
                    break;
                }

                // 是不是一个完整的包(包长 = 实际数据长度 + 包头长度)
                if (client.PacketData.Count == fullLength)
                {
                    // 得到完整包并处理
                    var fullData = client.PacketData.GetRange(headerLength, dataLength).ToArray();
                    result = OnProcessFullPacket(sender, client, fullData);

                    // 清空缓存
                    client.PacketData.Clear();
                    break;
                }

                // 如果来的数据比一个完整的包长
                if (client.PacketData.Count > fullLength)
                {
                    // 先得到完整包并处理
                    var fullData = client.PacketData.GetRange(headerLength, dataLength).ToArray();
                    result = OnProcessFullPacket(sender, client, fullData);
                    if (result == HandleResult.Error)
                    {
                        break;
                    }
                    // 再移除已经读了的数据
                    client.PacketData.RemoveRange(0, fullLength);

                    // 剩余的数据下个循环处理
                }

            } while (true);


            return result;
        }

        // ReSharper disable once InconsistentNaming
        private HandleResult OnClose(IServer sender, IntPtr connId, SocketOperation socketOperation, int errorCode)
        {
            AddLog($"OnClose({connId}), socket operation: {socketOperation}, error code: {errorCode}");

            var client = sender.GetExtra<ClientInfo>(connId);
            if (client != null)
            {
                sender.RemoveExtra(connId);
                client.PacketData.Clear();
                return HandleResult.Error;
            }

            return HandleResult.Ok;
        }

        // ReSharper disable once InconsistentNaming
        private HandleResult OnShutdown(IServer sender)
        {
            AddLog($"OnShutdown({sender.Address}:{sender.Port})");

            return HandleResult.Ok;
        }

        // ReSharper disable once InconsistentNaming
        private HandleResult OnProcessFullPacket(IServer sender, ClientInfo client, byte[] data)
        {
            // 这里来的都是完整的包, 但是这里不做耗时操作, 仅把数据放入队列
            var packet = JsonConvert.DeserializeObject<Packet>(Encoding.UTF8.GetString(data));
            var result = HandleResult.Ok;
            switch (packet.Type)
            {
                case PacketType.Echo: // 假如回显是一个非耗时操作, 在这处理
                {
                    // 组织packet为一个json
                    var json = JsonConvert.SerializeObject(new Packet
                    {
                        Type = packet.Type,
                        Data = packet.Data,
                    });
                        //
                        AddLog($"{DateTime.Now}:{packet}....");

                    // json转字节数组
                    var bytes = Encoding.UTF8.GetBytes(json);

                    // 先发包头
                    if (!SendPacketHeader(sender, client.ConnId, bytes.Length))
                    {
                        result = HandleResult.Error;
                        break;
                    }

                    // 再发实际数据
                    if (!sender.Send(client.ConnId, bytes, bytes.Length))
                    {
                        result = HandleResult.Error;
                    }

                    // 至此完成回显
                    break;
                }
                case PacketType.Time: // 假如获取服务器时间是耗时操作, 将该操作放入队列
                {
                    // 向异步队列添加数据
                    if (!_asyncQueue.Enqueue(new TaskInfo
                    {
                        Client = client,
                        Packet = packet,
                    }))
                    {
                        result = HandleResult.Error;
                    }

                    break;
                }
                default:
                    result = HandleResult.Error;
                    break;
            }
            return result;
        }

        /// <summary>
        /// 线程池任务回调函数
        /// </summary>
        /// <param name="taskInfo">任务参数</param>
        private void TaskTaskProc(TaskInfo taskInfo)
        {
            // 如果连接已经断开了(可能被踢了)
            // 它的任务就不做了(根据自己业务需求来, 也许你的任务就是要完成每个连接的所有任务, 每个包都要处理, 不管连接断开与否, 就不要写这个判断, 但是你回发包的时候要判断是否有效连接)
            if (!_server.IsConnected(taskInfo.Client.ConnId))
            {
                return;
            }

            // 在这里处理耗时任务逻辑

            switch (taskInfo.Packet.Type)
            {
                case PacketType.Time:
                {
                    // 模拟耗时操作
                    Thread.Sleep(6000);

                    // 组织packet为一个json
                    var json = JsonConvert.SerializeObject(new Packet
                    {
                        Type = PacketType.Time,
                        Data = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
                    });

                    // json转字节数组
                    var bytes = Encoding.UTF8.GetBytes(json);

                    // 先发包头
                    if (!SendPacketHeader(_server, taskInfo.Client.ConnId, bytes.Length))
                    {
                        // 发送失败断开连接
                        _server.Disconnect(taskInfo.Client.ConnId);
                        break;
                    }

                    // 再发实际数据
                    if (!_server.Send(taskInfo.Client.ConnId, bytes, bytes.Length))
                    {
                        _server.Disconnect(taskInfo.Client.ConnId);
                    }

                    // 至此完成当前任务
                    break;
                }
            }
        }

        /// <summary>
        /// 发送包头
        /// <para>固定包头网络字节序</para>
        /// </summary>
        /// <param name="sender">服务器组件</param>
        /// <param name="connId">连接id</param>
        /// <param name="dataLength">实际数据长度</param>
        /// <returns></returns>
        private bool SendPacketHeader(IServer sender, IntPtr connId, int dataLength)
        {
            // 包头转字节数组
            var packetHeaderBytes = BitConverter.GetBytes(dataLength);

            // 两端字节序要保持一致
            // 如果当前环境不是小端字节序
            if (!BitConverter.IsLittleEndian)
            {
                // 翻转字节数组, 变为小端字节序
                Array.Reverse(packetHeaderBytes);
            }

            return sender.Send(connId, packetHeaderBytes, packetHeaderBytes.Length);
        }

        private async void BtnSwitchService_Click(object sender, EventArgs e)
        {
            try
            {
                if (btnSwitchService.Text == @"启动")
                {
                    // 启动服务
                    if (!_server.Start())
                    {
                        throw new Exception($"error code: {_server.ErrorCode}, error message: {_server.ErrorMessage}");
                    }

                    btnSwitchService.Text = @"停止";

                    // 等待服务停止
                    await _server.WaitAsync();

                    // 停止以后还原按钮标题
                    btnSwitchService.Text = @"启动";

                    btnSwitchService.Enabled = true;
                }
                else
                {
                    btnSwitchService.Enabled = false;

                    // 等待服务停止
                    await _server.StopAsync();
                }
            }
            catch (Exception ex)
            {
                AddLog($"exception: {ex.Message}");
            }
        }

        private void BtnClear_Click(object sender, EventArgs e)
        {
            txtLog.Text = "";
        }

        private void AddLog(string log)
        {
            if (txtLog.IsDisposed)
            {
                return;
            }

            // 从ui线程去操作ui
            if (txtLog.InvokeRequired)
            {
                txtLog.Invoke(new AddLogHandler(AddLog), log);
            }
            else
            {
                txtLog.AppendText($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] {log}\r\n");
            }
        }

    }
TcpServer-AsyncQueue

==========================

TcpServer

    public partial class FormServer : Form
    {
        delegate void AddLogHandler(string log);

#pragma warning disable IDE0069 // 应释放可释放的字段
        private readonly ITcpServer _server = new TcpServer();

        /// <summary>
        /// 线程池
        /// </summary>
        private readonly ThreadPool _threadPool = new ThreadPool();

        /// <summary>
        /// 定时器
        /// </summary>
        private readonly Timer _timer = new Timer(5000)
        {
            AutoReset = true,
        };

#pragma warning restore IDE0069 // 应释放可释放的字段

        /// <summary>
        /// 线程池回调函数
        /// </summary>
        private TaskProcEx _taskTaskProc;

        /// <summary>
        /// 最大封包长度
        /// </summary>
        private const int MaxPacketSize = 4096;

        public FormServer()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            // 演示设置server属性

            // 缓冲区大小应根据实际业务设置, 并发数和包大小都是考虑条件
            // 都是小包的情况下4K合适, 刚好是一个内存分页(在非托管内存, 相关知识查百度)
            // 大包比较多的情况下, 应根据并发数设置比较大但是又不会爆内存的值
            _server.SocketBufferSize = 4096; // 4K

            // server绑定地址和端口
            _server.Address = "0.0.0.0";
            _server.Port = 5555;

            // 演示绑定事件
            _server.OnPrepareListen += OnPrepareListen;
            _server.OnAccept += OnAccept;
            _server.OnReceive += OnReceive;
            _server.OnClose += OnClose;
            _server.OnShutdown += OnShutdown;


            // 线程池相关设置
            _threadPool.OnStartup += ThreadPoolOnStartup; // 线程池启动事件
            _threadPool.OnShutdown += ThreadPoolOnShutdown; // 线程池退出事件
            _threadPool.OnWorkerThreadStart += ThreadPoolOnWorkerThreadStart; // 工作线程启动事件
            _threadPool.OnWorkerThreadEnd += ThreadPoolOnWorkerThreadEnd; // 工作线程关闭事件

            // 线程池回调函数
            _taskTaskProc = TaskTaskProc;


            // 定时输出线程池任务数
            _timer.Elapsed += (_, args) =>
            {
                if (_server.HasStarted && _threadPool.HasStarted)
                {
                    AddLog($"线程池当前在执行的任务数: {_threadPool.TaskCount}, 任务队列数: {_threadPool.QueueSize}");
                }
            };
            _timer.Start();
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (_server.HasStarted)
            {
                MessageBox.Show(@"请先停止服务", @"服务正在运行:", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                e.Cancel = true;
                return;
            }

            // 停止并释放线程池, 会等待所有任务结束
            _threadPool.Dispose();

            // 停止并释放定时器
            _timer.Stop();
            _timer.Close();

            // 停止释放服务器
            _server.Dispose();
            e.Cancel = false;
        }

        private void ThreadPoolOnStartup(ThreadPool threadPool)
        {
            AddLog("ThreadPoolOnStartup()");
        }

        private void ThreadPoolOnShutdown(ThreadPool threadPool)
        {
            AddLog("ThreadPoolOnShutdown()");
        }

        private void ThreadPoolOnWorkerThreadStart(ThreadPool threadPool, ulong threadId)
        {
            // 如果要在当前事件中操作ui, 请另开Task, 如下所示
            var id = threadId;
            Task.Run(() =>
            {
                AddLog($"ThreadPoolOnWorkerThreadStart({id})");
            });
        }

        private void ThreadPoolOnWorkerThreadEnd(ThreadPool threadPool, ulong threadId)
        {
            AddLog($"ThreadPoolOnWorkerThreadEnd({threadId},{Thread.CurrentThread.ManagedThreadId})");
        }

        // ReSharper disable once InconsistentNaming
        private HandleResult OnPrepareListen(IServer sender, IntPtr listen)
        {
            AddLog($"OnPrepareListen({sender.Address}:{sender.Port}), listen: {listen}, hp-socket version: {sender.Version}");

            return HandleResult.Ok;
        }

        // ReSharper disable once InconsistentNaming
        private HandleResult OnAccept(IServer sender, IntPtr connId, IntPtr client)
        {
            // 获取客户端地址
            if (!sender.GetRemoteAddress(connId, out var ip, out var port))
            {
                return HandleResult.Error;
            }

            AddLog($"OnAccept({connId}), ip: {ip}, port: {port}");

            // 设置附加数据, 用来做粘包处理
            sender.SetExtra(connId, new ClientInfo
            {
                ConnId = connId,
                PacketData = new List<byte>(),
            });

            return HandleResult.Ok;
        }

        // ReSharper disable once InconsistentNaming
        private HandleResult OnReceive(IServer sender, IntPtr connId, byte[] data)
        {
            AddLog($"OnReceive({connId}), data length: {data.Length}");

            var client = sender.GetExtra<ClientInfo>(connId);
            if (client == null)
            {
                return HandleResult.Error;
            }

            client.PacketData.AddRange(data);

            // 总长度小于包头
            if (client.PacketData.Count < sizeof(int))
            {
                return HandleResult.Ok;
            }

            HandleResult result;
            const int headerLength = sizeof(int);
            do
            {
                // 取头部字节得到包头
                var packetHeader = client.PacketData.GetRange(0, headerLength).ToArray();

                // 两端字节序要保持一致
                // 如果当前环境不是小端字节序
                if (!BitConverter.IsLittleEndian)
                {
                    // 翻转字节数组, 变为小端字节序
                    Array.Reverse(packetHeader);
                }

                // 得到包头指向的数据长度
                var dataLength = BitConverter.ToInt32(packetHeader, 0);

                // 完整的包长度(含包头和完整数据的大小)
                var fullLength = dataLength + headerLength;

                if (dataLength < 0 || fullLength > MaxPacketSize)
                {
                    result = HandleResult.Error;
                    break;
                }

                // 如果来的数据小于一个完整的包
                if (client.PacketData.Count < fullLength)
                {
                    // 下次数据到达处理
                    result = HandleResult.Ignore;
                    break;
                }

                // 是不是一个完整的包(包长 = 实际数据长度 + 包头长度)
                if (client.PacketData.Count == fullLength)
                {
                    // 得到完整包并处理
                    var fullData = client.PacketData.GetRange(headerLength, dataLength).ToArray();
                    result = OnProcessFullPacket(sender, client, fullData);

                    // 清空缓存
                    client.PacketData.Clear();
                    break;
                }

                // 如果来的数据比一个完整的包长
                if (client.PacketData.Count > fullLength)
                {
                    // 先得到完整包并处理
                    var fullData = client.PacketData.GetRange(headerLength, dataLength).ToArray();
                    result = OnProcessFullPacket(sender, client, fullData);
                    if (result == HandleResult.Error)
                    {
                        break;
                    }
                    // 再移除已经读了的数据
                    client.PacketData.RemoveRange(0, fullLength);

                    // 剩余的数据下个循环处理
                }

            } while (true);


            return result;
        }

        // ReSharper disable once InconsistentNaming
        private HandleResult OnClose(IServer sender, IntPtr connId, SocketOperation socketOperation, int errorCode)
        {
            AddLog($"OnClose({connId}), socket operation: {socketOperation}, error code: {errorCode}");

            var client = sender.GetExtra<ClientInfo>(connId);
            if (client != null)
            {
                sender.RemoveExtra(connId);
                client.PacketData.Clear();
                return HandleResult.Error;
            }

            return HandleResult.Ok;
        }

        // ReSharper disable once InconsistentNaming
        private HandleResult OnShutdown(IServer sender)
        {
            AddLog($"OnShutdown({sender.Address}:{sender.Port})");

            return HandleResult.Ok;
        }

        // ReSharper disable once InconsistentNaming
        private HandleResult OnProcessFullPacket(IServer sender, ClientInfo client, byte[] data)
        {
            // 这里来的都是完整的包, 但是这里不做耗时操作, 仅把数据放入队列
            var packet = JsonConvert.DeserializeObject<Packet>(Encoding.UTF8.GetString(data));
            var result = HandleResult.Ok;
            switch (packet.Type)
            {
                case PacketType.Echo: // 假如回显是一个非耗时操作, 在这处理
                {
                    // 组织packet为一个json
                    var json = JsonConvert.SerializeObject(new Packet
                    {
                        Type = packet.Type,
                        Data = packet.Data,
                    });

                    // json转字节数组
                    var bytes = Encoding.UTF8.GetBytes(json);

                    // 先发包头
                    if (!SendPacketHeader(sender, client.ConnId, bytes.Length))
                    {
                        result = HandleResult.Error;
                        break;
                    }

                    // 再发实际数据
                    if (!sender.Send(client.ConnId, bytes, bytes.Length))
                    {
                        result = HandleResult.Error;
                    }

                    // 至此完成回显
                    break;
                }
                case PacketType.Time: // 假如获取服务器时间是耗时操作, 将该操作放入队列
                {
                    // 向线程池提交任务
                    if (!_threadPool.Submit(_taskTaskProc, new TaskInfo
                    {
                        Client = client,
                        Packet = packet,
                    }))
                    {
                        result = HandleResult.Error;
                    }

                    break;
                }
                default:
                    result = HandleResult.Error;
                    break;
            }
            return result;
        }

        /// <summary>
        /// 线程池任务回调函数
        /// </summary>
        /// <param name="obj">任务参数</param>
        private void TaskTaskProc(object obj)
        {
            AddLog($"TaskTaskProc() 线程id: {Thread.CurrentThread.ManagedThreadId}");
            if (!(obj is TaskInfo taskInfo))
            {
                return;
            }

            // 如果连接已经断开了(可能被踢了)
            // 它的任务就不做了(根据自己业务需求来, 也许你的任务就是要完成每个连接的所有任务, 每个包都要处理, 不管连接断开与否, 就不要写这个判断, 但是你回发包的时候要判断是否有效连接)
            if (!_server.IsConnected(taskInfo.Client.ConnId))
            {
                return;
            }

            // 在这里处理耗时任务逻辑

            switch (taskInfo.Packet.Type)
            {
                case PacketType.Time:
                {
                    // 模拟耗时操作
                    Thread.Sleep(6000);

                    // 组织packet为一个json
                    var json = JsonConvert.SerializeObject(new Packet
                    {
                        Type = PacketType.Time,
                        Data = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
                    });

                    // json转字节数组
                    var bytes = Encoding.UTF8.GetBytes(json);

                    // 先发包头
                    if (!SendPacketHeader(_server, taskInfo.Client.ConnId, bytes.Length))
                    {
                        // 发送失败断开连接
                        _server.Disconnect(taskInfo.Client.ConnId);
                        break;
                    }

                    // 再发实际数据
                    if (!_server.Send(taskInfo.Client.ConnId, bytes, bytes.Length))
                    {
                        _server.Disconnect(taskInfo.Client.ConnId);
                    }

                    // 至此完成当前任务
                    break;
                }
            }
        }

        /// <summary>
        /// 发送包头
        /// <para>固定包头网络字节序</para>
        /// </summary>
        /// <param name="sender">服务器组件</param>
        /// <param name="connId">连接id</param>
        /// <param name="dataLength">实际数据长度</param>
        /// <returns></returns>
        private bool SendPacketHeader(IServer sender, IntPtr connId, int dataLength)
        {
            // 包头转字节数组
            var packetHeaderBytes = BitConverter.GetBytes(dataLength);

            // 两端字节序要保持一致
            // 如果当前环境不是小端字节序
            if (!BitConverter.IsLittleEndian)
            {
                // 翻转字节数组, 变为小端字节序
                Array.Reverse(packetHeaderBytes);
            }

            return sender.Send(connId, packetHeaderBytes, packetHeaderBytes.Length);
        }

        private async void BtnSwitchService_Click(object sender, EventArgs e)
        {
            try
            {
                if (btnSwitchService.Text == @"启动")
                {
                    // 2个线程处理耗时操作, 作为相对耗时的任务, 可根据业务需求多开线程处理
                    if (!_threadPool.Start(2, RejectedPolicy.WaitFor))
                    {
                        btnSwitchService.Enabled = false;
                        throw new Exception($"线程池启动失败, 错误码: {_threadPool.SysErrorCode}");
                    }

                    // 启动服务
                    if (!_server.Start())
                    {
                        throw new Exception($"error code: {_server.ErrorCode}, error message: {_server.ErrorMessage}");
                    }

                    btnSwitchService.Text = @"停止";

                    // 等待线程池停止
                    await _threadPool.WaitAsync();

                    // 等待服务停止
                    await _server.WaitAsync();

                    // 停止以后还原按钮标题
                    btnSwitchService.Text = @"启动";

                    btnSwitchService.Enabled = true;
                }
                else
                {
                    btnSwitchService.Enabled = false;
                    // 停止并等待线程池任务全部完成
                    await _threadPool.StopAsync();

                    // 等待服务停止
                    await _server.StopAsync();
                }
            }
            catch (Exception ex)
            {
                AddLog($"exception: {ex.Message}");
            }
        }

        private void BtnClear_Click(object sender, EventArgs e)
        {
            txtLog.Text = "";
        }

        private void AddLog(string log)
        {
            if (txtLog.IsDisposed)
            {
                return;
            }

            // 从ui线程去操作ui
            if (txtLog.InvokeRequired)
            {
                txtLog.Invoke(new AddLogHandler(AddLog), log);
            }
            else
            {
                txtLog.AppendText($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] {log}\r\n");
            }
        }

    }
TcpServer

TcpClient

    public partial class FormClient : Form
    {
        delegate void AddLogHandler(string log);

#pragma warning disable IDE0069 // 应释放可释放的字段
        private readonly ITcpClient _client = new TcpClient();
#pragma warning restore IDE0069 // 应释放可释放的字段

        /// <summary>
        /// 封包, 做粘包用
        /// </summary>
        private readonly List<byte> _packetData = new List<byte>();

        /// <summary>
        /// 最大封包长度
        /// </summary>
        private const int MaxPacketSize = 4096;

        public FormClient()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            // 演示设置client属性

            // 缓冲区大小
            _client.SocketBufferSize = 4096; // 4K
            // 异步连接
            _client.Async = true;

            // 要连接的服务器地址和端口(也可以调用Connect()方法时传入服务器ip和端口)
            // 例如: _client.Connect("127.0.0.1", 555)
            _client.Address = "127.0.0.1";
            _client.Port = 5555;

            // 事件绑定
            _client.OnPrepareConnect += OnPrepareConnect;
            _client.OnConnect += OnConnect;
            _client.OnReceive += OnReceive;
            _client.OnClose += OnClose;
        }

        private void FormClient_FormClosing(object sender, FormClosingEventArgs e)
        {

            if (_client.HasStarted)
            {
                MessageBox.Show(@"请先断开与服务器的连接", @"正在通信:", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                e.Cancel = true;
                return;
            }

            // 停止并释放客户端
            _client.Dispose();

            e.Cancel = false;
        }

        // ReSharper disable once InconsistentNaming
        private HandleResult OnPrepareConnect(IClient sender, IntPtr socket)
        {
            AddLog($"OnPrepareConnect({sender.Address}:{sender.Port}), socket handle: {socket}, hp-socket version: {sender.Version}");

            return HandleResult.Ok;
        }

        // ReSharper disable once InconsistentNaming
        private HandleResult OnConnect(IClient sender)
        {
            AddLog("OnConnect()");
            _packetData.Clear();
            return HandleResult.Ok;
        }

        // ReSharper disable once InconsistentNaming
        private HandleResult OnReceive(IClient sender, byte[] data)
        {
            AddLog($"OnReceive(), data length: {data.Length}");

            _packetData.AddRange(data);

            // 总长度小于包头
            if (_packetData.Count < sizeof(int))
            {
                return HandleResult.Ok;
            }

            HandleResult result;
            const int headerLength = sizeof(int);
            do
            {
                // 取头部字节得到包头
                var packetHeader = _packetData.GetRange(0, headerLength).ToArray();

                // 两端字节序要保持一致
                // 如果当前环境不是小端字节序
                if (!BitConverter.IsLittleEndian)
                {
                    // 翻转字节数组, 变为小端字节序
                    Array.Reverse(packetHeader);
                }

                // 得到包头指向的数据长度
                var dataLength = BitConverter.ToInt32(packetHeader, 0);

                // 完整的包长度(含包头和完整数据的大小)
                var fullLength = dataLength + headerLength;

                if (dataLength < 0 || fullLength > MaxPacketSize)
                {
                    result = HandleResult.Error;
                    break;
                }

                // 如果来的数据小于一个完整的包
                if (_packetData.Count < fullLength)
                {
                    // 下次数据到达处理
                    result = HandleResult.Ignore;
                    break;
                }

                // 是不是一个完整的包(包长 = 实际数据长度 + 包头长度)
                if (_packetData.Count == fullLength)
                {
                    // 得到完整包并处理
                    var fullData = _packetData.GetRange(headerLength, dataLength).ToArray();
                    result = OnProcessFullPacket(fullData);
                    // 清空缓存
                    _packetData.Clear();
                    break;
                }

                // 如果来的数据比一个完整的包长
                if (_packetData.Count > fullLength)
                {
                    // 先得到完整包并处理
                    var fullData = _packetData.GetRange(headerLength, dataLength).ToArray();
                    result = OnProcessFullPacket(fullData);
                    if (result == HandleResult.Error)
                    {
                        break;
                    }
                    // 再移除已经读了的数据
                    _packetData.RemoveRange(0, fullLength);

                    // 剩余的数据下个循环处理
                }

            } while (true);

            
            return result;
        }

        // ReSharper disable once InconsistentNaming
        private HandleResult OnClose(IClient sender, SocketOperation socketOperation, int errorCode)
        {
            _packetData.Clear();
            AddLog($"OnClose(), socket operation: {socketOperation}, error code: {errorCode}");
            return HandleResult.Ok;
        }

        // ReSharper disable once InconsistentNaming
        private HandleResult OnProcessFullPacket(byte[] data)
        {
            // 这里来的都是完整的包
            var packet = JsonConvert.DeserializeObject<Packet>(Encoding.UTF8.GetString(data));
            var result = HandleResult.Ok;
            switch (packet.Type)
            {
                case PacketType.Echo: // 回显是个字符串显示操作
                {
                    AddLog($"OnProcessFullPacket(), type: {packet.Type}, content: {packet.Data}");
                    break;
                }
                case PacketType.Time: // 获取服务器时间依然是个字符串操作^_^
                {
                    AddLog($"OnProcessFullPacket(), type: {packet.Type}, time: {packet.Data}");
                    break;
                }
                default:
                    result = HandleResult.Error;
                    break;
            }
            return result;
        }

        /// <summary>
        /// 发送包头
        /// <para>固定包头网络字节序</para>
        /// </summary>
        /// <param name="dataLength">实际数据长度</param>
        /// <returns></returns>
        private bool SendPacketHeader(int dataLength)
        {
            // 包头转字节数组
            var packetHeaderBytes = BitConverter.GetBytes(dataLength);

            // 两端字节序要保持一致
            // 如果当前环境不是小端字节序
            if (!BitConverter.IsLittleEndian)
            {
                // 翻转字节数组, 变为小端字节序
                Array.Reverse(packetHeaderBytes);
            }

            return _client.Send(packetHeaderBytes, packetHeaderBytes.Length);
        }


        /// <summary>
        /// 发送数据
        /// </summary>
        /// <param name="type"></param>
        /// <param name="data"></param>
        private void Send(PacketType type, string data)
        {

            if (!_client.HasStarted)
            {
                return;
            }

            // 组织封包, 取得要发送的数据
            var packet = new Packet
            {
                Type = type,
                Data = data,
            };

            var bytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(packet));

            // 发送包头, 发送失败断开连接
            if (!SendPacketHeader(bytes.Length))
            {
                _client.Stop();
                return;
            }

            // 发送实际数据, 发送失败断开连接
            if (!_client.Send(bytes, bytes.Length))
            {
                _client.Stop();
            }
        }

        private async void BtnConnectSwitch_Click(object sender, EventArgs e)
        {
            try
            {
                if (btnConnectSwitch.Text == @"连接")
                {
                    // 连接到目标服务器
                    if (!_client.Connect())
                    {
                        throw new Exception($"error code: {_client.ErrorCode}, error message: {_client.ErrorMessage}");
                    }

                    btnConnectSwitch.Text = @"断开";

                    // 等待服务停止
                    await _client.WaitAsync();

                    // 停止以后还原按钮标题
                    btnConnectSwitch.Text = @"连接";
                }
                else
                {
                    // 断开连接
                    await _client.StopAsync();
                }
            }
            catch (Exception ex)
            {
                AddLog($"exception: {ex.Message}");
            }
        }

        private void BtnSend_Click(object sender, EventArgs e)
        {
            Send(PacketType.Echo, txtContent.Text.Trim());
        }

        private void BtnTest_Click(object sender, EventArgs e)
        {
            var txt = txtContent.Text.Trim();
            Send(PacketType.Echo, txt);
            Send(PacketType.Echo, txt + new Random(Guid.NewGuid().GetHashCode()).NextDouble());
            Send(PacketType.Echo, txt + new Random(Guid.NewGuid().GetHashCode()).Next(1000, 1000000));
            Send(PacketType.Echo, txt + Guid.NewGuid());
        }

        private void BtnGetServerTime_Click(object sender, EventArgs e)
        {
            Send(PacketType.Time, null);
        }

        private void BtnClear_Click(object sender, EventArgs e)
        {
            txtLog.Text = "";
        }

        private void AddLog(string log)
        {
            if (txtLog.IsDisposed)
            {
                return;
            }

            // 从ui线程去操作ui
            if (txtLog.InvokeRequired)
            {
                txtLog.Invoke(new AddLogHandler(AddLog), log);
            }
            else
            {
                txtLog.AppendText($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] {log}\r\n");
            }
        }

    }
TcpClient

 

posted @ 2022-11-03 10:33  bxzjzg  阅读(24)  评论(0编辑  收藏  举报