解决MySql布尔型新旧版本兼容问题,采用枚举来表示布尔型的数据表。由正向工程赋值
|
# NetClient 网络客户端使用手册
## 目录
- [概述](/NewLife/X/Blob/v12/Doc/#概述)
- [架构设计](/NewLife/X/Blob/v12/Doc/#架构设计)
- [快速开始](/NewLife/X/Blob/v12/Doc/#快速开始)
- [属性å‚考](/NewLife/X/Blob/v12/Doc/#属性å‚考)
- [连接管ç†](/NewLife/X/Blob/v12/Doc/#连接管ç†)
- [æ•°æ®æ”¶å‘](/NewLife/X/Blob/v12/Doc/#æ•°æ®æ”¶å‘)
- [事件](/NewLife/X/Blob/v12/Doc/#事件)
- [æ–线é‡è¿ž](/NewLife/X/Blob/v12/Doc/#æ–线é‡è¿ž)
- [管é“ç¼–è§£ç ](/NewLife/X/Blob/v12/Doc/#管é“ç¼–è§£ç )
- [扩展与继承](/NewLife/X/Blob/v12/Doc/#扩展与继承)
- [扩展数æ®](/NewLife/X/Blob/v12/Doc/#扩展数æ®)
- [日志与追踪](/NewLife/X/Blob/v12/Doc/#日志与追踪)
- [常è§é—®é¢˜](/NewLife/X/Blob/v12/Doc/#常è§é—®é¢˜)
---
## 概述
`NetClient` 是对 `ISocketClient` çš„**应用层å°è£…**,与 `NetServer` é…对使用,æä¾›ä¸€è‡´çš„客户端-æœåŠ¡ç«¯é€šä¿¡ä½“éªŒã€‚
主è¦ç‰¹æ€§ï¼š
- **å议自动识别**:通过 `Server` 地å€å—符串(`tcp://`ã€`udp://`ã€`ws://`)自动创建对应的底层 Socket 客户端
- **逿˜Žæ–线é‡è¿ž**:连接æ„外æ–å¼€åŽè‡ªåЍé‡è¿žï¼Œå†…éƒ¨æ›¿æ¢ `ISocketClient` å®žä¾‹ï¼Œä¸Šå±‚ä¸šåŠ¡ä»£ç æ„ŸçŸ¥ä¸åˆ°åˆ‡æ¢è¿‡ç¨‹
- **å议编解ç **:通过 `Protocol` 属性设置å议编解ç 器(如 `SrmpCodec`ï¼‰ï¼Œè§£å†³ç²˜åŒ…ã€æ‹†åŒ…å’Œå议解æžé—®é¢˜
- **事件驱动接收**:订阅 `Received` 事件,异æ¥å¤„ç†åˆ°è¾¾çš„æ•°æ®æˆ–消æ¯
- **åŒæ¥ / 异æ¥åŒæ¨¡å¼**:`Open` / `OpenAsync`ã€`Send` / `SendMessageAsync` 覆盖全部场景
```csharp
// 典型用法
var client = new NetClient("tcp://127.0.0.1:8080");
client.Protocol = new SrmpCodec();
client.Received += (s, e) => XTrace.WriteLine("收到:{0}", e.Packet?.ToStr());
client.Open();
client.SendMessage(payload);
```
---
## 架构设计
```text
┌─────────────────────────────────────────────â”
│ NetClient │
│ Server / Remote → CreateClient() │
│ AutoReconnect Protocol │
│ Events: Opened / Closed / Received / Error │
└─────────────┬───────────────────────────────┘
│ æŒæœ‰ï¼ˆvolatile,æ–çº¿åŽæ›¿æ¢ï¼‰
â–¼
┌─────────────────────────────────────────────â”
│ ISocketClient │
│ TcpSession / UdpSession / WsSession │
└─────────────────────────────────────────────┘
```
**å‘逿µç¨‹**
```text
SendMessage(msg)
→ EnsureClient() // ç¡®ä¿å·²è¿žæŽ¥ï¼ŒAutoReconnect=false 时未连接直接抛出
→ å议构建整帧(Build)// ç¼–ç 为二进制(若设置了 Protocol)
→ ISocketClient.Send() // 底层å‘é€
```
**接收æµç¨‹**
```text
ISocketClient 收到数æ®
→ æ¶ˆæ¯æ³µå®šç•Œè§£ç (TryParse)
→ NetClient.OnClientReceived() // 事件转å‘
→ Received 事件 // 业务层处ç†
```
**æ–线é‡è¿žæµç¨‹**
```text
Closed / Error 事件触å‘
→ ScheduleReconnect() // 一次性 TimerX(Period=0)
→ DoReconnect() // 定时器回调
├── æˆåŠŸï¼šæ›¿æ¢ _client,_reconnectCount = 0
└── 失败:计数++ï¼Œå†æ¬¡ ScheduleReconnect()
```
---
## 快速开始
### 最简用法
```csharp
using NewLife.Net;
var client = new NetClient("tcp://127.0.0.1:8080");
if (client.Open())
{
client.Send("Hello World"u8);
client.Close("done");
}
```
### 事件驱动接收
```csharp
var client = new NetClient("tcp://127.0.0.1:8080");
client.Log = XTrace.Log;
client.Opened += (s, e) => XTrace.WriteLine("已连接");
client.Closed += (s, e) => XTrace.WriteLine("å·²æ–å¼€");
client.Received += (s, e) => XTrace.WriteLine("收到:{0} å—节", e.Packet?.Total);
client.Open();
```
### 使用管é“处ç†ç²˜åŒ…
```csharp
var client = new NetClient("tcp://127.0.0.1:8080");
client.Protocol = new SrmpCodec(); // æ ‡å‡†é•¿åº¦å¸§ç¼–è§£ç
client.Received += (s, e) =>
{
// e.Message 是管é“è§£ç åŽçš„æ¶ˆæ¯å¯¹è±¡
XTrace.WriteLine("消æ¯ï¼š{0}", e.Message);
};
client.Open();
client.SendMessage(myMessage);
```
### 请求-å“应模å¼
```csharp
var client = new NetClient("tcp://127.0.0.1:8080");
client.Protocol = new SrmpCodec();
await client.OpenAsync();
// SendMessageAsync ç‰å¾…管é“匹é…到对应å“应åŽè¿”回
var response = await client.SendMessageAsync(request);
XTrace.WriteLine("å“应:{0}", response);
```
---
## 属性å‚考
| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `Name` | `String` | ç±»å | æ ‡è¯†å称,用于日志输出 |
| `Server` | `String?` | `null` | æœåŠ¡ç«¯åœ°å€å—符串,自动解æžä¸º `Remote` |
| `Remote` | `NetUri?` | `null` | 远程地å€ï¼ˆåè®® + 主机 + 端å£ï¼‰ |
| `Client` | `ISocketClient?` | `null` | 当å‰åº•层 Socket 客户端,é‡è¿žåŽä¼šæ›¿æ¢ï¼Œ**ä¸å»ºè®®å¤–部长æŒå¼•用** |
| `Active` | `Boolean` | `false` | 当剿˜¯å¦å·²è¿žæŽ¥ |
| `Local` | `NetUri` | 空 | 本地绑定地å€ï¼Œé€šå¸¸æ— 需设置 |
| `Port` | `Int32` | `0` | 本地绑定端å£ï¼ˆ`Local.Port` å¿«æ·å±žæ€§ï¼‰ |
| `Timeout` | `Int32` | `3000` | 连接和读写超时(毫秒) |
| `AutoReconnect` | `Boolean` | `true` | 是å¦åœ¨æ„外æ–线åŽè‡ªåЍé‡è¿ž |
| `ReconnectDelay` | `Int32` | `5000` | 两次é‡è¿žä¹‹é—´çš„ç‰å¾…时间(毫秒) |
| `MaxReconnect` | `Int32` | `0` | 最大é‡è¿žæ¬¡æ•°ï¼Œ`0` è¡¨ç¤ºæ— é™é‡è¿ž |
| `Protocol` | `IMessageCodec?` | `null` | å议编解ç 器,éžç©ºå¯ç”¨å议模å¼ï¼ˆå®šç•Œ/构建,è§ã€Šæ¶ˆæ¯åè®®æ ˆã€‹ï¼‰ |
| `Tracer` | `ITracer?` | `null` | APM 追踪器 |
| `Log` | `ILog` | `Logger.Null` | 日志对象 |
| `LogPrefix` | `String` | `"{Name} "` | 日志行å‰ç¼€ |
| `Items` | `IDictionary<String, Object?>` | æ‡’åŠ è½½ | 扩展数æ®å—å…¸ |
---
## 连接管ç†
### Open / OpenAsync
```csharp
// åŒæ¥è¿žæŽ¥ï¼Œé€‚用于简å•场景
Boolean ok = client.Open();
// 异æ¥è¿žæŽ¥ï¼ŒæŽ¨è在 async 方法ä¸ä½¿ç”¨
Boolean ok = await client.OpenAsync(cancellationToken);
```
- 已连接时直接返回 `true`,ä¸é‡å¤å»ºç«‹è¿žæŽ¥
- ç½‘ç»œé”™è¯¯ï¼ˆå¦‚ç›®æ ‡ä¸å¯è¾¾ï¼‰è¿”回 `false`ï¼Œä¸æŠ›å‡ºå¼‚å¸¸
- `InvalidOperationException`(如未设置 `Remote`)**会抛出**,属于é…置错误,需修æ£ä»£ç
### Close / CloseAsync
```csharp
client.Close("用户主动æ–å¼€");
await client.CloseAsync("用户主动æ–å¼€", cancellationToken);
```
- 主动关é—会设置 `_userClosed = true`,阻æ¢è‡ªåЍé‡è¿ž
- å†…éƒ¨å…ˆå–æ¶ˆäº‹ä»¶è®¢é˜…(`Detach`),å†å…³é—底层 Socketï¼Œæœ€åŽæ‰‹åŠ¨è§¦å‘ `Closed` 事件
### Dispose
```csharp
client.Dispose(); // 或 using var client = new NetClient(...);
```
`Dispose` ä¼šåœæ¢é‡è¿žå®šæ—¶å™¨å¹¶é‡Šæ”¾åº•层 Socket,适åˆä¸å†ä½¿ç”¨çš„场景。
---
## æ•°æ®æ”¶å‘
### å‘é€
```csharp
// å‘é€å—节数组(全部)
client.Send(bytes);
// å‘é€å—节数组(部分)
client.Send(bytes, offset, count);
// å‘逿•°ç»„段
client.Send(new ArraySegment<Byte>(bytes, 0, len));
// é›¶æ‹·è´å‘é€ ReadOnlySpan(ä¸é€‚用于 async 上下文)
client.Send(span);
// å‘逿¶ˆæ¯å¯¹è±¡ï¼ˆç»ç®¡é“ç¼–ç )
client.SendMessage(myMessage);
// 异æ¥å‘é€å¹¶ç‰å¾…å“åº”ï¼ˆéœ€ç®¡é“æ”¯æŒè¯·æ±‚å“应匹é…)
var reply = await client.SendMessageAsync(myRequest);
```
> `SendMessageAsync` 仅在 `NETCOREAPP` 或 `NETSTANDARD2_1+` 下返回 `ValueTask`,其他平å°è¿”回 `Task`。
### 接收——事件驱动(推è)
```csharp
client.Received += (s, e) =>
{
var pkt = e.Packet; // 原始数æ®åŒ…(IOwnerPacket)
var msg = e.Message; // 管é“è§£ç åŽçš„æ¶ˆæ¯ï¼ˆæœªè®¾ç½®ç®¡é“时为 null)
XTrace.WriteLine("收到 {0} å—节", pkt?.Total);
};
```
### 接收——主动拉å–
```csharp
// åŒæ¥é˜»å¡žï¼ˆä¸æŽ¨è在异æ¥ç¨‹åºä¸ä½¿ç”¨ï¼‰
using var pkt = client.Receive();
// å¼‚æ¥æŽ¥æ”¶
using var pkt = await client.ReceiveAsync(cancellationToken);
```
---
## 事件
| 事件 | ç¾å | è§¦å‘æ—¶æœº |
|------|------|---------|
| `Opened` | `EventHandler` | 底层连接建立æˆåŠŸåŽ |
| `Closed` | `EventHandler` | 连接关é—åŽï¼ˆä¸»åЍ关闿ˆ–æ–线å‡è§¦å‘) |
| `Received` | `EventHandler<ReceivedEventArgs>` | æ”¶åˆ°æ•°æ®æˆ–管é“è§£ç å‡ºæ¶ˆæ¯æ—¶ |
| `Error` | `EventHandler<ExceptionEventArgs>` | å‘生错误或连接æ–开时 |
> 所有事件的 `sender` å‡ä¸º `NetClient` 实例本身,而éžåº•层 `ISocketClient`。
```csharp
client.Error += (s, e) =>
{
XTrace.WriteLine("错误 [{0}]:{1}", e.Action, e.Exception?.Message);
// e.Action å¯èƒ½ä¸º "Disconnect" / "Close" / "Receive" / "Send" ç‰
};
```
---
## æ–线é‡è¿ž
### é…ç½®
```csharp
client.AutoReconnect = true; // 默认 true,æ„外æ–线åŽè‡ªåЍé‡è¿ž
client.ReconnectDelay = 5_000; // æ¯æ¬¡é‡è¿žé—´éš” 5s(毫秒)
client.MaxReconnect = 10; // 最多é‡è¿ž 10 次,0 = æ— é™
```
### é‡è¿žæœºåˆ¶
```text
æ„外æ–线
├── OnClientClosed → ScheduleReconnect(若éžä¸»åЍ关é—)
└── OnClientError → ScheduleReconnect(Disconnect/Close/Receive 动作)
ScheduleReconnect:
1. 检查 AutoReconnect / Disposed / _userClosed(任一为å‡åˆ™è·³è¿‡ï¼‰
2. 检查 _reconnectTimer != null(已有挂起的定时器则跳过)
3. 检查 _reconnectCount >= MaxReconnect(超é™åˆ™åœæ¢ï¼Œä¸æ¸…零)
4. 创建一次性 TimerX(Period=0),延迟 ReconnectDelay 毫秒åŽè§¦å‘ DoReconnect
DoReconnect:
- æˆåŠŸï¼šæ›¿æ¢ _client,é‡ç½® _reconnectCount = 0
- 失败:ä¿ç•™è®¡æ•°ï¼Œå†æ¬¡è°ƒç”¨ ScheduleReconnect
```
- **主动调用 `Close()`**:设置 `_userClosed = true`,任何é‡è¿žè°ƒç”¨å‡è¢«æ‹¦æˆª
- **超过最大次数**ï¼šè®¡æ•°ä¸æ¸…零,åŽç» Closed/Error äº‹ä»¶è§¦å‘æ—¶ä»è¢«æ‹¦æˆª
### 监控é‡è¿žçжæ€
```csharp
client.Log = XTrace.Log; // å¯ç”¨æ—¥å¿—åŽï¼Œé‡è¿žè¿‡ç¨‹ä¼šè¾“出如:
// NetClient 连接æ–开,5000ms åŽå‘起第 1 次é‡è¿ž tcp://127.0.0.1:8080
// NetClient æ£åœ¨é‡è¿ž [1] tcp://127.0.0.1:8080
// NetClient é‡è¿žæˆåŠŸ tcp://127.0.0.1:8080
```
---
## å议编解ç
### å¯ç”¨å议模å¼
```csharp
// æ ‡å‡† SRMP å议(请求-å“应按åºåˆ—å·é…对)
client.Protocol = new SrmpCodec();
// é•¿åº¦å—æ®µåè®®ï¼ˆé€‚é… MQTT é£Žæ ¼ç‰è‡ªå®šä¹‰çº¿æ ¼å¼ï¼‰
client.Protocol = new LengthFieldCodec { Offset = 0, Size = 2 };
// 组åˆå˜æ¢å±‚(如压缩)——装饰器嵌套
client.Protocol = new CompressedCodec(new SrmpCodec());
```
### SrmpCodec
`SrmpCodec` 是 NewLife æ ‡å‡†å¸§ç¼–è§£ç 器(SRMPï¼‰ï¼ŒæŠ¥æ–‡æ ¼å¼ï¼š
```text
[1 Flag][1 Sequence][2 Length][负载数æ®] (负载 ≥ 0xFFFF 时使用 8 å—节扩展头)
```
与 `NetServer` + `SrmpCodec` é…åˆä½¿ç”¨å¯ç›´æŽ¥æ”¶å‘ä»»æ„长度消æ¯ï¼›`SendMessageAsync` 按åºåˆ—å·ç‰å¾…匹é…å“应。
### 自定义å议编解ç 器
实现 `IMessageCodec`(`TryParse` å®šç•Œæž„é€ / `Build` 整帧构建 / `BuildHeader` 头部构建)å³å¯æŽ¥å…¥ï¼Œå‚è§ã€Šæ¶ˆæ¯åè®®æ ˆã€‹Â§6 与 `SrmpCodec` 实现。
---
## 扩展与继承
通过é‡è½½ `CreateClient()` å¯å®šåˆ¶åº•层 Socket 客户端的行为(如 TLSã€KeepAlive ç‰ï¼‰ï¼š
```csharp
public class SslNetClient : NetClient
{
public SslNetClient(String server) : base(server) { }
protected override ISocketClient CreateClient()
{
var client = base.CreateClient();
// 在 base.CreateClient() 已完æˆåŸºç¡€åˆå§‹åŒ–åŽè¿½åŠ å®šåˆ¶
if (client is TcpSession tcp)
{
tcp.SslProtocol = SslProtocols.Tls12;
tcp.Certificate = LoadCert();
}
return client;
}
}
```
> `base.CreateClient()` 内部已完æˆï¼šè®¾ç½® `Name`ã€`Timeout`ã€`Log`ã€`Protocol`ã€`Tracer`ã€`Local` 并绑定事件监å¬ã€‚å类在调用 `base.CreateClient()` åŽåªéœ€è¿½åŠ ç‰¹å®šé…置。
---
## 扩展数æ®
`Items` / 索引器æä¾›çº¿ç¨‹å®‰å…¨çš„æ‰©å±•æ•°æ®é™„åŠ èƒ½åŠ›ï¼Œæ— éœ€ç»§æ‰¿å³å¯åœ¨ `NetClient` 实例上å˜å‚¨ä¸šåŠ¡ä¸Šä¸‹æ–‡ï¼š
```csharp
// å˜å…¥
client["userId"] = 42;
client["loginTime"] = DateTime.UtcNow;
client.Items["tag"] = "vip";
// 读å–
var id = (Int32?)client["userId"];
// æ‰¹é‡æ“作
foreach (var kv in client.Items)
XTrace.WriteLine("{0} = {1}", kv.Key, kv.Value);
```
底层使用 `ConcurrentDictionary<String, Object?>` æ‡’åŠ è½½ï¼Œæœªä½¿ç”¨å‰ä¸åˆ†é…内å˜ã€‚
---
## 日志与追踪
### 日志
```csharp
client.Log = XTrace.Log; // 输出到全局日志
client.LogPrefix = "MyClient "; // 自定义å‰ç¼€ï¼ˆé»˜è®¤ä¸º "{Name} ")
// 自定义日志输出(åç±»é‡è½½ï¼‰
public override void WriteLog(String format, params Object?[] args)
{
base.WriteLog("[{0}] " + format, new[] { Thread.CurrentThread.ManagedThreadId }.Concat(args).ToArray());
}
```
### APM 链路追踪
```csharp
// 接入 NewLife.Stardust 或其他 ITracer 实现
client.Tracer = DefaultTracer.Instance;
// NetClient å°† Tracer ä¼ é€’ç»™åº•å±‚ ISocketClient,由管é“自动创建追踪 Span
```
---
## 常è§é—®é¢˜
**Q:`Open()` 返回 `false` 时我该怎么办?**
A:`false` è¡¨ç¤ºç½‘ç»œå±‚è¿žæŽ¥å¤±è´¥ï¼ˆå¦‚ç›®æ ‡ä¸å¯è¾¾ã€ç«¯å£æœªå¼€æ”¾ï¼‰ã€‚若开å¯äº† `AutoReconnect`,åŽå°ä¼šè‡ªåЍé‡è¯•ï¼›å¦åˆ™è¯·æ£€æŸ¥æœåŠ¡ç«¯æ˜¯å¦æ£å¸¸ï¼Œå†æ‰‹åЍ釿–°è°ƒç”¨ `Open()`。
---
**Q:设置 `MaxReconnect` åŽï¼Œè¶…过次数是å¦å¯ä»¥æ‰‹åЍæ¢å¤ï¼Ÿ**
A:超过次数åŽï¼Œé‡è¿žè®¡æ•°ä¸ä¼šè‡ªåŠ¨æ¸…é›¶ã€‚è‹¥éœ€é‡æ–°å¯ç”¨é‡è¿žï¼Œè¯·è°ƒç”¨ `Close()` åŽå†è°ƒç”¨ `Open()`——`Open()` æˆåŠŸä¼šå°† `_reconnectCount` é‡ç½®ä¸º `0`(实际上 `Open` èµ° `DoReconnect` æˆåŠŸåˆ†æ”¯æ‰é‡ç½®ï¼›ç®€å•çš„é‡è¯•路径是先 `Close` å† `Open` 并在æˆåŠŸå»ºç«‹åŽè‡ªç„¶é‡ç½®è®¡æ•°ï¼‰ã€‚
---
**Q:为什么 `AutoReconnect=false` 时调用 `Send` 会直接抛出异常?**
A:`AutoReconnect=false` æ„味ç€è°ƒç”¨æ–¹è‡ªè¡Œç®¡ç†è¿žæŽ¥ç”Ÿå‘½å‘¨æœŸã€‚`EnsureClient()` å‘现客户端未连接时,ä¸ä¼šå°è¯•主动连接,直接抛出 `InvalidOperationException`,æç¤ºä¸Šå±‚调用 `Open()`。
---
**Q:æ–线é‡è¿žåŽï¼ŒåŽŸæ¥çš„事件订阅还有效å—?**
A:有效。`NetClient` çš„ `Opened`ã€`Closed`ã€`Received`ã€`Error` 事件订阅在 `NetClient` 层é¢ï¼Œä¸Žåº•层 `ISocketClient` å®žä¾‹æ— å…³ã€‚é‡è¿žæ—¶åº•层实例被替æ¢ï¼Œä½† `NetClient` 通过 `Attach` / `Detach` æœºåˆ¶å°†æ–°å®žä¾‹çš„äº‹ä»¶é‡æ–°æ˜ å°„ï¼Œä¸Šå±‚è®¢é˜…è€…æ— éœ€å…³æ³¨ã€‚
---
**Q:`Client` 属性å¯ä»¥é•¿æœŸæŒæœ‰å¼•用å—?**
A:**ä¸å»ºè®®**。æ–线é‡è¿žæ—¶ï¼Œ`NetClient` 内部会原ååœ°æ›¿æ¢ `_client` å—æ®µä¸ºæ–°çš„ `ISocketClient` å®žä¾‹ã€‚å¦‚æžœå¤–éƒ¨æŒæœ‰æ—§å®žä¾‹çš„引用,它将是一个已销æ¯çš„å¯¹è±¡ï¼Œç»§ç»æ“作会出错。推è始终通过 `NetClient` çš„æ–¹æ³•è¿›è¡Œæ”¶å‘æ“作。
---
**Qï¼šå¦‚ä½•åŒæ—¶è¿žæŽ¥å¤šä¸ªæœåŠ¡ç«¯ï¼Ÿ**
A:æ¯ä¸ªè¿žæŽ¥åˆ›å»ºç‹¬ç«‹çš„ `NetClient` 实例å³å¯ã€‚`NetClient` 是å•连接å°è£…,多连接场景下请管ç†å¥½å®žä¾‹ç”Ÿå‘½å‘¨æœŸï¼ˆæŽ¨è放入 `IDisposable` 容器或 `using` å—)。
---
*文档更新:2026年3月*
|