Files
zhaowei.huang a0dc1e6cd0 refactor: 插件 UI 从 WinForms 迁移到 CycleGUI,并修复代码质量问题
将 StandardScene 各插件的配置/监控窗体从 WinForms 迁移到 CycleGUI(删除 .Designer.cs/.resx,重写为 PanelBuilder 立即模式 UI,新增 CycleUiHelper 统一对话框)。

同时修复代码审核中的问题:
- 后台文件写入加锁 + try/catch(ButtonBoxManager / DoorManager,对齐 LoopViewer.SaveTasks 模式)
- CoderFieldsMetadata.cs 启用 #nullable enable,消除 CS8632 警告
- DummyCar 移除已废弃的 rightClickAction()/SetPosition()
- CarRemoteHelper.OpenVehicleWebPage 的 Process.Start 加 try/catch
- 重命名名不副实的 Mstsc()(现为打开网页)
- 统一弃元命名为 _
- TrafficInterlockViewer 改用稳定 Id(GUID)做选择/编辑,替代行索引
- csproj 改用 $(CGUILibDir) 解析 CycleGUI,绝对路径收敛到 Directory.Build.props

构建:dotnet build StandardScene.sln → 0 错误,30 警告(均为历史遗留)。
注:static 单例状态重构(审核第 8 项)暂未处理,留待单独任务。
2026-06-26 15:00:53 +08:00

501 lines
15 KiB
C#

using SimpleCore.Library;
using System;
using System.Diagnostics;
using System.Globalization;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Text;
using System.Threading;
namespace StandardScene.TCP
{
/// <summary>
/// 异步 TCP 客户端
/// </summary>
public class AsyncTcpClient : IDisposable
{
private sealed class DatagramReadState
{
public TcpClient Client { get; set; }
public byte[] Buffer { get; set; }
}
public event EventHandler<TcpDatagramReceivedEventArgs<byte[]>> DatagramReceived; // 接收到数据报文事件
public event EventHandler<TcpDatagramReceivedEventArgs<byte[]>> PlaintextReceived; // 接收到数据报文明文事件
public event EventHandler<TcpServerConnectedEventArgs> ServerConnected; // 与服务器的连接已建立事件
public event EventHandler<TcpServerDisconnectedEventArgs> ServerDisconnected; // 与服务器的连接已断开事件
public event EventHandler<TcpServerExceptionOccurredEventArgs> ServerExceptionOccurred; // 与服务器的连接发生异常事件
private TcpClient tcpClient;
private bool disposed = false;
private int retries = 0; // 重连计数
private readonly object _reconnectGate = new object();
private bool _closing = false;
private bool _isConnecting = false;
private bool _isReconnecting = false;
private Timer _reconnectTimer;
public AsyncTcpClient(IPAddress remoteIPAddress, int remotePort)
{
this.Addresses = remoteIPAddress;
this.Port = remotePort;
this.Encoding = Encoding.Default;
this.Retries = 3;
this.RetryInterval = 5;
}
/// <summary>
/// 是否已与服务器建立连接
/// </summary>
public bool Connected
{
get
{
try
{
return this.tcpClient != null && this.tcpClient.Connected;
}
catch
{
return false;
}
}
}
/// <summary>
/// 远端服务器的IP地址列表
/// </summary>
public IPAddress Addresses { get; private set; }
/// <summary>
/// 远端服务器的端口
/// </summary>
public int Port { get; private set; }
/// <summary>
/// 连接重试次数
/// </summary>
public int Retries { get; set; }
/// <summary>
/// 连接重试间隔
/// </summary>
public int RetryInterval { get; set; }
/// <summary>
/// 远端服务器终结点
/// </summary>
public IPEndPoint RemoteIPEndPoint
{
get
{
return new IPEndPoint(this.Addresses, this.Port);
}
}
/// <summary>
/// 通信所使用的编码
/// </summary>
public Encoding Encoding { get; set; }
uint on = 1;
/// <summary>
/// 连接到服务器
/// </summary>
/// <returns></returns>
public AsyncTcpClient Connect()
{
lock (_reconnectGate)
{
_closing = false;
}
if (this.Connected)
{
return this;
}
this.ConnectInternal(resetRetries: true);
return this;
}
private void ConnectInternal(bool resetRetries)
{
TcpClient client;
lock (_reconnectGate)
{
if (_closing || disposed)
{
return;
}
// Prevent multiple in-flight connection attempts.
if (_isConnecting || _isReconnecting)
{
return;
}
_isConnecting = true;
if (resetRetries)
{
retries = 0;
}
client = new TcpClient();
this.tcpClient = client;
}
try
{
client.Client.IOControl(IOControlCode.KeepAliveValues, KeepAlive(1, 500, 500), null);
client.BeginConnect(this.Addresses, this.Port, new AsyncCallback(this.HandleTcpServerConnected), client);
}
catch
{
try { client.Close(); } catch { }
lock (_reconnectGate)
{
_isConnecting = false;
}
if (!_closing && !disposed)
{
ScheduleReconnect("connect begin failed");
}
}
}
private void HandleRemoteDisconnect(TcpClient client, string reason)
{
if (!ReferenceEquals(client, this.tcpClient))
{
return;
}
try { client.Close(); } catch { }
this.RaiseServerDisconnected(this.Addresses, this.Port);
if (!_closing && !disposed)
{
ScheduleReconnect(reason);
}
}
private void ScheduleReconnect(string reason)
{
lock (_reconnectGate)
{
if (_closing || disposed)
{
return;
}
if (this.Connected)
{
return;
}
if (_isConnecting || _isReconnecting)
{
return;
}
// Keep reconnecting until the client is closed/disposed.
// Preserve the counter only for logging (avoid int overflow by wrapping).
if (retries == int.MaxValue)
{
retries = 0;
}
retries++;
_isReconnecting = true;
if (_reconnectTimer != null)
{
try { _reconnectTimer.Dispose(); } catch { }
_reconnectTimer = null;
}
Diagnosis.Post($"[AsyncTcpClient] schedule reconnect attempt {retries}/{this.Retries} in {this.RetryInterval}s. Reason={reason}");
Timer t = null;
t = new Timer(_ =>
{
try
{
lock (_reconnectGate)
{
_isReconnecting = false;
_reconnectTimer = null;
}
this.ConnectInternal(resetRetries: false);
}
catch { }
finally
{
try { t.Dispose(); } catch { }
}
}, null, TimeSpan.FromSeconds((double)this.RetryInterval), Timeout.InfiniteTimeSpan);
_reconnectTimer = t;
}
}
private byte[] KeepAlive(int onOff, int keepAliveTime, int keepAliveInterval)
{
byte[] buffer = new byte[12];
BitConverter.GetBytes(onOff).CopyTo(buffer, 0);
BitConverter.GetBytes(keepAliveTime).CopyTo(buffer, 4);
BitConverter.GetBytes(keepAliveInterval).CopyTo(buffer, 8);
return buffer;
}
/// <summary>
/// 关闭与服务器的连接
/// </summary>
/// <returns>异步TCP客户端</returns>
public AsyncTcpClient Close()
{
TcpClient clientToClose = null;
bool wasConnected = false;
lock (_reconnectGate)
{
_closing = true;
retries = 0;
_isConnecting = false;
_isReconnecting = false;
if (_reconnectTimer != null)
{
try { _reconnectTimer.Dispose(); } catch { }
_reconnectTimer = null;
}
clientToClose = this.tcpClient;
wasConnected = clientToClose != null && clientToClose.Connected;
this.tcpClient = null;
}
if (clientToClose != null)
{
try { clientToClose.Close(); } catch { }
}
if (wasConnected)
{
this.RaiseServerDisconnected(this.Addresses, this.Port);
}
return this;
}
private void HandleTcpServerConnected(IAsyncResult ar)
{
TcpClient client = (TcpClient)ar.AsyncState;
try
{
if (!ReferenceEquals(client, this.tcpClient))
{
// Stale connect callback for an old TcpClient instance.
try { client.Close(); } catch { }
return;
}
client.EndConnect(ar);
this.RaiseServerConnected(this.Addresses, this.Port);
lock (_reconnectGate)
{
this.retries = 0;
_isConnecting = false;
_isReconnecting = false;
if (_reconnectTimer != null)
{
try { _reconnectTimer.Dispose(); } catch { }
_reconnectTimer = null;
}
}
byte[] buffer = new byte[client.ReceiveBufferSize];
var state = new DatagramReadState { Client = client, Buffer = buffer };
client.GetStream().BeginRead(buffer, 0, buffer.Length, new AsyncCallback(this.HandleDatagramReceived), state);
}
catch (Exception ex)
{
if (!ReferenceEquals(client, this.tcpClient))
{
return;
}
lock (_reconnectGate)
{
_isConnecting = false;
}
if (!_closing && !disposed)
{
Diagnosis.Post($" HandleTcpServerConnected {this.Addresses} {this.Port} 连接断开,尝试重连...");
ScheduleReconnect("connect failed");
}
}
}
private void HandleDatagramReceived(IAsyncResult ar)
{
DatagramReadState state = null;
TcpClient client = null;
byte[] buffer = null;
try
{
state = (DatagramReadState)ar.AsyncState;
client = state.Client;
buffer = state.Buffer;
if (!ReferenceEquals(client, this.tcpClient))
{
// Stale read callback for an old TcpClient instance.
return;
}
NetworkStream stream = client.GetStream();
int numberOfReadBytes = 0;
try
{
numberOfReadBytes = stream.EndRead(ar);
}
catch
{
numberOfReadBytes = 0;
}
if (numberOfReadBytes == 0)
{
HandleRemoteDisconnect(client, "zero-byte read");
return;
}
byte[] receivedBytes = new byte[numberOfReadBytes];
Buffer.BlockCopy(buffer, 0, receivedBytes, 0, numberOfReadBytes);
this.RaiseDatagramReceived(client, receivedBytes);
this.RaisePlaintextReceived(client, receivedBytes);
// then start reading from the network again
stream.BeginRead(buffer, 0, buffer.Length, new AsyncCallback(this.HandleDatagramReceived), state);
}
catch (Exception ex)
{
Trace.WriteLine(ex.Message);
if (client != null && ReferenceEquals(client, this.tcpClient) && !_closing && !disposed)
{
HandleRemoteDisconnect(client, "read failed");
}
}
}
private void RaiseDatagramReceived(TcpClient sender, byte[] datagram)
{
if (this.DatagramReceived != null)
{
this.DatagramReceived(this, new TcpDatagramReceivedEventArgs<byte[]>(sender, datagram));
}
}
private void RaisePlaintextReceived(TcpClient sender, byte[] datagram)
{
if (this.PlaintextReceived != null)
{
//this.PlaintextReceived(this, new TcpDatagramReceivedEventArgs<string>(sender, this.Encoding.GetString(datagram, 0, datagram.Length)));
this.PlaintextReceived(this, new TcpDatagramReceivedEventArgs<byte[]>(sender, datagram));
}
}
private void RaiseServerConnected(IPAddress ipAddresses, int port)
{
if (this.ServerConnected != null)
{
this.ServerConnected(this, new TcpServerConnectedEventArgs(ipAddresses, port));
}
}
private void RaiseServerDisconnected(IPAddress ipAddresses, int port)
{
if (this.ServerDisconnected != null)
{
this.ServerDisconnected(this, new TcpServerDisconnectedEventArgs(ipAddresses, port));
}
}
private void RaiseServerExceptionOccurred(IPAddress ipAddresses, int port, Exception innerException)
{
if (this.ServerExceptionOccurred != null)
{
this.ServerExceptionOccurred(this, new TcpServerExceptionOccurredEventArgs(ipAddresses, port, innerException));
}
}
/// <summary>
/// 发送报文
/// </summary>
/// <param name="datagram"></param>
public void Send(byte[] datagram)
{
if (datagram == null)
{
throw new ArgumentNullException("datagram");
}
if (!this.Connected)
{
this.RaiseServerDisconnected(this.Addresses, this.Port);
throw new InvalidProgramException("This client has not connected to server.");
}
this.tcpClient.GetStream().BeginWrite(datagram, 0, datagram.Length, new AsyncCallback(this.HandleDatagramWritten), this.tcpClient);
}
private void HandleDatagramWritten(IAsyncResult ar)
{
((TcpClient)ar.AsyncState).GetStream().EndWrite(ar);
}
public void Send(string datagram)
{
this.Send(this.Encoding.GetBytes(datagram));
}
/// <summary>
/// 释放非托管资源
/// </summary>
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
this.disposed = true;
if (disposing)
{
try
{
this.Close();
}
catch// (SocketException ex)
{
}
}
}
}
}
}