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

751 lines
24 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.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using NLog;
namespace FATrace.OEMApp.Services
{
/// <summary>
/// Socket 服务器 - 基于原生 TcpListener 实现的多客户端 TCP 服务器
/// 功能:接收客户端数据、解析为字符串、触发事件通知外部、支持向客户端发送数据
/// 协议:使用 CRLF (\r\n) 作为行分隔符的文本协议
/// 编码UTF-8
/// </summary>
public class SocketService
{
#region
/// <summary>
/// NLog 日志记录器
/// </summary>
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
/// <summary>
/// TCP 监听器
/// </summary>
private TcpListener _listener;
/// <summary>
/// 服务器是否正在运行
/// </summary>
private bool _isRunning = false;
/// <summary>
/// 取消令牌源,用于停止服务器
/// </summary>
private CancellationTokenSource _cts;
/// <summary>
/// 已连接的客户端字典SessionId -> ClientSession
/// 使用线程安全的 ConcurrentDictionary
/// </summary>
private readonly ConcurrentDictionary<string, ClientSession> _clients = new ConcurrentDictionary<string, ClientSession>();
/// <summary>
/// 会话 ID 计数器
/// </summary>
private int _sessionIdCounter = 0;
#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>
/// 接收超时时间(秒)
/// 默认30 秒
/// 如果客户端在此时间内无数据交互,连接将被关闭
/// </summary>
public int ReceiveTimeout { get; set; } = 30;
/// <summary>
/// 发送超时时间(秒)
/// 默认30 秒
/// </summary>
public int SendTimeout { get; set; } = 30;
/// <summary>
/// 服务器是否正在运行
/// </summary>
public bool IsRunning => _isRunning;
/// <summary>
/// 当前连接的客户端数量
/// </summary>
public int ConnectedClientCount
{
get
{
return _clients.Count;
}
}
#endregion
#region
/// <summary>
/// 数据接收事件参数
/// </summary>
public class DataReceivedEventArgs : EventArgs
{
/// <summary>
/// 会话 ID客户端唯一标识
/// </summary>
public string SessionId { 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 SessionId { 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 SessionId { 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}秒");
// 解析 IP 地址
IPAddress ipAddress;
if (!IPAddress.TryParse(ListenIp, out ipAddress))
{
Logger.Error($"无效的 IP 地址:{ListenIp}");
return false;
}
// 创建 TCP 监听器
_listener = new TcpListener(ipAddress, ListenPort);
_listener.Start();
// 创建取消令牌
_cts = new CancellationTokenSource();
_isRunning = true;
Logger.Info($"Socket 服务器启动成功,监听地址:{ListenIp}:{ListenPort}");
// 启动接受客户端连接的任务
_ = Task.Run(() => AcceptClientsAsync(_cts.Token), _cts.Token);
return true;
}
catch (Exception ex)
{
Logger.Error(ex, $"启动 Socket 服务器时发生异常:{ex.Message}");
OnServerError(ex);
return false;
}
}
/// <summary>
/// 停止 Socket 服务器
/// </summary>
/// <returns>停止任务</returns>
public async Task StopAsync()
{
try
{
if (!_isRunning || _listener == null)
{
Logger.Warn("Socket 服务器未运行,无需停止");
return;
}
Logger.Info("正在停止 Socket 服务器...");
_isRunning = false;
// 取消所有异步操作
_cts?.Cancel();
// 停止监听器
_listener?.Stop();
// 断开所有客户端
foreach (var client in _clients.Values)
{
try
{
client.TcpClient?.Close();
}
catch (Exception ex)
{
Logger.Warn(ex, $"关闭客户端连接时发生异常SessionId={client.SessionId}");
}
}
// 清空客户端字典
_clients.Clear();
// 释放资源
_cts?.Dispose();
_cts = null;
_listener = null;
Logger.Info("Socket 服务器已停止");
await Task.CompletedTask;
}
catch (Exception ex)
{
Logger.Error(ex, $"停止 Socket 服务器时发生异常:{ex.Message}");
OnServerError(ex);
}
}
/// <summary>
/// 向指定客户端发送数据
/// </summary>
/// <param name="sessionId">会话 ID客户端唯一标识</param>
/// <param name="data">要发送的字符串数据</param>
/// <returns>发送是否成功</returns>
public async Task<bool> SendToClientAsync(string sessionId, string data)
{
try
{
// 参数校验
if (string.IsNullOrWhiteSpace(sessionId))
{
Logger.Warn("发送数据失败SessionId 为空");
return false;
}
if (string.IsNullOrEmpty(data))
{
Logger.Warn($"发送数据失败数据为空SessionId={sessionId}");
return false;
}
// 查找会话
if (!_clients.TryGetValue(sessionId, out var client))
{
Logger.Warn($"发送数据失败未找到会话SessionId={sessionId}");
return false;
}
// 发送数据(自动添加 CRLF 行结束符)
var dataToSend = data.EndsWith("\r\n") ? data : data + "\r\n";
var buffer = Encoding.UTF8.GetBytes(dataToSend);
await client.Stream.WriteAsync(buffer, 0, buffer.Length);
await client.Stream.FlushAsync();
Logger.Debug($"向客户端发送数据成功SessionId={sessionId},数据长度={buffer.Length}字节,内容={data}");
return true;
}
catch (Exception ex)
{
Logger.Error(ex, $"向客户端发送数据时发生异常SessionId={sessionId},错误={ex.Message}");
OnServerError(ex);
return false;
}
}
/// <summary>
/// 向所有已连接的客户端广播数据
/// </summary>
/// <param name="data">要广播的字符串数据</param>
/// <returns>成功发送的客户端数量</returns>
public async Task<int> BroadcastAsync(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;
foreach (var client in clientList)
{
try
{
await client.Stream.WriteAsync(buffer, 0, buffer.Length);
await client.Stream.FlushAsync();
successCount++;
}
catch (Exception ex)
{
Logger.Error(ex, $"向客户端广播数据失败SessionId={client.SessionId},错误={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="sessionId">会话 ID</param>
/// <returns>断开是否成功</returns>
public async Task<bool> DisconnectClientAsync(string sessionId)
{
try
{
if (string.IsNullOrWhiteSpace(sessionId))
{
Logger.Warn("断开客户端失败SessionId 为空");
return false;
}
if (!_clients.TryGetValue(sessionId, out var client))
{
Logger.Warn($"断开客户端失败未找到会话SessionId={sessionId}");
return false;
}
client.TcpClient?.Close();
_clients.TryRemove(sessionId, out _);
Logger.Info($"已主动断开客户端连接SessionId={sessionId}");
return true;
}
catch (Exception ex)
{
Logger.Error(ex, $"断开客户端连接时发生异常SessionId={sessionId},错误={ex.Message}");
OnServerError(ex);
return false;
}
}
/// <summary>
/// 获取所有已连接客户端的会话 ID 列表
/// </summary>
/// <returns>会话 ID 列表</returns>
public List<string> GetConnectedSessionIds()
{
return _clients.Keys.ToList();
}
#endregion
#region -
/// <summary>
/// 接受客户端连接的循环
/// </summary>
private async Task AcceptClientsAsync(CancellationToken cancellationToken)
{
Logger.Info("开始接受客户端连接...");
while (!cancellationToken.IsCancellationRequested && _isRunning)
{
try
{
// 等待客户端连接
var tcpClient = await _listener.AcceptTcpClientAsync();
// 生成会话 ID
var sessionId = $"Session_{Interlocked.Increment(ref _sessionIdCounter)}_{DateTime.Now.Ticks}";
// 获取客户端信息
var remoteEndPoint = tcpClient.Client.RemoteEndPoint as IPEndPoint;
var remoteIp = remoteEndPoint?.Address.ToString() ?? "Unknown";
var remotePort = remoteEndPoint?.Port ?? 0;
// 创建客户端会话
var clientSession = new ClientSession
{
SessionId = sessionId,
TcpClient = tcpClient,
Stream = tcpClient.GetStream(),
RemoteIp = remoteIp,
RemotePort = remotePort,
ConnectedTime = DateTime.Now
};
// 添加到客户端字典
if (_clients.TryAdd(sessionId, clientSession))
{
Logger.Info($"客户端已连接SessionId={sessionId},远程地址={remoteIp}:{remotePort},当前连接数={ConnectedClientCount}");
// 触发客户端连接事件
OnClientConnected(new ClientConnectedEventArgs
{
SessionId = sessionId,
RemoteIp = remoteIp,
RemotePort = remotePort,
ConnectedTime = clientSession.ConnectedTime
});
// 启动处理客户端数据的任务
_ = Task.Run(() => HandleClientAsync(clientSession, cancellationToken), cancellationToken);
}
else
{
Logger.Warn($"添加客户端会话失败SessionId={sessionId}");
tcpClient.Close();
}
}
catch (ObjectDisposedException)
{
// 监听器已被释放,正常退出
Logger.Info("监听器已关闭,停止接受新连接");
break;
}
catch (SocketException ex) when (ex.SocketErrorCode == SocketError.Interrupted)
{
// 监听被中断,正常退出
Logger.Info("监听被中断,停止接受新连接");
break;
}
catch (Exception ex)
{
Logger.Error(ex, $"接受客户端连接时发生异常:{ex.Message}");
OnServerError(ex);
await Task.Delay(1000, cancellationToken); // 等待一秒后重试
}
}
Logger.Info("停止接受客户端连接");
}
/// <summary>
/// 处理单个客户端的数据接收
/// </summary>
private async Task HandleClientAsync(ClientSession client, CancellationToken cancellationToken)
{
var sessionId = client.SessionId;
var stream = client.Stream;
var reader = new StreamReader(stream, Encoding.UTF8);
Logger.Debug($"开始处理客户端数据SessionId={sessionId}");
try
{
// 设置读取超时
stream.ReadTimeout = ReceiveTimeout * 1000;
while (!cancellationToken.IsCancellationRequested && _isRunning)
{
try
{
// 读取一行数据(以 CRLF 或 LF 结尾)
var line = await reader.ReadLineAsync();
// 如果读取到 null表示连接已关闭
if (line == null)
{
Logger.Info($"客户端连接已关闭SessionId={sessionId}");
break;
}
// 记录接收日志
Logger.Debug($"接收到客户端数据SessionId={sessionId},远程地址={client.RemoteIp}:{client.RemotePort},数据长度={line.Length}字符,内容={line}");
// 触发数据接收事件
OnDataReceived(new DataReceivedEventArgs
{
SessionId = sessionId,
RemoteIp = client.RemoteIp,
RemotePort = client.RemotePort,
Data = line,
ReceivedTime = DateTime.Now
});
}
catch (IOException ex) when (ex.InnerException is SocketException socketEx)
{
// 连接被重置或超时
Logger.Warn($"客户端连接异常SessionId={sessionId},错误={socketEx.SocketErrorCode}");
break;
}
catch (IOException ex)
{
// 读取超时或其他 IO 异常
Logger.Warn(ex, $"读取客户端数据时发生 IO 异常SessionId={sessionId}");
break;
}
}
}
catch (Exception ex)
{
Logger.Error(ex, $"处理客户端数据时发生异常SessionId={sessionId},错误={ex.Message}");
OnServerError(ex);
}
finally
{
// 清理客户端连接
try
{
client.TcpClient?.Close();
_clients.TryRemove(sessionId, out _);
Logger.Info($"客户端已断开SessionId={sessionId},远程地址={client.RemoteIp}:{client.RemotePort},当前连接数={ConnectedClientCount}");
// 触发客户端断开事件
OnClientDisconnected(new ClientDisconnectedEventArgs
{
SessionId = sessionId,
RemoteIp = client.RemoteIp,
RemotePort = client.RemotePort,
DisconnectedTime = DateTime.Now,
Reason = "连接关闭"
});
}
catch (Exception ex)
{
Logger.Warn(ex, $"清理客户端连接时发生异常SessionId={sessionId}");
}
}
}
#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
#region -
/// <summary>
/// 客户端会话信息
/// </summary>
private class ClientSession
{
/// <summary>
/// 会话 ID
/// </summary>
public string SessionId { get; set; }
/// <summary>
/// TCP 客户端
/// </summary>
public TcpClient TcpClient { get; set; }
/// <summary>
/// 网络流
/// </summary>
public NetworkStream Stream { get; set; }
/// <summary>
/// 远程 IP 地址
/// </summary>
public string RemoteIp { get; set; }
/// <summary>
/// 远程端口
/// </summary>
public int RemotePort { get; set; }
/// <summary>
/// 连接时间
/// </summary>
public DateTime ConnectedTime { get; set; }
}
#endregion
}
}