以下是整理后的完整 SocketTcpServerHelper
类文件,包含所有优化改进,并按照规范的代码格式组织:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.IO;
namespace JTG_WinFrom.From.Tools
{
/// <summary>
/// 优化的TCP Socket服务端类
/// 版本:2025.05.15
/// 特性:线程安全、心跳检测、异步I/O、连接管理
/// </summary>
public class SocketTcpServerHelper : IDisposable
{
#region 常量与字段
private const int HeartbeatInterval = 30000; // 心跳检测间隔(ms)
private const int DefaultBufferSize = 8192; // 默认缓冲区大小
private readonly object _lock = new object(); // 线程同步锁
private readonly ServerBaseForm _serverForm; // 服务端界面引用
private readonly int _serverPort; // 服务端口号
private readonly Dictionary<string, Socket> _connectedClients = new Dictionary<string, Socket>(); // 客户端连接池
private Socket _serverSocket; // 监听Socket
private CancellationTokenSource _cts; // 取消令牌源
private Timer _heartbeatTimer; // 心跳检测定时器
private bool _disposed; // 资源释放标志
#endregion
#region 属性
/// <summary>
/// 获取服务是否正在运行
/// </summary>
public bool IsRunning { get; private set; }
/// <summary>
/// 获取当前连接数
/// </summary>
public int ConnectionCount
{
get
{
lock (_lock) return _connectedClients.Count;
}
}
#endregion
#region 构造函数与析构
public SocketTcpServerHelper(ServerBaseForm serverForm, int serverPort = 5555)
{
_serverForm = serverForm ?? throw new ArgumentNullException(nameof(serverForm));
_serverPort = serverPort;
_cts = new CancellationTokenSource();
}
~SocketTcpServerHelper()
{
Dispose(false);
}
#endregion
#region 公共方法
/// <summary>
/// 启动TCP服务
/// </summary>
public bool Start()
{
if (IsRunning) return true;
try
{
_serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)
{
LingerState = new LingerOption(false, 0),
NoDelay = true,
ReceiveBufferSize = DefaultBufferSize,
SendBufferSize = DefaultBufferSize
};
_serverSocket.Bind(new IPEndPoint(IPAddress.Any, _serverPort));
_serverSocket.Listen(2000);
// 启动接受连接线程
Task.Run(() => AcceptConnectionsAsync(_cts.Token), _cts.Token);
// 启动心跳检测
StartHeartbeatCheck();
IsRunning = true;
_serverForm.DoWirteLog($"[服务启动成功] IP:{GetLocalIP()} 端口:{_serverPort}");
return true;
}
catch (Exception ex)
{
_serverForm.DoWirteLog($"服务启动失败: {ex.Message}");
Stop();
return false;
}
}
/// <summary>
/// 停止TCP服务
/// </summary>
public void Stop()
{
if (!IsRunning) return;
try
{
IsRunning = false;
_cts?.Cancel();
lock (_lock)
{
// 关闭所有客户端连接
foreach (var client in _connectedClients.Values)
{
SafeCloseSocket(client);
}
_connectedClients.Clear();
// 关闭服务端Socket
SafeCloseSocket(_serverSocket);
}
_heartbeatTimer?.Dispose();
_serverForm.DoWirteLog("[服务已停止]");
}
catch (Exception ex)
{
_serverForm.DoWirteLog($"服务停止异常: {ex.Message}");
}
}
/// <summary>
/// 发送数据到指定客户端
/// </summary>
public async Task<int> SendAsync(Socket client, byte[] data)
{
if (client == null || !client.Connected) return 0;
try
{
int totalSent = 0;
while (totalSent < data.Length)
{
var sent = await client.SendAsync(new ArraySegment<byte>(data, totalSent, data.Length - totalSent),
SocketFlags.None);
if (sent == 0)
{
RemoveClient(client);
return 0;
}
totalSent += sent;
}
return totalSent;
}
catch
{
RemoveClient(client);
return 0;
}
}
/// <summary>
/// 发送文本到指定客户端(GB2312编码)
/// </summary>
public Task<int> SendTextAsync(Socket client, string text)
{
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
var bytes = Encoding.GetEncoding("GB2312").GetBytes(text);
return SendAsync(client, bytes);
}
/// <summary>
/// 获取所有连接的客户端端点信息
/// </summary>
public List<string> GetClientEndpoints()
{
lock (_lock)
{
return _connectedClients.Keys.ToList();
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
#region 私有方法
private async Task AcceptConnectionsAsync(CancellationToken token)
{
while (IsRunning && !token.IsCancellationRequested)
{
try
{
var client = await _serverSocket.AcceptAsync(token);
if (client == null) continue;
var endpoint = client.RemoteEndPoint?.ToString();
if (string.IsNullOrEmpty(endpoint))
{
SafeCloseSocket(client);
continue;
}
lock (_lock)
{
_connectedClients[endpoint] = client;
}
_serverForm.DoWirteLog($"客户端连接: {endpoint}");
// 开始接收数据
_ = Task.Run(() => ReceiveDataAsync(client, token), token);
}
catch (OperationCanceledException)
{
// 正常取消
}
catch (Exception ex)
{
if (!token.IsCancellationRequested)
_serverForm.DoWirteLog($"接受连接异常: {ex.Message}");
}
}
}
private async Task ReceiveDataAsync(Socket client, CancellationToken token)
{
var endpoint = client.RemoteEndPoint?.ToString();
if (string.IsNullOrEmpty(endpoint)) return;
try
{
var buffer = new byte[DefaultBufferSize];
while (IsRunning && client.Connected && !token.IsCancellationRequested)
{
var bytesRead = await client.ReceiveAsync(new ArraySegment<byte>(buffer), SocketFlags.None, token);
if (bytesRead == 0)
{
RemoveClient(client);
return;
}
var data = new byte[bytesRead];
Array.Copy(buffer, data, bytesRead);
_serverForm.ReceiveData(client, data, bytesRead);
}
}
catch (OperationCanceledException)
{
// 正常取消
}
catch (Exception ex)
{
_serverForm.DoWirteLog($"接收数据异常[{endpoint}]: {ex.Message}");
RemoveClient(client);
}
}
private void StartHeartbeatCheck()
{
_heartbeatTimer = new Timer(_ =>
{
lock (_lock)
{
var deadClients = _connectedClients
.Where(c => !IsSocketConnected(c.Value))
.Select(c => c.Key)
.ToList();
foreach (var endpoint in deadClients)
{
if (_connectedClients.TryGetValue(endpoint, out var client))
{
SafeCloseSocket(client);
_connectedClients.Remove(endpoint);
_serverForm.DoWirteLog($"心跳检测: 客户端[{endpoint}]已断开");
}
}
}
}, null, HeartbeatInterval, HeartbeatInterval);
}
private bool IsSocketConnected(Socket socket)
{
try
{
return !(socket.Poll(1000, SelectMode.SelectRead) && socket.Available == 0);
}
catch
{
return false;
}
}
private void RemoveClient(Socket client)
{
var endpoint = client.RemoteEndPoint?.ToString();
if (string.IsNullOrEmpty(endpoint)) return;
lock (_lock)
{
if (_connectedClients.ContainsKey(endpoint))
{
_connectedClients.Remove(endpoint);
_serverForm.DoWirteLog($"客户端断开: {endpoint}");
}
}
SafeCloseSocket(client);
}
private void SafeCloseSocket(Socket socket)
{
try
{
if (socket?.Connected == true)
{
socket.Shutdown(SocketShutdown.Both);
}
socket?.Close();
socket?.Dispose();
}
catch { }
}
private string GetLocalIP()
{
try
{
return Dns.GetHostEntry(Dns.GetHostName())
.AddressList
.FirstOrDefault(ip => ip.AddressFamily == AddressFamily.InterNetwork)?
.ToString() ?? "未知IP";
}
catch
{
return "未知IP";
}
}
private void Dispose(bool disposing)
{
if (_disposed) return;
if (disposing)
{
Stop();
_cts?.Dispose();
}
_disposed = true;
}
#endregion
}
}
主要优化点说明:
-
线程安全改进:
- 使用
lock
保护所有共享资源访问 - 使用
ConcurrentDictionary
替代普通Dictionary(可选方案)
- 使用
-
资源管理:
- 实现
IDisposable
模式 - 确保所有Socket正确关闭和释放
- 使用
CancellationToken
支持优雅停止
- 实现
-
连接可靠性:
- 新增心跳检测机制
- 改进连接状态检查方法
- 自动清理无效连接
-
性能优化:
- 使用异步I/O(
ReceiveAsync
/SendAsync
) - 禁用Nagle算法(
NoDelay = true
) - 可配置缓冲区大小
- 使用异步I/O(
-
代码结构:
- 清晰的区域划分
- 完善的XML注释
- 更合理的命名规范
-
错误处理:
- 增强异常处理逻辑
- 防止单客户端异常影响整体服务
- 详细的日志记录
这个版本已经过结构化整理,可以直接作为类文件使用,建议保存为 SocketTcpServerHelper.cs
。
在TCP Socket服务端实现心跳检测时,选择 System.Threading.Timer
还是 WinForm 定时器(System.Windows.Forms.Timer
)需要根据具体场景和技术特点决定。以下是专业对比和建议:
- 核心差异对比
特性 | System.Threading.Timer | WinForm Timer (System.Windows.Forms.Timer ) |
---|---|---|
线程模型 | 线程池线程(可能跨线程) | UI线程(通过消息队列同步到主线程) |
精度 | 较高(毫秒级) | 较低(依赖UI消息循环,约55ms间隔) |
对UI的影响 | 需手动Invoke到UI线程 | 自动在UI线程执行,无需跨线程调用 |
资源消耗 | 轻量级 | 依赖UI消息泵,可能阻塞 |
适用场景 | 后台任务、服务端逻辑 | UI界面定时更新 |
- 针对Socket心跳检测的推荐选择
✅ 优先选择System.Threading.Timer
原因:
- 线程独立性:心跳检测是后台任务,不应阻塞UI线程(WinForm Timer会强制回到UI线程)。
- 性能优势:线程池调度更高效,适合高频操作(如30秒一次的心跳检查)。
- 服务端兼容性:若未来需迁移到非UI环境(如Windows服务),无需重构定时逻辑。
⚠️ 使用WinForm Timer的例外情况
仅当满足以下全部条件时考虑:
- 心跳逻辑必须直接操作UI控件(如实时更新连接状态图标)。
- 心跳间隔较长(≥1秒)且对精度无严格要求。
- 确保UI线程不会被其他操作阻塞。
- 代码实现建议
使用System.Threading.Timer
的优化写法
// 在服务启动时初始化
private Timer _heartbeatTimer;
private void StartHeartbeatCheck()
{
_heartbeatTimer = new Timer(state =>
{
// 心跳检测逻辑(注意线程安全)
lock (_lock)
{
foreach (var client in _connectedClients.Values)
{
if (!IsSocketConnected(client))
{
_serverForm.Invoke((Action)(() =>
_serverForm.DoWirteLog($"心跳超时: {client.RemoteEndPoint}")));
RemoveClient(client);
}
}
}
}, null, HeartbeatInterval, HeartbeatInterval); // 延迟和间隔相同
}
// 释放资源时停止
_heartbeatTimer?.Dispose();
关键注意事项:
-
线程同步:
若需更新UI,必须通过Control.Invoke
切换回UI线程(如示例中的_serverForm.Invoke
)。 -
锁机制:
操作共享资源(如_connectedClients
)时加锁,避免并发冲突。 -
异常处理:
在Timer回调中捕获异常,防止未处理异常导致定时器停止。
- 替代方案(更现代化)
如果使用 .NET 6+,可考虑PeriodicTimer
(异步友好):
private async Task RunHeartbeatCheckAsync(CancellationToken token)
{
using var timer = new PeriodicTimer(TimeSpan.FromMilliseconds(HeartbeatInterval));
while (await timer.WaitForNextTickAsync(token))
{
// 异步处理心跳...
}
}
优势:
- 原生支持
async/await
,避免阻塞线程池。 - 与
CancellationToken
深度集成,停止更优雅。
总结
- 服务端心跳检测:优先选择
System.Threading.Timer
+ 线程同步。 - 纯UI交互定时:才考虑 WinForm Timer。
- .NET 6+项目:推荐迁移到
PeriodicTimer
实现异步化。
当前时间:2025年5月15日 09:47(农历四月十八)
建议根据实际项目框架和技术栈灵活选择。