『7x24小时有问必答』

一、核心设计思路

工业级 TCP 客户端需满足:
1、断线自动重连(带重试策略,避免无限无效重试)
2、异步通信(避免阻塞主线程,提升并发处理能力)
3、数据粘包 / 拆包处理(基于固定头 + 数据体的通用协议)
4、完善的异常处理与资源释放
5、关键流程日志记录(便于问题排查)
6、线程安全(避免多线程操作冲突

二、完整实现代码

1. 先定义通用数据协议(解决粘包 / 拆包)

工业场景中最常用的是「固定长度头 + 数据体」协议,这里定义头长度为 4 字节(存储数据体长度),后续可根据需求扩展
  • 内置支持的编码类型(新增ASCII编码)
•  工业场景编码选型总结表
编码格式
字符支持
跨平台性
工业适用场景
优先级
UTF-8
全 Unicode 字符(含多语言)
极强
跨平台设备、多语言、长期稳定系统
GBK
中文(简 / 繁)、ASCII
较差
老旧 Windows 工控设备、国内传统产线
UTF-16(Unicode)
全 Unicode 字符
一般
纯Windows.NET工业环境、部分嵌入式设备
Latin-1(ISO 8859-1)
西欧字符、ASCII、工业符号
极强
欧美进口设备、西欧语言环境
ISO 8859-15
西欧字符、欧元符号、ASCII
极强
欧美高端设备、工业财务报表
ASCII
英文、数字、基础符号
极强
纯英文 / 数字设备指令、参数配置

using  System;using  System.IO;using  System.Net;using  System.Net.Sockets;using  System.Text;using  System.Threading;using  System.Threading.Tasks;namespace  IndustrialMultiEncodingTcpClient{       ///  <summary>       ///  客户端接收数据模式       ///  </summary>       public  enum  ReceiveMode      {            IndustrialProtocol =  0,  // 工业协议模式(带5字节协议头,对接配套服务端)            RawData =  1  // 裸数据模式(无协议头,对接调试助手)      }       ///  <summary>       ///  内置支持的编码类型(用于协议头标识,可扩展)       ///  </summary>       public  enum  EncodingType      {            UTF8 =  0,            // 跨平台首选            GBK =  1,              // 传统Windows/老旧工业设备            GB2312 =  2,         // 早期简体中文场景            Unicode =  3,        // .NET默认内部编码(UTF-16小端序)            BigEndianUnicode =  4,// UTF-16大端序(跨设备场景)            ASCII =  5             // 新增:ASCII编码(仅支持英文/数字/基础符号)      }       ///  <summary>       ///  支持大部分编码规则的工业级TCP客户端       ///  </summary>       public  class  MultiEncodingIndustrialTcpClient  :  IDisposable      {             // 新增:接收模式配置(默认工业协议模式)             public  ReceiveMode CurrentReceiveMode {  get;  set; } = ReceiveMode.IndustrialProtocol;             #region  配置参数             private  readonly  string  _serverIp;             private  readonly  int  _serverPort;             private  readonly  int  _reconnectIntervalMs;             private  readonly  int  _maxReconnectAttempts;             private  readonly  EncodingType _defaultEncodingType;  // 默认编码类型             private  Encoding _currentEncoding;  // 当前使用的编码实例             #endregion             #region  核心字段             private  TcpClient? _tcpClient;             private  NetworkStream? _networkStream;             private  CancellationTokenSource? _cts;             private  bool  _isConnected;             private  bool  _isDisposed;             private  int  _currentReconnectAttempt;             private  readonly  object  _encodingLock =  new  object();  // 编码操作锁(线程安全)             #endregion             #region  公共属性             ///  <summary>             ///  是否已成功连接到服务端             ///  </summary>             public  bool  IsConnected => _isConnected && _tcpClient?.Connected ==  true;             ///  <summary>             ///  当前使用的编码(可外部修改,线程安全)             ///  </summary>             public  Encoding CurrentEncoding            {                   get                  {                         lock  (_encodingLock)                        {                               return  _currentEncoding ?? Encoding.UTF8;                        }                  }                   set                  {                         lock  (_encodingLock)                        {                               if  (value  ==  null)                                     throw  new  ArgumentNullException(nameof(CurrentEncoding),  "编码实例不能为null");                              _currentEncoding =  value;                        }                  }            }             #endregion             #region  构造函数             ///  <summary>             ///  初始化多编码工业级TCP客户端             ///  </summary>             ///  <param name="serverIp">服务端IP             ///  <param name="serverPort">服务端端口             ///  <param name="defaultEncodingType">默认编码类型(默认UTF-8)             ///  <param name="reconnectIntervalMs">重连间隔(默认5000毫秒)             ///  <param name="maxReconnectAttempts">最大重连次数(默认-1,无限重试)             public  MultiEncodingIndustrialTcpClient(string  serverIp,  int  serverPort,                  EncodingType defaultEncodingType = EncodingType.UTF8,                   int  reconnectIntervalMs =  5000,  int  maxReconnectAttempts =  -1)            {                   // 参数校验                   if  (string.IsNullOrEmpty(serverIp) || !IPAddress.TryParse(serverIp,  out  _))                         throw  new  ArgumentException("无效的服务端IP地址",  nameof(serverIp));                   if  (serverPort <  1  || serverPort >  65535)                         throw  new  ArgumentOutOfRangeException(nameof(serverPort),  "端口号必须在1-65535之间");                   if  (reconnectIntervalMs <  1000)                         throw  new  ArgumentOutOfRangeException(nameof(reconnectIntervalMs),  "重连间隔不能小于1000毫秒");                  _serverIp = serverIp;                  _serverPort = serverPort;                  _reconnectIntervalMs = reconnectIntervalMs;                  _maxReconnectAttempts = maxReconnectAttempts;                  _defaultEncodingType = defaultEncodingType;                   // 初始化默认编码                  _currentEncoding = GetEncodingByType(defaultEncodingType);                  _cts =  new  CancellationTokenSource();            }             #endregion             #region  核心工具方法:编码转换与获取             ///  <summary>             ///  根据编码类型获取对应的编码实例(新增ASCII编码支持)             ///  </summary>             ///  <param name="encodingType">编码类型             ///  <returns>编码实例</returns>             public  static  Encoding  GetEncodingByType(EncodingType encodingType)            {                   switch  (encodingType)                  {                         case  EncodingType.UTF8:                               return  Encoding.UTF8;  // 无BOM,跨平台首选                         case  EncodingType.GBK:                               try                              {                                     return  Encoding.GetEncoding("GBK");                              }                               catch  (ArgumentException)                              {                                     throw  new  NotSupportedException("当前系统不支持GBK编码,请安装对应的编码库");                              }                         case  EncodingType.GB2312:                               try                              {                                     return  Encoding.GetEncoding("GB2312");                              }                               catch  (ArgumentException)                              {                                     throw  new  NotSupportedException("当前系统不支持GB2312编码,请安装对应的编码库");                              }                         case  EncodingType.Unicode:                               return  Encoding.Unicode;  // UTF-16 小端序                         case  EncodingType.BigEndianUnicode:                               return  Encoding.BigEndianUnicode;  // UTF-16 大端序                         case  EncodingType.ASCII:  // 新增:ASCII编码分支                               return  Encoding.ASCII;  // 直接返回内置ASCII编码实例,全平台支持                         default:                               return  Encoding.UTF8;  // 默认回退到UTF-8                  }            }             ///  <summary>             ///  根据协议头编码标识获取编码类型             ///  </summary>             ///  <param name="encodeFlag">编码标识字节             ///  <returns>编码类型</returns>             public  static  EncodingType  GetEncodingTypeByFlag(byte  encodeFlag)            {                   if  (Enum.IsDefined(typeof(EncodingType), encodeFlag))                  {                         return  (EncodingType)encodeFlag;                  }                   return  EncodingType.UTF8;  // 未知标识回退到UTF-8            }             #endregion             #region  核心方法:连接/重连(工业级断线重连)             ///  <summary>             ///  异步连接到服务端             ///  </summary>             ///  <returns>连接是否成功</returns>             public  async  Task<bool>  ConnectAsync()            {                   if  (IsConnected)                  {                        Console.WriteLine("已处于连接状态,无需重复连接");                         return  true;                  }                   try                  {                        _currentReconnectAttempt =  0;                        _tcpClient =  new  TcpClient();                        Console.WriteLine($"正在连接到服务端  {_serverIp}:{_serverPort},默认编码:{_defaultEncodingType}");                         await  _tcpClient.ConnectAsync(_serverIp, _serverPort);                         // 初始化网络流,配置读写超时(工业场景避免无限阻塞)                        _networkStream = _tcpClient.GetStream();                        _networkStream.ReadTimeout =  5000;                        _networkStream.WriteTimeout =  5000;                         // 更新连接状态                        _isConnected =  true;                        _currentReconnectAttempt =  0;                        Console.WriteLine($"成功连接到服务端  {_serverIp}:{_serverPort}");                         // 启动异步接收数据任务(自动适配编码解析)                        _ = Task.Run(ReceiveDataLoopAsync, _cts?.Token ?? CancellationToken.None);                         return  true;                  }                   catch  (SocketException ex)                  {                        Console.WriteLine($"连接服务端失败:{ex.Message}(错误码:{ex.ErrorCode})");                        _isConnected =  false;                         // 触发自动重连                        _ = Task.Run(ReconnectLoopAsync, _cts?.Token ?? CancellationToken.None);                         return  false;                  }                   catch  (Exception ex)                  {                        Console.WriteLine($"连接服务端异常:{ex.Message}");                        _isConnected =  false;                         return  false;                  }            }             ///  <summary>             ///  自动重连循环(带可控重试策略)             ///  </summary>             private  async  Task  ReconnectLoopAsync()            {                   if  (_cts?.IsCancellationRequested ??  true)  return;                  Console.WriteLine("启动自动重连循环...");                   while  (!_cts?.IsCancellationRequested ??  false)                  {                         // 达到最大重连次数,终止重连                         if  (_maxReconnectAttempts !=  -1  && _currentReconnectAttempt >= _maxReconnectAttempts)                        {                              Console.WriteLine($"已达到最大重连次数  {_maxReconnectAttempts},终止重连");                               break;                        }                        _currentReconnectAttempt++;                        Console.WriteLine($"第  {_currentReconnectAttempt}  次重连尝试,将在  {_reconnectIntervalMs /  1000}  秒后执行...");                         try                        {                               // 等待重连间隔,可被取消                               await  Task.Delay(_reconnectIntervalMs, _cts?.Token ?? CancellationToken.None);                               // 尝试重新连接                               if  (await  ConnectAsync())                              {                                    Console.WriteLine("重连成功,终止重连循环");                                     break;                              }                        }                         catch  (OperationCanceledException)                        {                              Console.WriteLine("重连循环已被主动终止");                               break;                        }                         catch  (Exception ex)                        {                              Console.WriteLine($"重连过程异常:{ex.Message}");                               continue;                        }                  }            }             #endregion             #region  核心方法:多编码数据发送(支持普通字符串/字节数组)             ///  <summary>             ///  异步发送字符串数据(使用当前编码,自动封装协议头含编码标识)             ///  </summary>             ///  <param name="message">待发送的字符串(支持中文等多字符)             ///  <returns>是否发送成功</returns>             public  async  Task<bool>  SendStringDataAsync(string  message)            {                   if  (string.IsNullOrEmpty(message))                  {                        Console.WriteLine("发送数据为空,忽略发送");                         return  false;                  }                   if  (!IsConnected || _networkStream ==  null)                  {                        Console.WriteLine("未连接到服务端,无法发送数据");                         return  false;                  }                   try                  {                         // 1. 获取当前编码,转换字符串为字节数组                        Encoding currentEncode = CurrentEncoding;                         byte[] dataBody = currentEncode.GetBytes(message);                         // 2. 封装工业级协议头:                         // 格式:[编码标识(1字节)] + [数据体长度(4字节)] + [数据体(N字节)]                         byte  encodeFlag = (byte)GetEncodingTypeByInstance(currentEncode);  // 编码标识                         byte[] lengthBytes = BitConverter.GetBytes(dataBody.Length);                         // 统一字节序(大端序),避免跨设备长度解析错误                         if  (BitConverter.IsLittleEndian) Array.Reverse(lengthBytes);                         // 3. 拼接完整发送缓冲区                         byte[] sendBuffer =  new  byte[1  +  4  + dataBody.Length];                        Buffer.BlockCopy(new[] { encodeFlag },  0, sendBuffer,  0,  1);                        Buffer.BlockCopy(lengthBytes,  0, sendBuffer,  1,  4);                        Buffer.BlockCopy(dataBody,  0, sendBuffer,  5, dataBody.Length);                         // 4. 异步发送数据,强制刷新缓冲区                         await  _networkStream.WriteAsync(sendBuffer,  0, sendBuffer.Length, _cts?.Token ?? CancellationToken.None);                         await  _networkStream.FlushAsync();                        Console.WriteLine($"成功发送数据(编码:{currentEncode.EncodingName})");                        Console.WriteLine($"发送内容:{message},数据体长度:{dataBody.Length},总发送长度:{sendBuffer.Length}");                         return  true;                  }                   catch  (IOException ex)                  {                        Console.WriteLine($"发送数据异常,检测到断线:{ex.Message}");                        _isConnected =  false;                        _ = Task.Run(ReconnectLoopAsync, _cts?.Token ?? CancellationToken.None);                         return  false;                  }                   catch  (Exception ex)                  {                        Console.WriteLine($"发送数据未知异常:{ex.Message}");                         return  false;                  }            }             ///  <summary>             ///  异步发送原始字节数组(指定编码标识,适配服务端解析)             ///  </summary>             ///  <param name="data">原始字节数组             ///  <param name="encodingType">编码标识(用于服务端解码)             ///  <returns>是否发送成功</returns>             public  async  Task<bool>  SendRawDataAsync(byte[] data, EncodingType encodingType)            {                   if  (data ==  null  || data.Length ==  0)                  {                        Console.WriteLine("原始字节数组为空,忽略发送");                         return  false;                  }                   if  (!IsConnected || _networkStream ==  null)                  {                        Console.WriteLine("未连接到服务端,无法发送数据");                         return  false;                  }                   try                  {                         // 1. 封装协议头                         byte  encodeFlag = (byte)encodingType;                         byte[] lengthBytes = BitConverter.GetBytes(data.Length);                         if  (BitConverter.IsLittleEndian) Array.Reverse(lengthBytes);                         // 2. 拼接缓冲区                         byte[] sendBuffer =  new  byte[1  +  4  + data.Length];                        Buffer.BlockCopy(new[] { encodeFlag },  0, sendBuffer,  0,  1);                        Buffer.BlockCopy(lengthBytes,  0, sendBuffer,  1,  4);                        Buffer.BlockCopy(data,  0, sendBuffer,  5, data.Length);                         // 3. 异步发送                         await  _networkStream.WriteAsync(sendBuffer,  0, sendBuffer.Length, _cts?.Token ?? CancellationToken.None);                         await  _networkStream.FlushAsync();                        Console.WriteLine($"成功发送原始字节数组(编码标识:{encodingType}),总长度:{sendBuffer.Length}");                         return  true;                  }                   catch  (IOException ex)                  {                        Console.WriteLine($"发送原始数据异常,检测到断线:{ex.Message}");                        _isConnected =  false;                        _ = Task.Run(ReconnectLoopAsync, _cts?.Token ?? CancellationToken.None);                         return  false;                  }                   catch  (Exception ex)                  {                        Console.WriteLine($"发送原始数据未知异常:{ex.Message}");                         return  false;                  }            }             ///  <summary>             ///  根据编码实例获取对应的编码类型(辅助方法)             ///  </summary>             private  EncodingType  GetEncodingTypeByInstance(Encoding encoding)            {                   if  (encoding.Equals(Encoding.UTF8))  return  EncodingType.UTF8;                   if  (encoding.Equals(Encoding.Unicode))  return  EncodingType.Unicode;                   if  (encoding.Equals(Encoding.BigEndianUnicode))  return  EncodingType.BigEndianUnicode;                   if  (encoding.Equals(Encoding.ASCII))  return  EncodingType.ASCII;  // 新增:ASCII编码匹配                   try                  {                         if  (encoding.Equals(Encoding.GetEncoding("GBK")))  return  EncodingType.GBK;                         if  (encoding.Equals(Encoding.GetEncoding("GB2312")))  return  EncodingType.GB2312;                  }                   catch                  {                         // 忽略编码不存在异常                  }                   return  EncodingType.UTF8;  // 回退默认            }             ///  <summary>             ///  异步发送ASCII裸数据(无任何自定义协议头,适配调试助手/无协议服务端)             ///  说明:直接发送数据体字节数组,不添加编码标识和数据长度,避免乱码前缀             ///  </summary>             ///  <param name="message">仅包含ASCII支持的英文/数字/基础符号             ///  <returns>是否发送成功</returns>             public  async  Task<bool>  SendAsciiRawDataWithoutProtocolAsync(string  message)            {                   if  (string.IsNullOrEmpty(message))                  {                        Console.WriteLine("发送数据为空,忽略发送");                         return  false;                  }                   if  (!IsConnected || _networkStream ==  null)                  {                        Console.WriteLine("未连接到服务端,无法发送数据");                         return  false;                  }                   try                  {                         // 1. 仅将字符串转换为ASCII字节数组(无任何附加协议头)                        Encoding asciiEncoding = Encoding.ASCII;                         byte[] rawData = asciiEncoding.GetBytes(message);                         // 2. 直接发送纯数据体,不封装任何额外字节(核心:适配调试助手)                         await  _networkStream.WriteAsync(rawData,  0, rawData.Length, _cts?.Token ?? CancellationToken.None);                         await  _networkStream.FlushAsync();                        Console.WriteLine($"成功发送ASCII裸数据(无协议头):{message}");                        Console.WriteLine($"数据字节长度:{rawData.Length}");                         return  true;                  }                   catch  (IOException ex)                  {                        Console.WriteLine($"发送ASCII裸数据异常,检测到断线:{ex.Message}");                        _isConnected =  false;                        _ = Task.Run(ReconnectLoopAsync, _cts?.Token ?? CancellationToken.None);                         return  false;                  }                   catch  (Exception ex)                  {                        Console.WriteLine($"发送ASCII裸数据未知异常:{ex.Message}");                         return  false;                  }            }             ///  <summary>             ///  异步发送通用裸数据(无自定义协议头,适配调试助手)             ///  </summary>             ///  <param name="message">待发送字符串             ///  <param name="encoding">编码格式             ///  <returns>是否发送成功</returns>             public  async  Task<bool>  SendRawDataWithoutProtocolAsync(string  message, Encoding encoding)            {                   if  (string.IsNullOrEmpty(message) || encoding ==  null  || !IsConnected || _networkStream ==  null)                         return  false;                   try                  {                         byte[] rawData = encoding.GetBytes(message);                         await  _networkStream.WriteAsync(rawData,  0, rawData.Length, _cts?.Token ?? CancellationToken.None);                         await  _networkStream.FlushAsync();                        Console.WriteLine($"成功发送通用裸数据({encoding.EncodingName},无协议头):{message}");                         return  true;                  }                   catch  (IOException ex)                  {                        Console.WriteLine($"发送通用裸数据异常,检测到断线:{ex.Message}");                        _isConnected =  false;                        _ = Task.Run(ReconnectLoopAsync, _cts?.Token ?? CancellationToken.None);                         return  false;                  }                   catch  (Exception ex)                  {                        Console.WriteLine($"发送通用裸数据未知异常:{ex.Message}");                         return  false;                  }            }             #endregion             #region  核心方法:多编码数据接收(自动适配编码解析)             ///  <summary>             ///  异步接收数据循环(持续监听,自动适配编码解析)             ///  </summary>             private  async  Task  ReceiveDataLoopAsync()            {                   if  (_cts?.IsCancellationRequested ??  true  || _networkStream ==  null)  return;                  Console.WriteLine($"开始监听数据,当前接收模式:{CurrentReceiveMode}");                   byte[] protocolHeader =  new  byte[5];  // 工业协议头(5字节)                   byte[] rawDataBuffer =  new  byte[4096];  // 裸数据接收缓冲区(可根据需求调整大小)                   byte[] dataBodyBuffer = Array.Empty<byte>();                   while  (!_cts?.IsCancellationRequested ??  false  && IsConnected)                  {                         try                        {                               // 分支1:工业协议模式(对接配套服务端,原有逻辑不变)                               if  (CurrentReceiveMode == ReceiveMode.IndustrialProtocol)                              {                                     // 接收完整5字节协议头                                     int  headerReceived =  await  _networkStream.ReadAsync(                                          protocolHeader,  0, protocolHeader.Length, _cts?.Token ?? CancellationToken.None);                                     if  (headerReceived ==  0)                                    {                                          Console.WriteLine("服务端主动关闭连接,检测到断线");                                          _isConnected =  false;                                           break;                                    }                                     if  (headerReceived !=  5)                                    {                                          Console.WriteLine($"协议头接收不完整,预期5字节,实际接收{headerReceived}字节,忽略本次数据");                                          Array.Clear(protocolHeader,  0, protocolHeader.Length);                                           continue;                                    }                                     // 解析协议头(字节序转换+长度提取)                                    ProcessProtocolHeader(protocolHeader,  out  byte  encodeFlag,  out  int  dataBodyLength);                                     // 验证长度                                     if  (!ValidateDataBodyLength(dataBodyLength))                                    {                                          Array.Clear(protocolHeader,  0, protocolHeader.Length);                                           continue;                                    }                                     // 接收数据体并解析(原有逻辑)                                    dataBodyBuffer =  new  byte[dataBodyLength];                                     int  totalReceived =  0;                                     while  (totalReceived < dataBodyLength && (!_cts?.IsCancellationRequested ??  false  && IsConnected))                                    {                                           int  received =  await  _networkStream.ReadAsync(                                                dataBodyBuffer, totalReceived, dataBodyLength - totalReceived, _cts?.Token ?? CancellationToken.None);                                           if  (received ==  0)                                          {                                                Console.WriteLine("接收数据体时,服务端关闭连接");                                                _isConnected =  false;                                                 break;                                          }                                          totalReceived += received;                                    }                                     if  (totalReceived == dataBodyLength && IsConnected)                                    {                                          ProcessReceivedData(dataBodyBuffer, encodeFlag);                                    }                                     // 清空缓冲区                                    Array.Clear(protocolHeader,  0, protocolHeader.Length);                                    Array.Clear(dataBodyBuffer,  0, dataBodyBuffer.Length);                              }                               // 分支2:裸数据模式(对接调试助手,新增逻辑)                               else  if  (CurrentReceiveMode == ReceiveMode.RawData)                              {                                     // 直接读取裸数据(不解析协议头,缓冲区大小4096字节)                                     int  rawReceivedLength =  await  _networkStream.ReadAsync(                                          rawDataBuffer,  0, rawDataBuffer.Length, _cts?.Token ?? CancellationToken.None);                                     if  (rawReceivedLength ==  0)                                    {                                          Console.WriteLine("调试助手主动关闭连接,检测到断线");                                          _isConnected =  false;                                           break;                                    }                                     // 仅解析有效接收长度(避免冗余空字节),使用当前编码解码                                    Encoding currentEncode = CurrentEncoding;                                     string  rawContent = currentEncode.GetString(rawDataBuffer,  0, rawReceivedLength);                                     // 触发接收事件,供外部处理                                    OnRawDataReceived(                                          rawDataBuffer.Take(rawReceivedLength).ToArray(),                                          (byte)GetEncodingTypeByInstance(currentEncode));                                    Console.WriteLine($"成功接收调试助手裸数据(编码:{currentEncode.EncodingName})");                                    Console.WriteLine($"接收内容:{rawContent},有效字节长度:{rawReceivedLength}");                                     // 清空裸数据缓冲区,避免残留                                    Array.Clear(rawDataBuffer,  0, rawDataBuffer.Length);                              }                        }                         catch  (IOException ex)                        {                              Console.WriteLine($"接收数据异常,检测到断线:{ex.Message}");                              _isConnected =  false;                               break;                        }                         catch  (OperationCanceledException)                        {                              Console.WriteLine("接收循环已被主动终止");                               break;                        }                         catch  (Exception ex)                        {                              Console.WriteLine($"接收数据未知异常:{ex.Message}");                               continue;                        }                  }                   // 触发自动重连                   if  (!_cts?.IsCancellationRequested ??  false)                  {                        _ = Task.Run(ReconnectLoopAsync, _cts?.Token ?? CancellationToken.None);                  }            }             ///  <summary>             ///  处理协议头,统一字节序解析数据长度(核心:小端序转大端序)             ///  </summary>             ///  <param name="protocolHeader">5字节协议头             ///  <param name="encodeFlag">输出:编码标识             ///  <param name="dataBodyLength">输出:正确的大端序数据体长度             private  void  ProcessProtocolHeader(byte[] protocolHeader,  out  byte  encodeFlag,  out  int  dataBodyLength)            {                   // 1. 提取1字节编码标识                  encodeFlag = protocolHeader[0];                   // 2. 提取4字节长度字段(协议头第2-5字节,索引1-4)                   byte[] lengthBytes =  new  byte[4];                  Buffer.BlockCopy(protocolHeader,  1, lengthBytes,  0,  4);                   // 3. 核心:统一字节序(大端序),小端序系统执行反转操作                   // BitConverter.IsLittleEndian:判断本地系统是否为小端序(Windows/Linux均默认小端序)                   if  (BitConverter.IsLittleEndian)                  {                        Array.Reverse(lengthBytes);  // 小端序→大端序,与协议约定一致                  }                   // 4. 解析为正确的整数长度                  dataBodyLength = BitConverter.ToInt32(lengthBytes,  0);            }             ///  <summary>             ///  处理接收到的数据(自动适配编码解析)             ///  </summary>             ///  <param name="dataBody">数据体字节数组             ///  <param name="encodeFlag">编码标识             private  void  ProcessReceivedData(byte[] dataBody,  byte  encodeFlag)            {                   try                  {                         // 1. 获取编码类型与实例                        EncodingType encodingType = GetEncodingTypeByFlag(encodeFlag);                        Encoding encoding = GetEncodingByType(encodingType);                         // 2. 解码数据体                         string  receivedContent = encoding.GetString(dataBody,  0, dataBody.Length);                         // 3. 触发外部回调(供上层业务处理)                        OnDataReceived(receivedContent, encodingType, dataBody);                        Console.WriteLine($"成功解析数据(编码:{encoding.EncodingName})");                        Console.WriteLine($"解析内容:{receivedContent},数据体长度:{dataBody.Length}");                  }                   catch  (Exception ex)                  {                        Console.WriteLine($"解析接收数据失败:{ex.Message}");                         // 即使解码失败,也触发原始字节回调,避免数据丢失                        OnRawDataReceived(dataBody, encodeFlag);                  }            }             ///  <summary>             ///  验证数据体长度的有效性(容错机制)             ///  </summary>             ///  <param name="dataBodyLength">解析后的大端序长度             ///  <returns>是否有效</returns>             private  bool  ValidateDataBodyLength(int  dataBodyLength)            {                   // 工业级安全范围:1字节 ~ 1MB(1048576字节),可根据需求调整                   int  maxAllowedLength =  1024  *  1024;  // 1MB                   if  (dataBodyLength <=  0  || dataBodyLength > maxAllowedLength)                  {                        Console.WriteLine($"无效的数据体长度:{dataBodyLength},超出允许范围(1-{maxAllowedLength}字节)");                         return  false;                  }                   // 额外容错:避免极端场景下的长度溢出(可选)                   if  (dataBodyLength >  int.MaxValue /  2)                  {                        Console.WriteLine($"数据体长度过大,存在溢出风险,忽略本次数据");                         return  false;                  }                   return  true;            }             #endregion             #region  公共事件:供外部订阅处理数据             ///  <summary>             ///  已解析的数据接收事件(字符串格式)             ///  </summary>             public  event  Action<string, EncodingType,  byte[]>? DataReceived;             ///  <summary>             ///  原始字节数组接收事件(解码失败时触发)             ///  </summary>             public  event  Action<byte[],  byte>? RawDataReceived;             ///  <summary>             ///  触发数据接收事件             ///  </summary>             private  void  OnDataReceived(string  content, EncodingType encodingType,  byte[] rawData)            {                  DataReceived?.Invoke(content, encodingType, rawData);            }             ///  <summary>             ///  触发原始数据接收事件             ///  </summary>             private  void  OnRawDataReceived(byte[] rawData,  byte  encodeFlag)            {                  RawDataReceived?.Invoke(rawData, encodeFlag);            }             #endregion             #region  资源释放(IDisposable实现,优雅终止)             public  void  Dispose()            {                  Dispose(true);                  GC.SuppressFinalize(this);            }             protected  virtual  void  Dispose(bool  disposing)            {                   if  (_isDisposed)  return;                   if  (disposing)                  {                         // 取消所有异步任务                        _cts?.Cancel();                        _cts?.Dispose();                        _cts =  null;                         // 更新连接状态                        _isConnected =  false;                         // 释放网络资源                        _networkStream?.Close();                        _networkStream?.Dispose();                        _networkStream =  null;                        _tcpClient?.Close();                        _tcpClient?.Dispose();                        _tcpClient =  null;                        Console.WriteLine("多编码TCP客户端资源已释放,连接已断开");                  }                  _isDisposed =  true;            }            ~MultiEncodingIndustrialTcpClient()            {                  Dispose(false);            }             #endregion      }}
2. 调用示例(控制台程序)
using  System;using  System.Threading.Tasks;namespace  IndustrialFinalTcpClient{       class  Program      {             static  async  Task  Main(string[]  args)            {                   // 1. 初始化最终版TCP客户端(对接本地服务端/调试助手,端口8888)                   using  var  tcpClient =  new  FinalIndustrialTcpClient(                        serverIp:  "127.0.0.1",                        serverPort:  8888,                        defaultEncodingType: EncodingType.ASCII,                        reconnectIntervalMs:  5000,                        maxReconnectAttempts:  -1,                        maxDataBodyLength:  1024  *  1024                  );                   // 2. 订阅各类事件(处理数据接收和连接状态变更)                  tcpClient.DataReceived += (content, encodingType, rawData) =>                  {                        Console.WriteLine($"\n=== 工业协议数据接收事件 ===");                        Console.WriteLine($"编码类型:{encodingType}");                        Console.WriteLine($"接收内容:{content}");                        Console.WriteLine($"原始字节长度:{rawData.Length}");                        Console.WriteLine($"===========================\n");                  };                  tcpClient.RawDataReceived += (rawData, encodeFlag) =>                  {                         var  encodingType = FinalIndustrialTcpClient.GetEncodingTypeByFlag(encodeFlag);                         var  encoding = FinalIndustrialTcpClient.GetEncodingByType(encodingType);                         string  content = encoding.GetString(rawData,  0, rawData.Length);                        Console.WriteLine($"\n=== 裸数据接收事件 ===");                        Console.WriteLine($"编码标识:{encodeFlag}{encodingType})");                        Console.WriteLine($"接收内容:{content}");                        Console.WriteLine($"原始字节长度:{rawData.Length}");                        Console.WriteLine($"===========================\n");                  };                  tcpClient.ConnectionStateChanged += (isConnected, message) =>                  {                        Console.WriteLine($"\n【连接状态】{(isConnected ?  " 已连接"  :  " 未连接")}  -  {message}");                  };                   // 3. 切换接收模式(根据对接对象选择,此处演示裸数据模式适配调试助手)                   // 对接工业级配套服务端:使用 ReceiveMode.IndustrialProtocol(默认)                   // 对接调试助手:使用 ReceiveMode.RawData                  tcpClient.CurrentReceiveMode = ReceiveMode.RawData;                   // 4. 切换编码(按需选择,此处演示ASCII编码,如需中文可切换为UTF8/GBK)                  tcpClient.CurrentEncoding = FinalIndustrialTcpClient.GetEncodingByType(EncodingType.ASCII);                   // 5. 连接服务端/调试助手                   await  tcpClient.ConnectAsync();                   // 6. 循环发送数据(按需选择发送方法)                   int  sendCount =  0;                   while  (!Console.KeyAvailable)                  {                         if  (tcpClient.IsConnected)                        {                              sendCount++;                               string  message =  $"Final TCP Client Test  {sendCount}  - Device Command: START_00{sendCount}";                               // 对接调试助手:使用裸数据发送方法                               await  tcpClient.SendStringDataWithoutProtocolAsync(message);                               // 对接工业服务端:使用带协议头发送方法                               // await tcpClient.SendStringDataWithProtocolAsync(message);                               // 每5秒发送一次                               await  Task.Delay(5000);                        }                  }                   // 7. 按下任意键退出,自动释放客户端资源                  Console.WriteLine("\n按下任意键关闭客户端...");                  Console.ReadKey();            }      }}

三、核心特性总结(整合所有优化)

多编码完整支持:         内置ASCII/UTF-8/GBK/GB2312/Unicode等 6 种编码,支持编码动态切换,线程安全,解码失败容错回退。双接收模式适:
     「工业协议模式」:带 5 字节协议头(编码标识 + 大端序长度),解决粘包 / 拆包,适配工业级配套服务端;
「裸数据模式」:无协议头直接解析,适配调试助手等简易工具,避免长度无效报错。
工业级可靠连接:           可控断线重连(间隔 / 最大次数配置),网络流超时设置,连接状态事件通知,避免无限阻塞。

完善的容错机制:
        1、协议头完整性校验,缓冲区残留清空;
        2、字节序统一(大端序),解决跨设备长度解析错误;
        3、数据体长度安全校验,防止恶意数据 / 内存溢出;
        4、编码库不存在异常捕获,工业场景抗干扰。
灵活的数据发送:        支持「带协议头 / 裸数据」两种发送方式,适配不同对接对象,支持原始字节数组发送优雅的资源释放:           实现IDisposable接口,按顺序释放网络流 / TCP 客户端 / 取消令牌,避免内存泄露和句柄残留。解耦的事件设计:            数据接收和连接状态通过事件暴露,方便上层业务逻辑扩展,无需修改客户端核心代码

四、使用指南(快速落地)

1、对接工业级配套服务端:
     • 接收模式:ReceiveMode.IndustrialProtocol(默认);
     •  发送方法:   
   SendStringDataWithProtocolAsync/SendRawDataWithProtocolAsync
   

     •  编码:双方约定一致(推荐 UTF-8,兼容全字符集)。
2、对接调试助手 / 裸数据工具:
     •  接收模式:ReceiveMode.RawData
     •  发送方法:SendStringDataWithoutProtocolAsync
     •  编码:手动约定一致(推荐 ASCII/UTF-8,避免中文乱码);
     •  缓冲区:根据数据大小调整RawDataBufferSize(默认 4096 字节)。
3、中文场景适配:
     •  编码切换为EncodingType.UTF8EncodingType.GBK
     •  确保服务端 / 调试助手使用相同编码发送数据;
     •  最大数据体长度可调整为 4MB(4 * 1024 * 1024),适配大报文。
4、工业场景部署:
     •  开启无限重连(maxReconnectAttempts = -1),保证断线自动恢复;
     •  日志升级:将ConnectionStateChanged事件中的日志接入企业日志系统(如 Serilog/NLog);
     •  数据加密:发送前对字节数组进行 AES 加密,接收后解密,提升传输安全性。

五、注意事项

     •  服务端对接:工业协议模式下,服务端需严格遵循「5 字节协议头 + 数据体」格式,字节序统一为大端序,编码标识与客户端一致。
     •  编码限制:ASCII 编码不支持中文,强行发送中文会转换为?,中文场景请使用 UTF-8/GBK。
     •  跨平台兼容:Linux 系统使用 GBK/GB2312 编码时,需提前安装libgdiplusiconv编码库。
     •  粘包处理:裸数据模式不处理 TCP 粘包,适合单次发送少量数据的调试场景,工业场景请使用协议模式。
     •  性能优化:高频发送场景下,可复用发送缓冲区,避免频繁创建字节数组,提升吞吐量。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

上一主题上一主题         下一主题下一主题
QQ手机版小黑屋粤ICP备17165530号

关于我们·投诉举报· 用户帮助· 联系我们 · 本站服务 · 版权声明· 隐私政策 · 投搞指南

法律保护:PLC技术网,plcjs.com,plcjs.net等字样
Copyright 2010-2030. All rights reserved. 


微信公众号二维码 抖音二维码 百家号二维码 今日头条二维码哔哩哔哩二维码