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.UTF8或EncodingType.GBK;
• 确保服务端 / 调试助手使用相同编码发送数据;
• 最大数据体长度可调整为 4MB(4 * 1024 * 1024),适配大报文。
4、工业场景部署: • 开启无限重连(maxReconnectAttempts = -1),保证断线自动恢复;
• 日志升级:将ConnectionStateChanged事件中的日志接入企业日志系统(如 Serilog/NLog);
• 数据加密:发送前对字节数组进行 AES 加密,接收后解密,提升传输安全性。
五、注意事项
• 服务端对接:工业协议模式下,服务端需严格遵循「5 字节协议头 + 数据体」格式,字节序统一为大端序,编码标识与客户端一致。
• 编码限制:ASCII 编码不支持中文,强行发送中文会转换为?,中文场景请使用 UTF-8/GBK。
• 跨平台兼容:Linux 系统使用 GBK/GB2312 编码时,需提前安装libgdiplus或iconv编码库。
• 粘包处理:裸数据模式不处理 TCP 粘包,适合单次发送少量数据的调试场景,工业场景请使用协议模式。
• 性能优化:高频发送场景下,可复用发送缓冲区,避免频繁创建字节数组,提升吞吐量。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!