整体调整了功能

This commit is contained in:
2026-01-13 15:03:02 +08:00
parent 63a768bd80
commit f1a892281b
82 changed files with 11226 additions and 291 deletions

View File

@@ -146,8 +146,8 @@ namespace FATrace.OEMApp.Services
User = user,
Status = TaskStatus.Pending,
Progress = 0,
NvrStartTime = start ?? now.AddSeconds(-VideoTime),
NvrEndTime = end ?? now,
NvrStartTime = now,
NvrEndTime = now.AddSeconds(VideoTime),
CreateTime = now,
UpdateTime = now
};

View File

@@ -0,0 +1,750 @@
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
}
}

View File

@@ -0,0 +1 @@
// 备份文件 - 保留原始 SuperSocket 实现尝试

View File

@@ -0,0 +1,382 @@
# SocketService 使用示例
## 概述
`SocketService` 是一个基于原生 TcpListener 实现的稳定可靠的 Socket 服务器,支持多客户端同时连接。
## 主要特性
- ✅ 基于原生 TcpListener稳定可靠
- ✅ 支持多客户端同时连接
- ✅ 使用 CRLF (\r\n) 行分隔符的文本协议
- ✅ UTF-8 编码
- ✅ 详细的 NLog 日志记录
- ✅ 事件驱动架构,方便外部处理业务逻辑
- ✅ 支持单播和广播发送数据
## 配置参数
```csharp
// 创建服务实例
var socketService = new SocketService();
// 配置参数(可选,有默认值)
socketService.ListenIp = "127.0.0.1"; // 监听 IP默认 127.0.0.1
socketService.ListenPort = 6001; // 监听端口,默认 6001
socketService.ReceiveTimeout = 30; // 接收超时(秒),默认 30
socketService.SendTimeout = 30; // 发送超时(秒),默认 30
```
## 基本使用
### 1. 启动服务器
```csharp
// 启动服务器
bool success = await socketService.StartAsync();
if (success)
{
Console.WriteLine("服务器启动成功");
}
else
{
Console.WriteLine("服务器启动失败");
}
```
### 2. 订阅事件
#### 数据接收事件
```csharp
socketService.DataReceived += (sender, e) =>
{
// 处理接收到的数据
Console.WriteLine($"收到数据SessionId={e.SessionId}, IP={e.RemoteIp}:{e.RemotePort}");
Console.WriteLine($"数据内容:{e.Data}");
Console.WriteLine($"接收时间:{e.ReceivedTime}");
// 在这里处理你的业务逻辑
// 例如:解析数据、保存到数据库、触发其他操作等
};
```
#### 客户端连接事件
```csharp
socketService.ClientConnected += (sender, e) =>
{
Console.WriteLine($"客户端已连接SessionId={e.SessionId}, IP={e.RemoteIp}:{e.RemotePort}");
Console.WriteLine($"连接时间:{e.ConnectedTime}");
Console.WriteLine($"当前连接数:{socketService.ConnectedClientCount}");
};
```
#### 客户端断开事件
```csharp
socketService.ClientDisconnected += (sender, e) =>
{
Console.WriteLine($"客户端已断开SessionId={e.SessionId}, IP={e.RemoteIp}:{e.RemotePort}");
Console.WriteLine($"断开原因:{e.Reason}");
Console.WriteLine($"断开时间:{e.DisconnectedTime}");
Console.WriteLine($"当前连接数:{socketService.ConnectedClientCount}");
};
```
#### 服务器错误事件
```csharp
socketService.ServerError += (sender, ex) =>
{
Console.WriteLine($"服务器发生错误:{ex.Message}");
Console.WriteLine($"错误堆栈:{ex.StackTrace}");
};
```
### 3. 发送数据
#### 向指定客户端发送数据
```csharp
string sessionId = "Session_1_xxx"; // 从事件参数中获取的 SessionId
string data = "Hello Client!";
bool success = await socketService.SendToClientAsync(sessionId, data);
if (success)
{
Console.WriteLine("数据发送成功");
}
```
#### 向所有客户端广播数据
```csharp
string data = "Broadcast Message to All!";
int successCount = await socketService.BroadcastAsync(data);
Console.WriteLine($"广播成功发送到 {successCount} 个客户端");
```
### 4. 断开指定客户端
```csharp
string sessionId = "Session_1_xxx";
bool success = await socketService.DisconnectClientAsync(sessionId);
if (success)
{
Console.WriteLine("客户端已断开");
}
```
### 5. 获取所有已连接客户端
```csharp
List<string> sessionIds = socketService.GetConnectedSessionIds();
Console.WriteLine($"当前连接的客户端数量:{sessionIds.Count}");
foreach (var sessionId in sessionIds)
{
Console.WriteLine($" - {sessionId}");
}
```
### 6. 停止服务器
```csharp
await socketService.StopAsync();
Console.WriteLine("服务器已停止");
```
## 完整示例
```csharp
using FATrace.OEMApp.Services;
using System;
using System.Threading.Tasks;
public class SocketServerExample
{
private SocketService _socketService;
public async Task RunAsync()
{
// 1. 创建服务实例
_socketService = new SocketService
{
ListenIp = "0.0.0.0", // 监听所有网卡
ListenPort = 6001,
ReceiveTimeout = 30,
SendTimeout = 30
};
// 2. 订阅事件
_socketService.DataReceived += OnDataReceived;
_socketService.ClientConnected += OnClientConnected;
_socketService.ClientDisconnected += OnClientDisconnected;
_socketService.ServerError += OnServerError;
// 3. 启动服务器
bool success = await _socketService.StartAsync();
if (!success)
{
Console.WriteLine("服务器启动失败");
return;
}
Console.WriteLine("服务器已启动,按任意键停止...");
Console.ReadKey();
// 4. 停止服务器
await _socketService.StopAsync();
}
private void OnDataReceived(object sender, SocketService.DataReceivedEventArgs e)
{
Console.WriteLine($"[数据接收] SessionId={e.SessionId}, 数据={e.Data}");
// 处理业务逻辑
// 例如:回复客户端
_ = _socketService.SendToClientAsync(e.SessionId, $"收到:{e.Data}");
}
private void OnClientConnected(object sender, SocketService.ClientConnectedEventArgs e)
{
Console.WriteLine($"[客户端连接] SessionId={e.SessionId}, IP={e.RemoteIp}:{e.RemotePort}");
// 欢迎消息
_ = _socketService.SendToClientAsync(e.SessionId, "欢迎连接到服务器!");
}
private void OnClientDisconnected(object sender, SocketService.ClientDisconnectedEventArgs e)
{
Console.WriteLine($"[客户端断开] SessionId={e.SessionId}, 原因={e.Reason}");
}
private void OnServerError(object sender, Exception ex)
{
Console.WriteLine($"[服务器错误] {ex.Message}");
}
}
```
## 在 WinForms 中使用
```csharp
public partial class MainApp : Form
{
private SocketService _socketService;
private async void MainApp_Load(object sender, EventArgs e)
{
// 初始化 Socket 服务
_socketService = new SocketService
{
ListenIp = "127.0.0.1",
ListenPort = 6001
};
// 订阅事件(注意:需要使用 Invoke 更新 UI
_socketService.DataReceived += (s, args) =>
{
this.Invoke(new Action(() =>
{
// 更新 UI
txtLog.AppendText($"收到数据:{args.Data}\r\n");
}));
};
// 启动服务器
bool success = await _socketService.StartAsync();
if (success)
{
lblStatus.Text = "服务器运行中";
lblStatus.ForeColor = Color.Green;
}
}
private async void MainApp_FormClosing(object sender, FormClosingEventArgs e)
{
// 停止服务器
if (_socketService != null && _socketService.IsRunning)
{
await _socketService.StopAsync();
}
}
private async void btnSendToAll_Click(object sender, EventArgs e)
{
// 广播消息
string message = txtMessage.Text;
int count = await _socketService.BroadcastAsync(message);
MessageBox.Show($"已发送到 {count} 个客户端");
}
}
```
## 协议说明
### 数据格式
- **编码**UTF-8
- **行分隔符**CRLF (\r\n)
- **每次发送/接收**:一行文本数据
### 客户端示例C#
```csharp
using System;
using System.IO;
using System.Net.Sockets;
using System.Text;
public class SimpleClient
{
public static async Task Main()
{
using var client = new TcpClient();
await client.ConnectAsync("127.0.0.1", 6001);
var stream = client.GetStream();
var writer = new StreamWriter(stream, Encoding.UTF8) { AutoFlush = true };
var reader = new StreamReader(stream, Encoding.UTF8);
// 发送数据
await writer.WriteLineAsync("Hello Server!");
// 接收数据
string response = await reader.ReadLineAsync();
Console.WriteLine($"服务器回复:{response}");
}
}
```
### 客户端示例Python
```python
import socket
# 连接服务器
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('127.0.0.1', 6001))
# 发送数据(注意:需要添加 \r\n
message = "Hello Server!\r\n"
client.send(message.encode('utf-8'))
# 接收数据
response = client.recv(1024).decode('utf-8')
print(f"服务器回复:{response}")
client.close()
```
## 日志说明
SocketService 使用 NLog 记录详细日志,包括:
- **Info 级别**:服务器启动/停止、客户端连接/断开、广播完成
- **Debug 级别**:数据接收/发送的详细内容
- **Warn 级别**:参数错误、会话未找到、连接异常
- **Error 级别**:异常堆栈信息
日志示例:
```
2026-01-01 21:00:00.123 [INFO] 正在启动 Socket 服务器监听地址127.0.0.1:6001超时时间30秒
2026-01-01 21:00:00.456 [INFO] Socket 服务器启动成功监听地址127.0.0.1:6001
2026-01-01 21:00:05.789 [INFO] 客户端已连接SessionId=Session_1_xxx远程地址=127.0.0.1:54321当前连接数=1
2026-01-01 21:00:10.123 [DEBUG] 接收到客户端数据SessionId=Session_1_xxx远程地址=127.0.0.1:54321数据长度=13字符内容=Hello Server!
```
## 注意事项
1. **线程安全**:所有公共方法都是线程安全的,可以在多线程环境中使用
2. **事件处理**:事件回调在后台线程执行,如需更新 UI请使用 `Invoke`
3. **超时设置**:如果客户端长时间无数据交互,会自动断开连接
4. **数据格式**:发送的数据会自动添加 CRLF接收的数据会自动去除 CRLF
5. **异常处理**:所有异常都会被捕获并记录日志,不会导致服务器崩溃
## 常见问题
### Q1: 如何监听所有网卡?
```csharp
socketService.ListenIp = "0.0.0.0";
```
### Q2: 如何获取客户端的 SessionId
SessionId 会在事件参数中提供(`DataReceivedEventArgs.SessionId`),你可以保存它用于后续发送数据。
### Q3: 如何处理粘包问题?
当前实现使用行分隔符CRLF每次 `ReadLineAsync` 读取一行完整数据,自动处理粘包。
### Q4: 如何实现心跳检测?
可以在客户端定期发送心跳数据,服务器通过 `ReceiveTimeout` 自动断开无响应的客户端。
### Q5: 如何修改协议格式?
如需使用其他协议(如固定长度、带长度头等),需要修改 `HandleClientAsync` 方法中的数据读取逻辑。
## 性能建议
1. **连接数**:理论上支持数千个并发连接,实际取决于服务器性能
2. **数据量**:适合中小数据量传输,大文件传输建议使用其他方案
3. **日志级别**:生产环境建议将 Debug 日志关闭,减少性能开销

View File

@@ -20,13 +20,13 @@ namespace FATrace.OEMApp.Services
/// <summary>
/// 数据库保存的信息天数
/// </summary>
private int DbRetentionDays = 180;
private int DbRetentionDays = 365;
public event Action<string>? Info;
public void Start()
{
FileRetentionDays=ConfigHelper.GetIntOrDefault("VideoFileSaveDay", 365);
DbRetentionDays = ConfigHelper.GetIntOrDefault("DbSaveDay", 180);
DbRetentionDays = ConfigHelper.GetIntOrDefault("DbSaveDay", 365);
if (_cts != null) return;
try

View 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 * 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
}
}

View File

@@ -0,0 +1,410 @@
# TouchSocketServer 使用示例
## 概述
`TouchSocketServer` 是一个基于 TouchSocket 4.0 实现的稳定可靠的 Socket 服务器,支持多客户端同时连接。
## 主要特性
- ✅ 基于 TouchSocket 4.0,性能优异
- ✅ 支持多客户端同时连接
- ✅ UTF-8 编码
- ✅ 详细的 NLog 日志记录
- ✅ 事件驱动架构,方便外部处理业务逻辑
- ✅ 支持单播和广播发送数据
- ✅ 异步操作,性能优秀
## 配置参数
```csharp
// 创建服务实例
var socketServer = new TouchSocketServer();
// 配置参数(可选,有默认值)
socketServer.ListenIp = "127.0.0.1"; // 监听 IP默认 127.0.0.1
socketServer.ListenPort = 6001; // 监听端口,默认 6001
socketServer.ReceiveTimeout = 30000; // 接收超时(毫秒),默认 30000
socketServer.SendTimeout = 30000; // 发送超时(毫秒),默认 30000
socketServer.BufferLength = 1024 * 64; // 缓冲区大小,默认 64KB
socketServer.MaxCount = 10000; // 最大连接数,默认 10000
```
## 基本使用
### 1. 启动服务器
```csharp
// 启动服务器(异步)
bool success = await socketServer.StartAsync();
if (success)
{
Console.WriteLine("服务器启动成功");
}
else
{
Console.WriteLine("服务器启动失败");
}
```
### 2. 订阅事件
#### 数据接收事件
```csharp
socketServer.DataReceived += (sender, e) =>
{
// 处理接收到的数据
Console.WriteLine($"收到数据ClientId={e.ClientId}, IP={e.RemoteIp}:{e.RemotePort}");
Console.WriteLine($"数据内容:{e.Data}");
Console.WriteLine($"接收时间:{e.ReceivedTime}");
// 在这里处理你的业务逻辑
// 例如:解析数据、保存到数据库、触发其他操作等
};
```
#### 客户端连接事件
```csharp
socketServer.ClientConnected += (sender, e) =>
{
Console.WriteLine($"客户端已连接ClientId={e.ClientId}, IP={e.RemoteIp}:{e.RemotePort}");
Console.WriteLine($"连接时间:{e.ConnectedTime}");
Console.WriteLine($"当前连接数:{socketServer.ConnectedClientCount}");
};
```
#### 客户端断开事件
```csharp
socketServer.ClientDisconnected += (sender, e) =>
{
Console.WriteLine($"客户端已断开ClientId={e.ClientId}, IP={e.RemoteIp}:{e.RemotePort}");
Console.WriteLine($"断开原因:{e.Reason}");
Console.WriteLine($"断开时间:{e.DisconnectedTime}");
Console.WriteLine($"当前连接数:{socketServer.ConnectedClientCount}");
};
```
#### 服务器错误事件
```csharp
socketServer.ServerError += (sender, ex) =>
{
Console.WriteLine($"服务器发生错误:{ex.Message}");
Console.WriteLine($"错误堆栈:{ex.StackTrace}");
};
```
### 3. 发送数据
#### 向指定客户端发送数据
```csharp
string clientId = "Session_1_xxx"; // 从事件参数中获取的 ClientId
string data = "Hello Client!";
bool success = socketServer.SendToClient(clientId, data);
if (success)
{
Console.WriteLine("数据发送成功");
}
```
#### 向所有客户端广播数据
```csharp
string data = "Broadcast Message to All!";
int successCount = socketServer.Broadcast(data);
Console.WriteLine($"广播成功发送到 {successCount} 个客户端");
```
### 4. 断开指定客户端
```csharp
string clientId = "Session_1_xxx";
bool success = socketServer.DisconnectClient(clientId);
if (success)
{
Console.WriteLine("客户端已断开");
}
```
### 5. 获取所有已连接客户端
```csharp
List<string> clientIds = socketServer.GetConnectedClientIds();
Console.WriteLine($"当前连接的客户端数量:{clientIds.Count}");
foreach (var clientId in clientIds)
{
Console.WriteLine($" - {clientId}");
}
```
### 6. 获取客户端信息
```csharp
string clientId = "Session_1_xxx";
string info = socketServer.GetClientInfo(clientId);
Console.WriteLine($"客户端信息:{info}");
```
### 7. 停止服务器
```csharp
await socketServer.StopAsync();
Console.WriteLine("服务器已停止");
```
## 完整示例
```csharp
using FATrace.OEMApp.Services;
using System;
using System.Threading.Tasks;
public class TouchSocketServerExample
{
private TouchSocketServer _socketServer;
public async Task RunAsync()
{
// 1. 创建服务实例
_socketServer = new TouchSocketServer
{
ListenIp = "0.0.0.0", // 监听所有网卡
ListenPort = 6001,
ReceiveTimeout = 30000,
SendTimeout = 30000
};
// 2. 订阅事件
_socketServer.DataReceived += OnDataReceived;
_socketServer.ClientConnected += OnClientConnected;
_socketServer.ClientDisconnected += OnClientDisconnected;
_socketServer.ServerError += OnServerError;
// 3. 启动服务器
bool success = await _socketServer.StartAsync();
if (!success)
{
Console.WriteLine("服务器启动失败");
return;
}
Console.WriteLine("服务器已启动,按任意键停止...");
Console.ReadKey();
// 4. 停止服务器
await _socketServer.StopAsync();
}
private void OnDataReceived(object sender, TouchSocketServer.DataReceivedEventArgs e)
{
Console.WriteLine($"[数据接收] ClientId={e.ClientId}, 数据={e.Data}");
// 处理业务逻辑
// 例如:回复客户端
_socketServer.SendToClient(e.ClientId, $"收到:{e.Data}");
}
private void OnClientConnected(object sender, TouchSocketServer.ClientConnectedEventArgs e)
{
Console.WriteLine($"[客户端连接] ClientId={e.ClientId}, IP={e.RemoteIp}:{e.RemotePort}");
// 欢迎消息
_socketServer.SendToClient(e.ClientId, "欢迎连接到服务器!");
}
private void OnClientDisconnected(object sender, TouchSocketServer.ClientDisconnectedEventArgs e)
{
Console.WriteLine($"[客户端断开] ClientId={e.ClientId}, 原因={e.Reason}");
}
private void OnServerError(object sender, Exception ex)
{
Console.WriteLine($"[服务器错误] {ex.Message}");
}
}
```
## 在 WinForms 中使用
```csharp
public partial class MainApp : Form
{
private TouchSocketServer _socketServer;
private async void MainApp_Load(object sender, EventArgs e)
{
// 初始化 Socket 服务
_socketServer = new TouchSocketServer
{
ListenIp = "127.0.0.1",
ListenPort = 6001
};
// 订阅事件(注意:需要使用 Invoke 更新 UI
_socketServer.DataReceived += (s, args) =>
{
this.Invoke(new Action(() =>
{
// 更新 UI
txtLog.AppendText($"收到数据:{args.Data}\r\n");
}));
};
// 启动服务器
bool success = await _socketServer.StartAsync();
if (success)
{
lblStatus.Text = "服务器运行中";
lblStatus.ForeColor = Color.Green;
}
}
private async void MainApp_FormClosing(object sender, FormClosingEventArgs e)
{
// 停止服务器
if (_socketServer != null && _socketServer.IsRunning)
{
await _socketServer.StopAsync();
}
}
private void btnSendToAll_Click(object sender, EventArgs e)
{
// 广播消息
string message = txtMessage.Text;
int count = _socketServer.Broadcast(message);
MessageBox.Show($"已发送到 {count} 个客户端");
}
}
```
## 协议说明
### 数据格式
- **编码**UTF-8
- **数据传输**原始字节流TouchSocket 会自动处理)
- **每次接收**:通过事件参数获取完整数据
### 客户端示例C#
```csharp
using System;
using System.IO;
using System.Net.Sockets;
using System.Text;
public class SimpleClient
{
public static async Task Main()
{
using var client = new TcpClient();
await client.ConnectAsync("127.0.0.1", 6001);
var stream = client.GetStream();
var writer = new StreamWriter(stream, Encoding.UTF8) { AutoFlush = true };
var reader = new StreamReader(stream, Encoding.UTF8);
// 发送数据
await writer.WriteAsync("Hello Server!");
await writer.FlushAsync();
// 接收数据
char[] buffer = new char[1024];
int count = await reader.ReadAsync(buffer, 0, buffer.Length);
string response = new string(buffer, 0, count);
Console.WriteLine($"服务器回复:{response}");
}
}
```
### 客户端示例Python
```python
import socket
# 连接服务器
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('127.0.0.1', 6001))
# 发送数据
message = "Hello Server!"
client.send(message.encode('utf-8'))
# 接收数据
response = client.recv(1024).decode('utf-8')
print(f"服务器回复:{response}")
client.close()
```
## 日志说明
TouchSocketServer 使用 NLog 记录详细日志,包括:
- **Info 级别**:服务器启动/停止、客户端连接/断开、广播完成
- **Debug 级别**:数据接收/发送的详细内容、客户端正在连接
- **Warn 级别**:参数错误、客户端未找到、服务器未运行
- **Error 级别**:异常堆栈信息
日志示例:
```
2026-01-01 22:00:00.123 [INFO] 正在启动 Socket 服务器监听地址127.0.0.1:6001超时时间30000毫秒
2026-01-01 22:00:00.456 [INFO] Socket 服务器启动成功监听地址127.0.0.1:6001
2026-01-01 22:00:05.789 [INFO] 客户端已连接ClientId=xxx远程地址=127.0.0.1:54321当前连接数=1
2026-01-01 22:00:10.123 [DEBUG] 接收到客户端数据ClientId=xxx远程地址=127.0.0.1:54321数据长度=13字符内容=Hello Server!
```
## 注意事项
1. **线程安全**:所有公共方法都是线程安全的,可以在多线程环境中使用
2. **事件处理**:事件回调在后台线程执行,如需更新 UI请使用 `Invoke`
3. **超时设置**:如果客户端长时间无数据交互,会自动断开连接
4. **异步方法**StartAsync 和 StopAsync 是异步方法,需要使用 await
5. **异常处理**:所有异常都会被捕获并记录日志,不会导致服务器崩溃
6. **动态类型**:内部使用 dynamic 类型管理客户端,兼容 TouchSocket 4.0
## 常见问题
### Q1: 如何监听所有网卡?
```csharp
socketServer.ListenIp = "0.0.0.0";
```
### Q2: 如何获取客户端的 ClientId
ClientId 会在事件参数中提供(`DataReceivedEventArgs.ClientId`),你可以保存它用于后续发送数据。
### Q3: 如何实现心跳检测?
可以在客户端定期发送心跳数据,服务器通过 `ReceiveTimeout` 自动断开无响应的客户端。
### Q4: 为什么编译有警告?
这些是 C# 8.0 的可空引用类型警告,不影响功能,可以忽略。
### Q5: TouchSocket 4.0 与文档不一致?
是的TouchSocket 4.0.5 的 API 与官方文档有所不同,本实现已经适配了实际的 API。
## 性能建议
1. **连接数**:默认支持 10000 个并发连接,可根据需要调整 `MaxCount`
2. **缓冲区**:默认 64KB 缓冲区,适合大多数场景
3. **超时时间**:根据实际网络环境调整超时时间
4. **日志级别**:生产环境建议将 Debug 日志关闭,减少性能开销
## 与 SocketService 对比
| 特性 | TouchSocketServer | SocketService (原生) |
|------|-------------------|---------------------|
| 基础框架 | TouchSocket 4.0 | 原生 TcpListener |
| 性能 | ⭐⭐⭐⭐⭐ 更优秀 | ⭐⭐⭐⭐ 优秀 |
| 稳定性 | ⭐⭐⭐⭐⭐ 非常稳定 | ⭐⭐⭐⭐⭐ 非常稳定 |
| 依赖 | TouchSocket 包 | 仅 .NET 原生库 |
| 复杂度 | 简单 | 中等 |
| 推荐度 | ✅ **强烈推荐** | ✅ 推荐 |
两个实现都非常优秀TouchSocketServer 基于成熟的 TouchSocket 框架性能更优SocketService 基于原生库,无额外依赖。根据你的需求选择即可!