TcpProtocol.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. /*
  2. 伪代码计划(详细步骤):
  3. 1. 在文件顶部添加多行注释,说明将要执行的注释生成工作(用于审阅)。
  4. 2. 为公共类 `TcpProtocol` 添加 XML 文档注释,描述其用途与行为。
  5. 3. 为构造函数添加注释,说明参数含义及默认值。
  6. 4. 为每个公共属性(如 `IsConnected`,`_waitClient`,`Terminator`,`Encoding`)添加 XML 注释,说明返回值或作用,指明线程/连接相关注意事项(只读/可写)。
  7. 5. 为事件(`ConnectionChanged`、`DataReceived`、`DataSent`)添加注释,说明何时触发、参数含义。
  8. 6. 为每个公共方法添加 XML 注释:
  9. - `ConnectAsync`: 说明尝试连接的行为、返回值及异常处理,标注使用的配置项(结束符处理)。
  10. - `DisconnectAsync`: 说明断开连接的行为及事件触发。
  11. - `SendAndReceiveAsync(byte[], int)`: 说明发送/接收的流程、超时含义及可能抛出的异常。
  12. - `SendAsync(byte[])`, `Send(byte[])`, `Send(string)`, `SendAsync(string)`: 说明发送方法区别与同步/异步注意事项。
  13. - 同步版本的 `SendAndReceive` 方法说明阻塞行为与异常。
  14. - `Dispose`: 说明释放资源。
  15. 7. 保持现有实现不变,仅插入文档注释与必要的内部注释,以便于 IntelliSense 与维护。
  16. 8. 确保注释为中文,简洁明了,符合 .NET XML 注释惯例,并且不修改代码逻辑或签名。
  17. 注:所有注释均以 XML 文档注释形式写入,便于 Visual Studio 的 IntelliSense 展示。
  18. */
  19. using System;
  20. using System.Collections.Generic;
  21. using System.Linq;
  22. using System.Net;
  23. using System.Text;
  24. using System.Threading;
  25. using System.Threading.Tasks;
  26. using System.Web.UI.WebControls.WebParts;
  27. using TeamAAS_VP.Enums;
  28. using TeamAAS_VP.Models.Robot;
  29. using TouchSocket.Core;
  30. using TouchSocket.Sockets;
  31. namespace TeamAAS_VP.Core.Lights
  32. {
  33. /// <summary>
  34. /// 基于 TouchSocket 的 TCP 通信协议实现。
  35. /// 提供同步/异步的发送/接收方法,并在连接状态、发送/接收数据时触发事件。
  36. /// 注意:此类不保证线程安全,调用方应在多线程场景做并发控制。
  37. /// </summary>
  38. public class TcpProtocol : ICommunicationProtocol
  39. {
  40. /// <summary>
  41. /// 目标主机地址(仅构造时设置)。
  42. /// </summary>
  43. private readonly string _host;
  44. /// <summary>
  45. /// 目标端口(仅构造时设置)。
  46. /// </summary>
  47. private readonly int _port;
  48. /// <summary>
  49. /// TouchSocket 的 TCP 客户端实例。
  50. /// </summary>
  51. private TcpClient _client;
  52. /// <summary>
  53. /// 获取当前连接状态。若未初始化客户端或客户端不在线,则返回 false。
  54. /// </summary>
  55. public bool IsConnected => _client?.Online == true;
  56. /// <summary>
  57. /// 等待客户端,用于发送后等待返回(SendThenReturn)的操作。
  58. /// 注意:该字段在连接成功后由 ConnectAsync 初始化。
  59. /// </summary>
  60. public IWaitingClient<ITcpClient, IReceiverResult> _waitClient { get; private set; }
  61. /// <summary>
  62. /// 数据包结束符配置,决定接收时的数据分包行为(None/CR/LF/CRLF)。
  63. /// </summary>
  64. public Terminator Terminator { get; set; } = Terminator.None;
  65. /// <summary>
  66. /// 数据编码,默认使用 ASCII。
  67. /// 在发送/接收时用于将字符串与字节数组互相转换。
  68. /// </summary>
  69. public Encoding Encoding { get; set; } = Encoding.ASCII;
  70. /// <summary>
  71. /// 当连接状态发生变化时触发。参数:sender、是否已连接(true=已连接)。
  72. /// </summary>
  73. public event Action<object, bool> ConnectionChanged;
  74. /// <summary>
  75. /// 当接收到数据时触发。参数:sender、接收到的数据(已使用 <see cref="Encoding"/> 转为字符串)。
  76. /// </summary>
  77. public event Action<object, string> DataReceived;
  78. /// <summary>
  79. /// 当发送数据时触发。参数:sender、发送的数据(字符串形式,使用 <see cref="Encoding"/> 转换)。
  80. /// </summary>
  81. public event Action<object, string> DataSent;
  82. /// <summary>
  83. /// 创建一个新的 <see cref="TcpProtocol"/> 实例。
  84. /// </summary>
  85. /// <param name="host">目标主机 IP 地址字符串。</param>
  86. /// <param name="port">目标端口号。</param>
  87. /// <param name="terminator">可选的数据结束符配置,默认为 <see cref="Terminator.None"/>。</param>
  88. public TcpProtocol(string host, int port, Terminator terminator = Terminator.None)
  89. {
  90. _host = host;
  91. _port = port;
  92. Terminator = terminator;
  93. }
  94. /// <summary>
  95. /// 异步连接到远端主机并根据 <see cref="Terminator"/> 配置数据分包适配器。
  96. /// 成功连接后会触发 <see cref="ConnectionChanged"/> 事件。
  97. /// </summary>
  98. /// <returns>如果连接成功返回 true,否则返回 false。</returns>
  99. public async Task<bool> ConnectAsync()
  100. {
  101. try
  102. {
  103. _client = new TouchSocket.Sockets.TcpClient();
  104. var config = new TouchSocketConfig();
  105. config.SetRemoteIPHost(new IPHost(IPAddress.Parse(_host), _port));
  106. config.ConfigurePlugins(a => { a.UseTcpReconnection(); }); ////如需永远尝试连接,tryCount设置为-1即可。
  107. ////设置结束符
  108. if (Terminator == Terminator.None)
  109. {
  110. config.SetTcpDataHandlingAdapter(() => { return new NormalDataHandlingAdapter(); }); ////亦或者省略\r\n,但此时调用方不能高速调用,会粘包
  111. }
  112. else if (Terminator == Terminator.CR)
  113. {
  114. config.SetTcpDataHandlingAdapter(() => { return new TerminatorPackageAdapter("\r"); }); //命令行中使用\r结尾
  115. }
  116. else if (Terminator == Terminator.LF)
  117. {
  118. config.SetTcpDataHandlingAdapter(() => { return new TerminatorPackageAdapter("\n"); }); //命令行中使用\n结尾
  119. }
  120. else if (Terminator == Terminator.CRLF)
  121. {
  122. config.SetTcpDataHandlingAdapter(() => { return new TerminatorPackageAdapter("\r\n"); }); //命令行中使用\r\n结尾
  123. }
  124. //载入配置
  125. _client.Setup(config);
  126. ////调用CreateWaitingClient获取到IWaitingClient的对象。
  127. _waitClient = _client.CreateWaitingClient(new WaitingOptions()
  128. {
  129. FilterFunc = response => //设置用于筛选的fun委托,当返回为true时,才会响应返回
  130. {
  131. return true;
  132. //if (response.Data.Length == 1)
  133. //{
  134. // return true;
  135. //}
  136. //return false;
  137. }
  138. });
  139. var result = await _client.TryConnectAsync();
  140. if (result.IsSuccess)
  141. {
  142. ConnectionChanged?.Invoke(this, true);
  143. return true;
  144. }
  145. ConnectionChanged?.Invoke(this, false);
  146. return false;
  147. }
  148. catch
  149. {
  150. return false;
  151. }
  152. }
  153. /// <summary>
  154. /// 异步断开当前连接并触发 <see cref="ConnectionChanged"/> 事件(false)。
  155. /// </summary>
  156. /// <returns>已完成的任务。</returns>
  157. public Task DisconnectAsync()
  158. {
  159. ConnectionChanged?.Invoke(this, false);
  160. _client?.Close();
  161. return Task.CompletedTask;
  162. }
  163. /// <summary>
  164. /// 异步发送字节数据并等待响应。
  165. /// </summary>
  166. /// <param name="data">要发送的字节数组。</param>
  167. /// <param name="timeout">等待响应的超时时间(毫秒),默认 5000 毫秒。</param>
  168. /// <returns>返回接收到的字节数组。</returns>
  169. /// <exception cref="InvalidOperationException">当尚未连接或等待客户端未初始化时抛出。</exception>
  170. public async Task<byte[]> SendAndReceiveAsync(byte[] data, int timeout = 5000)
  171. {
  172. if (_waitClient == null)
  173. throw new InvalidOperationException("Not connected");
  174. DataSent?.Invoke(this, Encoding.GetString(data));
  175. var response = await _waitClient.SendThenReturnAsync(data, timeout);
  176. DataReceived?.Invoke(this, Encoding.GetString(response));
  177. return response;
  178. }
  179. /// <summary>
  180. /// 异步发送字节数据(不等待响应)。
  181. /// 注意:内部使用同步发送接口,立即返回 Task.CompletedTask。
  182. /// </summary>
  183. /// <param name="data">要发送的字节数组。</param>
  184. /// <returns>已完成的任务。</returns>
  185. /// <exception cref="InvalidOperationException">当尚未连接时抛出。</exception>
  186. public Task SendAsync(byte[] data)
  187. {
  188. if (_client == null)
  189. throw new InvalidOperationException("Not connected");
  190. DataSent?.Invoke(this, Encoding.GetString(data));
  191. _client.Send(data);
  192. return Task.CompletedTask;
  193. }
  194. /// <summary>
  195. /// 异步发送字符串并等待响应(使用当前 <see cref="Encoding"/> 编码)。
  196. /// </summary>
  197. /// <param name="data">要发送的字符串。</param>
  198. /// <param name="timeout">等待响应的超时时间(毫秒),默认 5000 毫秒。</param>
  199. /// <returns>接收到的字符串响应(使用当前编码解码)。</returns>
  200. /// <exception cref="InvalidOperationException">当尚未连接或等待客户端未初始化时抛出。</exception>
  201. public async Task<string> SendAndReceiveAsync(string data, int timeout = 5000)
  202. {
  203. if (_waitClient == null)
  204. throw new InvalidOperationException("Not connected");
  205. DataSent?.Invoke(this, data);
  206. var response = await SendAndReceiveAsync(Encoding.GetBytes(data), timeout);
  207. DataReceived?.Invoke(this, Encoding.GetString(response));
  208. return Encoding.GetString(response);
  209. }
  210. /// <summary>
  211. /// 同步发送字符串并等待响应(阻塞调用线程)。
  212. /// </summary>
  213. /// <param name="data">要发送的字符串。</param>
  214. /// <param name="timeout">等待响应的超时时间(毫秒),默认 5000 毫秒。</param>
  215. /// <returns>接收到的字符串响应(使用当前编码解码)。</returns>
  216. /// <exception cref="InvalidOperationException">当尚未连接或等待客户端未初始化时抛出。</exception>
  217. public string SendAndReceive(string data, int timeout = 5000)
  218. {
  219. if (_waitClient == null)
  220. throw new InvalidOperationException("Not connected");
  221. DataSent?.Invoke(this, data);
  222. var response = _waitClient.SendThenReturn(Encoding.GetBytes(data), timeout);
  223. DataReceived?.Invoke(this, Encoding.GetString(response));
  224. return Encoding.GetString(response);
  225. }
  226. /// <summary>
  227. /// 同步发送字节数组并等待响应(阻塞调用线程)。
  228. /// </summary>
  229. /// <param name="data">要发送的字节数组。</param>
  230. /// <param name="timeout">等待响应的超时时间(毫秒),默认 5000 毫秒。</param>
  231. /// <returns>接收到的字节数组。</returns>
  232. /// <exception cref="InvalidOperationException">当尚未连接或等待客户端未初始化时抛出。</exception>
  233. public byte[] SendAndReceive(byte[] data, int timeout = 5000)
  234. {
  235. if (_waitClient == null)
  236. throw new InvalidOperationException("Not connected");
  237. DataSent?.Invoke(this, Encoding.GetString(data));
  238. var response = _waitClient.SendThenReturn(data, timeout);
  239. DataReceived?.Invoke(this, Encoding.GetString(response));
  240. return response;
  241. }
  242. /// <summary>
  243. /// 同步发送字符串(不等待响应)。
  244. /// </summary>
  245. /// <param name="data">要发送的字符串。</param>
  246. /// <exception cref="InvalidOperationException">当尚未连接时抛出。</exception>
  247. public void Send(string data)
  248. {
  249. if (_client == null)
  250. throw new InvalidOperationException("Not connected");
  251. DataSent?.Invoke(this, data);
  252. _client.Send(Encoding.GetBytes(data));
  253. }
  254. /// <summary>
  255. /// 异步发送字符串(不等待响应),使用客户端的异步发送接口。
  256. /// </summary>
  257. /// <param name="data">要发送的字符串。</param>
  258. /// <returns>发送完成的任务。</returns>
  259. /// <exception cref="InvalidOperationException">当尚未连接时抛出。</exception>
  260. public async Task SendAsync(string data)
  261. {
  262. if (_client == null)
  263. throw new InvalidOperationException("Not connected");
  264. DataSent?.Invoke(this, data);
  265. await _client.SendAsync(Encoding.GetBytes(data));
  266. }
  267. /// <summary>
  268. /// 同步发送字节数组(不等待响应)。
  269. /// </summary>
  270. /// <param name="data">要发送的字节数组。</param>
  271. /// <exception cref="InvalidOperationException">当尚未连接时抛出。</exception>
  272. public void Send(byte[] data)
  273. {
  274. if (_client == null)
  275. throw new InvalidOperationException("Not connected");
  276. DataSent?.Invoke(this, Encoding.GetString(data));
  277. _client.Send(data);
  278. }
  279. /// <summary>
  280. /// 释放底层客户端资源。调用后实例不应再使用。
  281. /// </summary>
  282. public void Dispose()
  283. {
  284. _client?.Dispose();
  285. }
  286. }
  287. }