TcpClientCommunication.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  1. using System;
  2. using System.Collections.Concurrent;
  3. using System.ComponentModel;
  4. using System.Net.Sockets;
  5. using System.Text;
  6. using System.Threading.Tasks;
  7. using Newtonsoft.Json;
  8. using TeamAAS.Communication.Attributes;
  9. using TeamAAS.Communication.Base;
  10. using TeamAAS.Communication.Interfaces;
  11. using TeamAAS.Communication.Enums;
  12. namespace TeamAAS.Communication.Devices
  13. {
  14. /// <summary>
  15. /// TCP 客户端(主动连接外部设备),纯透传。
  16. /// </summary>
  17. [Communication("TCP 客户端", "基础通讯", "TCP 主动连接,纯透传")]
  18. public class TcpClientCommunication : BindableCommunicationBase
  19. {
  20. [JsonIgnore]
  21. private TcpClient _tcpClient;
  22. [JsonIgnore]
  23. private NetworkStream _stream;
  24. private readonly ConcurrentQueue<string> _received = new ConcurrentQueue<string>();
  25. [JsonIgnore]
  26. private readonly StringBuilder _receiveBuffer = new StringBuilder();
  27. [JsonIgnore]
  28. private System.Threading.Timer _reconnectTimer;
  29. [JsonIgnore]
  30. private volatile bool _hasConnectedOnce;
  31. [JsonIgnore]
  32. private volatile bool _manualDisconnect;
  33. [JsonIgnore]
  34. private volatile bool _isReconnecting;
  35. private string _ipAddress = "127.0.0.1";
  36. [Category("I.客户端配置"), DisplayName("1.对端 IP 地址"), Description("对端 IP 地址")]
  37. public string IpAddress
  38. {
  39. get { return _ipAddress; }
  40. set { if (SetProperty(ref _ipAddress, value)) Notify(nameof(EndpointUrl)); }
  41. }
  42. private int _port = 5000;
  43. [Category("I.客户端配置"), DisplayName("2.对端端口"), Description("对端端口")]
  44. public int Port
  45. {
  46. get { return _port; }
  47. set { if (SetProperty(ref _port, value)) Notify(nameof(EndpointUrl)); }
  48. }
  49. private Terminator _terminator = Terminator.None;
  50. [Category("III.数据格式"), DisplayName("结束符"), Description("发送时自动附加、接收时自动去除的结束符")]
  51. public Terminator Terminator
  52. {
  53. get { return _terminator; }
  54. set { SetProperty(ref _terminator, value); }
  55. }
  56. private DataEncoding _dataEncoding = DataEncoding.Default;
  57. [Category("III.数据格式"), DisplayName("编码格式"), Description("收发数据的编码格式")]
  58. public DataEncoding DataEncoding
  59. {
  60. get { return _dataEncoding; }
  61. set { SetProperty(ref _dataEncoding, value); }
  62. }
  63. [Browsable(false)]
  64. public override string EndpointUrl
  65. {
  66. get { return $"tcp://{IpAddress}:{Port}"; }
  67. set
  68. {
  69. if (!string.IsNullOrWhiteSpace(value) && value.StartsWith("tcp://"))
  70. {
  71. var uri = new Uri(value);
  72. IpAddress = uri.Host;
  73. Port = uri.Port;
  74. }
  75. }
  76. }
  77. [JsonIgnore, Browsable(false)]
  78. public override bool IsConnected => (_tcpClient != null && _tcpClient.Connected);
  79. public override event Action<object, bool> ConnectChangedEvent;
  80. public override event Action<object, string> DataReceivedEvent;
  81. public override void Connect()
  82. {
  83. if (string.IsNullOrWhiteSpace(IpAddress))
  84. throw new InvalidOperationException("TCP 客户端 IP 不能为空。");
  85. if (Port <= 0)
  86. throw new InvalidOperationException("TCP 客户端端口必须大于 0。");
  87. Disconnect();
  88. _tcpClient = new TcpClient();
  89. _tcpClient.Connect(IpAddress, Port);
  90. _stream = _tcpClient.GetStream();
  91. _manualDisconnect = false;
  92. _hasConnectedOnce = true;
  93. StopReconnectTimer();
  94. _ = Task.Run(ReceiveLoop);
  95. ConnectChangedEvent?.Invoke(this, IsConnected);
  96. Notify(nameof(IsConnected));
  97. }
  98. public override Task ConnectAsync()
  99. {
  100. return Task.Run(() => Connect());
  101. }
  102. public override void Disconnect()
  103. {
  104. StopReconnectTimer();
  105. _manualDisconnect = true;
  106. if (_stream != null)
  107. {
  108. _stream.Dispose();
  109. _stream = null;
  110. }
  111. if (_tcpClient != null)
  112. {
  113. _tcpClient.Dispose();
  114. _tcpClient = null;
  115. }
  116. ConnectChangedEvent?.Invoke(this, false);
  117. Notify(nameof(IsConnected));
  118. }
  119. public override void Dispose()
  120. {
  121. Disconnect();
  122. }
  123. private async Task ReceiveLoop()
  124. {
  125. var buffer = new byte[4096];
  126. try
  127. {
  128. while (_stream != null && IsConnected)
  129. {
  130. int n = await _stream.ReadAsync(buffer, 0, buffer.Length);
  131. if (n <= 0) break;
  132. _receiveBuffer.Append(GetEncoding().GetString(buffer, 0, n));
  133. FlushReceivedMessages(_receiveBuffer);
  134. }
  135. }
  136. catch
  137. {
  138. }
  139. finally
  140. {
  141. if (_hasConnectedOnce && !_manualDisconnect)
  142. StartAutoReconnect();
  143. }
  144. }
  145. /// <summary>
  146. /// 获取编码
  147. /// </summary>
  148. public Encoding GetEncoding()
  149. {
  150. switch (DataEncoding)
  151. {
  152. case DataEncoding.ASCII: return Encoding.ASCII;
  153. case DataEncoding.UTF7: return Encoding.UTF7;
  154. case DataEncoding.UTF8: return Encoding.UTF8;
  155. case DataEncoding.UTF32: return Encoding.UTF32;
  156. case DataEncoding.Unicode: return Encoding.Unicode;
  157. case DataEncoding.BigEndianUnicode: return Encoding.BigEndianUnicode;
  158. case DataEncoding.GB2312: return Encoding.GetEncoding("gb2312");
  159. default: return Encoding.Default;
  160. }
  161. }
  162. /// <summary>
  163. /// 获取结束符字符串
  164. /// </summary>
  165. public string GetTerminatorString()
  166. {
  167. switch (Terminator)
  168. {
  169. case Terminator.CR: return "\r";
  170. case Terminator.LF: return "\n";
  171. case Terminator.CRLF: return "\r\n";
  172. case Terminator.None:
  173. default: return string.Empty;
  174. }
  175. }
  176. /// <summary>
  177. /// 去除数据尾部结束符
  178. /// </summary>
  179. public string TrimTerminator(string text)
  180. {
  181. if (string.IsNullOrEmpty(text)) return text;
  182. var term = GetTerminatorString();
  183. if (string.IsNullOrEmpty(term)) return text;
  184. return text.EndsWith(term) ? text.Substring(0, text.Length - term.Length) : text;
  185. }
  186. /// <summary>
  187. /// 按结束符分帧:收到完整结束符才触发接收事件;无结束符时直接触发
  188. /// </summary>
  189. private void FlushReceivedMessages(StringBuilder receiveBuffer)
  190. {
  191. var term = GetTerminatorString();
  192. if (string.IsNullOrEmpty(term))
  193. {
  194. if (receiveBuffer.Length > 0)
  195. {
  196. var text = receiveBuffer.ToString();
  197. receiveBuffer.Clear();
  198. _received.Enqueue(text);
  199. DataReceivedEvent?.Invoke(this, text);
  200. }
  201. return;
  202. }
  203. string content = receiveBuffer.ToString();
  204. int idx;
  205. while ((idx = content.IndexOf(term, StringComparison.Ordinal)) >= 0)
  206. {
  207. var msg = content.Substring(0, idx);
  208. content = content.Substring(idx + term.Length);
  209. _received.Enqueue(msg);
  210. DataReceivedEvent?.Invoke(this, msg);
  211. }
  212. receiveBuffer.Clear();
  213. receiveBuffer.Append(content);
  214. }
  215. /// <summary>
  216. /// 自动重连:初始化连接成功过,掉线后每5秒尝试连接一次
  217. /// </summary>
  218. private void StartAutoReconnect()
  219. {
  220. if (_reconnectTimer != null) return;
  221. if (_stream != null) { try { _stream.Dispose(); } catch { } _stream = null; }
  222. if (_tcpClient != null) { try { _tcpClient.Dispose(); } catch { } _tcpClient = null; }
  223. ConnectChangedEvent?.Invoke(this, false);
  224. Notify(nameof(IsConnected));
  225. _reconnectTimer = new System.Threading.Timer(ReconnectTimer_Elapsed, null, TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(5));
  226. }
  227. private void ReconnectTimer_Elapsed(object state)
  228. {
  229. if (_manualDisconnect) { StopReconnectTimer(); return; }
  230. if (_isReconnecting) return;
  231. _isReconnecting = true;
  232. try
  233. {
  234. if (_stream != null) { try { _stream.Dispose(); } catch { } _stream = null; }
  235. if (_tcpClient != null) { try { _tcpClient.Dispose(); } catch { } _tcpClient = null; }
  236. var client = new TcpClient();
  237. try
  238. {
  239. client.Connect(IpAddress, Port);
  240. _tcpClient = client;
  241. _stream = _tcpClient.GetStream();
  242. StopReconnectTimer();
  243. _ = Task.Run(ReceiveLoop);
  244. ConnectChangedEvent?.Invoke(this, true);
  245. Notify(nameof(IsConnected));
  246. }
  247. catch
  248. {
  249. try { client.Dispose(); } catch { }
  250. }
  251. }
  252. finally
  253. {
  254. _isReconnecting = false;
  255. }
  256. }
  257. private void StopReconnectTimer()
  258. {
  259. var t = _reconnectTimer;
  260. _reconnectTimer = null;
  261. if (t != null) { try { t.Dispose(); } catch { } }
  262. }
  263. public void Send(string text)
  264. {
  265. if (_stream == null)
  266. throw new InvalidOperationException("TCP 客户端未连接。");
  267. var data = GetEncoding().GetBytes((text ?? string.Empty) + GetTerminatorString());
  268. _stream.Write(data, 0, data.Length);
  269. }
  270. public override object ReadValue(string address)
  271. {
  272. _received.TryDequeue(out var text);
  273. return text;
  274. }
  275. public override Task<object> ReadValueAsync(string address)
  276. {
  277. return Task.FromResult(ReadValue(address));
  278. }
  279. public override void WriteValue(string address, object value)
  280. {
  281. Send(value?.ToString() ?? string.Empty);
  282. }
  283. public override Task WriteValueAsync(string address, object value)
  284. {
  285. Send(value?.ToString() ?? string.Empty);
  286. return Task.CompletedTask;
  287. }
  288. }
  289. }