Files
FATrace/FATrace.OEMApp/Services/SocketService_使用示例.md
2026-01-13 15:03:02 +08:00

383 lines
11 KiB
Markdown
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.
# 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 日志关闭,减少性能开销