# 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 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 日志关闭,减少性能开销