Files
FATrace/FATrace.OEMApp/Services/TouchSocketServer.cs
2026-01-13 15:03:02 +08:00

641 lines
21 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NLog;
using TouchSocket.Core;
using TouchSocket.Sockets;
namespace FATrace.OEMApp.Services
{
/// <summary>
/// TouchSocket 服务器 - 基于 TouchSocket 实现的多客户端 TCP 服务器
/// 功能:接收客户端数据、解析为字符串、触发事件通知外部、支持向客户端发送数据
/// 协议:使用 CRLF (\r\n) 作为行分隔符的文本协议
/// 编码UTF-8
/// </summary>
public class TouchSocketServer
{
/// <summary>
/// 构造函数
/// </summary>
/// <param name="listenIp"></param>
/// <param name="listenPort"></param>
/// <param name="receiveTimeout"></param>
/// <param name="sendTimeout"></param>
public TouchSocketServer(string listenIp, int listenPort, int receiveTimeout, int sendTimeout)
{
ListenIp = listenIp;
ListenPort = listenPort;
ReceiveTimeout = receiveTimeout;
SendTimeout = sendTimeout;
}
#region
/// <summary>
/// NLog 日志记录器
/// </summary>
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
/// <summary>
/// TouchSocket TCP 服务实例
/// </summary>
private TcpService _service;
/// <summary>
/// 服务器是否正在运行
/// </summary>
private bool _isRunning = false;
/// <summary>
/// 已连接的客户端字典ClientId -> dynamic
/// 用于快速查找和管理客户端
/// 使用 dynamic 类型以兼容 TouchSocket 4.0 的客户端类型
/// </summary>
private readonly ConcurrentDictionary<string, dynamic> _clients = new ConcurrentDictionary<string, dynamic>();
#endregion
#region
/// <summary>
/// 服务器监听 IP 地址
/// 默认127.0.0.1(本地回环)
/// 0.0.0.0 表示监听所有网卡
/// </summary>
public string ListenIp { get; set; } = "127.0.0.1";
/// <summary>
/// 服务器监听端口
/// 默认6001
/// </summary>
public int ListenPort { get; set; } = 6001;
/// <summary>
/// 接收超时时间(毫秒)
/// 默认30000 毫秒30秒
/// 如果客户端在此时间内无数据交互,连接将被关闭
/// </summary>
public int ReceiveTimeout { get; set; } = 30000;
/// <summary>
/// 发送超时时间(毫秒)
/// 默认30000 毫秒30秒
/// </summary>
public int SendTimeout { get; set; } = 30000;
/// <summary>
/// 缓冲区大小(字节)
/// 默认1024 * 6464KB
/// </summary>
public int BufferLength { get; set; } = 1024 * 64;
/// <summary>
/// 最大连接数
/// 默认10000
/// </summary>
public int MaxCount { get; set; } = 10000;
/// <summary>
/// 服务器是否正在运行
/// </summary>
public bool IsRunning => _isRunning;
/// <summary>
/// 当前连接的客户端数量
/// </summary>
public int ConnectedClientCount => _clients.Count;
#endregion
#region
/// <summary>
/// 数据接收事件参数
/// </summary>
public class DataReceivedEventArgs : EventArgs
{
/// <summary>
/// 客户端 ID唯一标识
/// </summary>
public string? ClientId { get; set; }
/// <summary>
/// 客户端 IP 地址
/// </summary>
public string? RemoteIp { get; set; }
/// <summary>
/// 客户端端口
/// </summary>
public int RemotePort { get; set; }
/// <summary>
/// 接收到的数据(已解析为字符串)
/// </summary>
public string? Data { get; set; }
/// <summary>
/// 接收时间
/// </summary>
public DateTime ReceivedTime { get; set; }
}
/// <summary>
/// 客户端连接事件参数
/// </summary>
public class ClientConnectedEventArgs : EventArgs
{
/// <summary>
/// 客户端 ID
/// </summary>
public string? ClientId { get; set; }
/// <summary>
/// 客户端 IP 地址
/// </summary>
public string? RemoteIp { get; set; }
/// <summary>
/// 客户端端口
/// </summary>
public int RemotePort { get; set; }
/// <summary>
/// 连接时间
/// </summary>
public DateTime ConnectedTime { get; set; }
}
/// <summary>
/// 客户端断开事件参数
/// </summary>
public class ClientDisconnectedEventArgs : EventArgs
{
/// <summary>
/// 客户端 ID
/// </summary>
public string? ClientId { get; set; }
/// <summary>
/// 客户端 IP 地址
/// </summary>
public string? RemoteIp { get; set; }
/// <summary>
/// 客户端端口
/// </summary>
public int RemotePort { get; set; }
/// <summary>
/// 断开时间
/// </summary>
public DateTime DisconnectedTime { get; set; }
/// <summary>
/// 断开原因
/// </summary>
public string? Reason { get; set; }
}
/// <summary>
/// 数据接收事件
/// 当接收到客户端数据时触发,外部可订阅此事件进行业务处理
/// </summary>
public event EventHandler<DataReceivedEventArgs> DataReceived;
/// <summary>
/// 客户端连接事件
/// 当有新客户端连接时触发
/// </summary>
public event EventHandler<ClientConnectedEventArgs> ClientConnected;
/// <summary>
/// 客户端断开事件
/// 当客户端断开连接时触发
/// </summary>
public event EventHandler<ClientDisconnectedEventArgs> ClientDisconnected;
/// <summary>
/// 服务器错误事件
/// 当服务器发生错误时触发
/// </summary>
public event EventHandler<Exception> ServerError;
#endregion
#region
/// <summary>
/// 启动 Socket 服务器
/// </summary>
/// <returns>启动是否成功</returns>
public async Task<bool> StartAsync()
{
try
{
// 检查是否已经在运行
if (_isRunning)
{
Logger.Warn("Socket 服务器已经在运行中,无需重复启动");
return true;
}
Logger.Info($"正在启动 Socket 服务器,监听地址:{ListenIp}:{ListenPort},超时时间:{ReceiveTimeout}毫秒");
// 创建 TouchSocket 服务实例
_service = new TcpService();
// 配置连接事件
_service.Connecting = (client, e) =>
{
Logger.Debug($"客户端正在连接:{client.Id},远程地址:{client.IP}:{client.Port}");
return EasyTask.CompletedTask;
};
// 配置连接成功事件
_service.Connected = (client, e) =>
{
try
{
// 添加到客户端字典
_clients.TryAdd(client.Id, client);
// 记录连接日志
Logger.Info($"客户端已连接ClientId={client.Id},远程地址={client.IP}:{client.Port},当前连接数={ConnectedClientCount}");
// 触发客户端连接事件
OnClientConnected(new ClientConnectedEventArgs
{
ClientId = client.Id,
RemoteIp = client.IP,
RemotePort = client.Port,
ConnectedTime = DateTime.Now
});
}
catch (Exception ex)
{
Logger.Error(ex, $"处理客户端连接时发生异常ClientId={client.Id},错误={ex.Message}");
OnServerError(ex);
}
return EasyTask.CompletedTask;
};
// 配置断开连接事件
_service.Closed = (client, e) =>
{
try
{
// 从客户端字典中移除
_clients.TryRemove(client.Id, out _);
// 记录断开日志
Logger.Info($"客户端已断开ClientId={client.Id},远程地址={client.IP}:{client.Port},原因={e.Message},当前连接数={ConnectedClientCount}");
// 触发客户端断开事件
OnClientDisconnected(new ClientDisconnectedEventArgs
{
ClientId = client.Id,
RemoteIp = client.IP,
RemotePort = client.Port,
DisconnectedTime = DateTime.Now,
Reason = e.Message
});
}
catch (Exception ex)
{
Logger.Error(ex, $"处理客户端断开时发生异常ClientId={client.Id},错误={ex.Message}");
OnServerError(ex);
}
return EasyTask.CompletedTask;
};
// 配置数据接收事件
_service.Received = (client, e) =>
{
try
{
// 解析数据为字符串
// TouchSocket 4.0 使用 e.Memory 获取数据
var data = Encoding.UTF8.GetString(e.Memory.Span);
// 记录接收日志
Logger.Debug($"接收到客户端数据ClientId={client.Id},远程地址={client.IP}:{client.Port},数据长度={data.Length}字符,内容={data}");
// 触发数据接收事件
OnDataReceived(new DataReceivedEventArgs
{
ClientId = client.Id,
RemoteIp = client.IP,
RemotePort = client.Port,
Data = data,
ReceivedTime = DateTime.Now
});
}
catch (Exception ex)
{
Logger.Error(ex, $"处理接收数据时发生异常ClientId={client.Id},错误={ex.Message}");
OnServerError(ex);
}
return EasyTask.CompletedTask;
};
// 配置服务器
var config = new TouchSocketConfig();
config.SetListenIPHosts($"tcp://{ListenIp}:{ListenPort}")
.SetMaxCount(MaxCount); // 设置最大连接数
// 应用配置并启动服务器
await _service.SetupAsync(config);
await _service.StartAsync();
_isRunning = true;
Logger.Info($"Socket 服务器启动成功,监听地址:{ListenIp}:{ListenPort}");
return true;
}
catch (Exception ex)
{
Logger.Error(ex, $"启动 Socket 服务器时发生异常:{ex.Message}");
OnServerError(ex);
return false;
}
}
/// <summary>
/// 停止 Socket 服务器
/// </summary>
public async Task StopAsync()
{
try
{
if (!_isRunning || _service == null)
{
Logger.Warn("Socket 服务器未运行,无需停止");
return;
}
Logger.Info("正在停止 Socket 服务器...");
_isRunning = false;
// 停止服务器
await _service?.StopAsync();
_service?.Dispose();
_service = null;
// 清空客户端字典
_clients.Clear();
Logger.Info("Socket 服务器已停止");
}
catch (Exception ex)
{
Logger.Error(ex, $"停止 Socket 服务器时发生异常:{ex.Message}");
OnServerError(ex);
}
}
/// <summary>
/// 向指定客户端发送数据
/// </summary>
/// <param name="clientId">客户端 ID唯一标识</param>
/// <param name="data">要发送的字符串数据</param>
/// <returns>发送是否成功</returns>
public bool SendToClient(string clientId, string data)
{
try
{
// 参数校验
if (string.IsNullOrWhiteSpace(clientId))
{
Logger.Warn("发送数据失败ClientId 为空");
return false;
}
if (string.IsNullOrEmpty(data))
{
Logger.Warn($"发送数据失败数据为空ClientId={clientId}");
return false;
}
if (_service == null || !_isRunning)
{
Logger.Warn($"发送数据失败服务器未运行或服务实例为空ClientId={clientId}");
return false;
}
// 可选:先检查是否存在该客户端,便于输出更友好的日志
//if (!_clients.ContainsKey(clientId))
//{
// Logger.Warn($"发送数据失败未找到客户端ClientId={clientId}");
// return false;
//}
// 发送数据(自动添加 CRLF 行结束符)
var dataToSend = data.EndsWith("\r\n") ? data : data + "\r\n";
var buffer = Encoding.UTF8.GetBytes(dataToSend);
// 使用 TcpService 的 SendAsync(id, buffer) 按会话 Id 发送
_service.SendAsync(clientId, buffer).GetAwaiter().GetResult();
Logger.Debug($"向客户端发送数据成功ClientId={clientId},数据长度={buffer.Length}字节,内容={data}");
return true;
}
catch (Exception ex)
{
Logger.Error(ex, $"向客户端发送数据时发生异常ClientId={clientId},错误={ex.Message}");
OnServerError(ex);
return false;
}
}
/// <summary>
/// 向所有已连接的客户端广播数据
/// </summary>
/// <param name="data">要广播的字符串数据</param>
/// <returns>成功发送的客户端数量</returns>
public int Broadcast(string data)
{
try
{
if (string.IsNullOrEmpty(data))
{
Logger.Warn("广播数据失败:数据为空");
return 0;
}
// 获取所有客户端
var clientList = _clients.Values.ToList();
if (clientList.Count == 0)
{
Logger.Debug("广播数据:当前无已连接的客户端");
return 0;
}
// 准备数据
var dataToSend = data.EndsWith("\r\n") ? data : data + "\r\n";
var buffer = Encoding.UTF8.GetBytes(dataToSend);
// 向所有客户端发送
int successCount = 0;
if (_service == null || !_isRunning)
{
Logger.Warn("广播数据失败:服务器未运行或服务实例为空");
return 0;
}
foreach (var client in clientList)
{
try
{
// 使用 TcpService.SendAsync 通过客户端 Id 发送
_service.SendAsync(client.Id, buffer).GetAwaiter().GetResult();
successCount++;
}
catch (Exception ex)
{
Logger.Error(ex, $"向客户端广播数据失败ClientId={client.Id},错误={ex.Message}");
}
}
Logger.Info($"广播数据完成,成功发送到 {successCount}/{clientList.Count} 个客户端,数据长度={buffer.Length}字节");
return successCount;
}
catch (Exception ex)
{
Logger.Error(ex, $"广播数据时发生异常:{ex.Message}");
OnServerError(ex);
return 0;
}
}
/// <summary>
/// 断开指定客户端的连接
/// </summary>
/// <param name="clientId">客户端 ID</param>
/// <returns>断开是否成功</returns>
public bool DisconnectClient(string clientId)
{
try
{
if (string.IsNullOrWhiteSpace(clientId))
{
Logger.Warn("断开客户端失败ClientId 为空");
return false;
}
if (!_clients.TryGetValue(clientId, out var client))
{
Logger.Warn($"断开客户端失败未找到客户端ClientId={clientId}");
return false;
}
client.Close("服务器主动断开");
Logger.Info($"已主动断开客户端连接ClientId={clientId}");
return true;
}
catch (Exception ex)
{
Logger.Error(ex, $"断开客户端连接时发生异常ClientId={clientId},错误={ex.Message}");
OnServerError(ex);
return false;
}
}
/// <summary>
/// 获取所有已连接客户端的 ID 列表
/// </summary>
/// <returns>客户端 ID 列表</returns>
public List<string> GetConnectedClientIds()
{
return _clients.Keys.ToList();
}
/// <summary>
/// 获取指定客户端的详细信息
/// </summary>
/// <param name="clientId">客户端 ID</param>
/// <returns>客户端信息字符串,如果未找到返回 null</returns>
public string GetClientInfo(string clientId)
{
if (_clients.TryGetValue(clientId, out var client))
{
return $"ClientId={client.Id}, IP={client.IP}:{client.Port}, 在线={client.Online}";
}
return null;
}
#endregion
#region -
/// <summary>
/// 触发数据接收事件
/// </summary>
private void OnDataReceived(DataReceivedEventArgs e)
{
try
{
DataReceived?.Invoke(this, e);
}
catch (Exception ex)
{
Logger.Error(ex, $"触发 DataReceived 事件时发生异常:{ex.Message}");
}
}
/// <summary>
/// 触发客户端连接事件
/// </summary>
private void OnClientConnected(ClientConnectedEventArgs e)
{
try
{
ClientConnected?.Invoke(this, e);
}
catch (Exception ex)
{
Logger.Error(ex, $"触发 ClientConnected 事件时发生异常:{ex.Message}");
}
}
/// <summary>
/// 触发客户端断开事件
/// </summary>
private void OnClientDisconnected(ClientDisconnectedEventArgs e)
{
try
{
ClientDisconnected?.Invoke(this, e);
}
catch (Exception ex)
{
Logger.Error(ex, $"触发 ClientDisconnected 事件时发生异常:{ex.Message}");
}
}
/// <summary>
/// 触发服务器错误事件
/// </summary>
private void OnServerError(Exception ex)
{
try
{
ServerError?.Invoke(this, ex);
}
catch (Exception eventEx)
{
Logger.Error(eventEx, $"触发 ServerError 事件时发生异常:{eventEx.Message}");
}
}
#endregion
}
}