整体调整了功能
This commit is contained in:
640
FATrace.OEMApp/Services/TouchSocketServer.cs
Normal file
640
FATrace.OEMApp/Services/TouchSocketServer.cs
Normal file
@@ -0,0 +1,640 @@
|
||||
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 * 64(64KB)
|
||||
/// </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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user