Add XCode skills for entity caching, ORM, and sharding ETL
|
---
name: network-client
description: >
使用 NewLife.Net.NetClient 构建带自动é‡è¿žçš„通用网络客户端(TCP/UDP/WebSocket),
以åŠä½¿ç”¨ NewLife.Web.WebClientX 进行 HTTP 文件下载ã€ç½‘页抓å–å’Œç›®å½•å¼æ–‡ä»¶æŽ¢æµ‹ã€‚
适用于物è”ç½‘è®¾å¤‡è¿žæŽ¥ã€æœåŠ¡é—´é€šä¿¡ã€é…置文件拉å–和安装包自动更新ç‰åœºæ™¯ã€‚
argument-hint: >
è¯´æ˜Žä½ çš„å®¢æˆ·ç«¯åœºæ™¯ï¼šTCP/UDP/WebSocket 长连接(用 NetClient),
还是 HTTP 下载/抓å–/CDN(用 WebClientX)。
如需æ–线é‡è¿žï¼Œè¯´æ˜Ž AutoReconnect/ReconnectDelay/MaxReconnect é…ç½®æ„图。
---
# 网络客户端技能(NetClient + WebClientX)
## 适用场景
- `NetClient`:IoT 设备到æœåŠ¡ç«¯çš„é•¿è¿žæŽ¥ï¼›å¾®æœåС间 TCP/UDP 通信;WebSocket åŒå‘推é€å®¢æˆ·ç«¯ä¾§ï¼›éœ€è¦ç®¡é“ç¼–è§£ç (粘包/拆包处ç†ï¼‰çš„自定义å议。
- `WebClientX`:从远端拉å–é…置文件或安装包;按文件å通é…符探测目录并下载最新版本;CDN ç¾å鉴æƒä¸‹è½½ï¼›ä¸‹è½½åŽè‡ªåŠ¨è§£åŽ‹ã€‚
- 代ç 审查:确认 `AutoReconnect=true` æ—¶ `MaxReconnect` å·²è®¾ç½®é˜²æ¢æ— é™å¾ªçŽ¯ï¼›`Close()` 主动调用以阻æ¢ä¸å¿…è¦çš„é‡è¿žã€‚
## æ ¸å¿ƒåŽŸåˆ™
1. **`NetClient` 是 `ISocketClient` 的应用层å°è£…**:ä¸ç›´æŽ¥æŒæœ‰åº•层 Socket —— é‡è¿žæ—¶ `_client` 会被é™é»˜æ›¿æ¢ï¼Œ**ä¸è¦åœ¨å¤–éƒ¨é•¿æŒ `Client` 属性引用**。
2. **åè®®å—符串驱动选择**:`Server = "tcp://…"` / `"udp://…"` / `"ws://…"` è‡ªåŠ¨å†³å®šåº•å±‚å®žçŽ°ï¼Œæ— éœ€æ‰‹åŠ¨åˆ¤æ–。
3. **管é“先于 Open**:`Add<T>()` 必须在 `Open()` 之å‰è°ƒç”¨ï¼›`SendMessageAsync` ä¾èµ–管é“的请求-å“应匹é…ï¼Œæ— ç®¡é“æ—¶ä¸å¯ç”¨ã€‚
4. **主动关é—阻æ¢é‡è¿ž**:`client.Close("reason")` 设置 `_userClosed = true`,自动é‡è¿žè¢«æ‹¦æˆªï¼›`Dispose()` ä¹Ÿä¼šåœæ¢é‡è¿žè®¡æ—¶å™¨ã€‚
5. **`WebClientX` 原å写盘**:`DownloadFileAsync` 先写 `.tmp`,完æˆåŽé‡å‘½å,ä¸é€”å¤±è´¥ä¸æ±¡æŸ“ç›®æ ‡æ–‡ä»¶ã€‚
## 执行æ¥éª¤
### 一ã€NetClient — 长连接事件驱动
```csharp
using NewLife.Net;
using NewLife.Log;
var client = new NetClient("tcp://127.0.0.1:8080")
{
Log = XTrace.Log,
AutoReconnect = true,
ReconnectDelay = 5_000, // 5 ç§’é‡è¿žé—´éš”
MaxReconnect = 0, // æ— é™é‡è¿ž
Timeout = 3_000,
};
// 注册管é“(粘包处ç†ï¼Œå¿…须在 Open 之å‰ï¼‰
client.Add<StandardCodec>();
client.Opened += (s, e) => XTrace.WriteLine("已连接");
client.Closed += (s, e) => XTrace.WriteLine("å·²æ–开,ç‰å¾…é‡è¿ž");
client.Received += (s, e) =>
{
var msg = e.Message ?? e.Packet?.ToStr();
XTrace.WriteLine("收到:{0}", msg);
};
client.Error += (s, e) => XTrace.WriteLine("错误[{0}]:{1}", e.Action, e.Exception?.Message);
await client.OpenAsync();
// å‘é€å¹¶ç‰å¾…å“åº”ï¼ˆéœ€ç®¡é“æ”¯æŒ RequestReply)
var reply = await client.SendMessageAsync(request);
// ä¸»åŠ¨å…³é—æ—¶é˜»æ¢é‡è¿ž
client.Close("任务完æˆ");
client.Dispose();
```
### 二ã€NetClient — 请求å“应模å¼
```csharp
var client = new NetClient("tcp://127.0.0.1:8080");
client.Add<StandardCodec>();
await client.OpenAsync();
// ç‰å¾…匹é…å“应,直到 Timeout
var resp = await client.SendMessageAsync(myRequest);
XTrace.WriteLine("å“应:{0}", resp);
```
### 三ã€NetClient — UDP å•包模å¼
```csharp
var client = new NetClient("udp://127.0.0.1:9090");
// UDP æ— è¿žæŽ¥ï¼ŒOpen æˆåŠŸåŽç›´æŽ¥å‘é€
client.Open();
client.Send("ping"u8);
using var pkt = await client.ReceiveAsync();
XTrace.WriteLine("收到:{0}", pkt?.ToStr());
client.Close("done");
```
### å››ã€å±žæ€§é€ŸæŸ¥
| 属性 | 默认值 | 说明 |
|------|--------|------|
| `Server` | `null` | æœåŠ¡ç«¯åœ°å€ï¼ˆ`tcp://host:port`) |
| `Timeout` | `3000` | 连接与读写超时(毫秒) |
| `AutoReconnect` | `true` | æ„外æ–线åŽè‡ªåЍé‡è¿ž |
| `ReconnectDelay` | `5000` | é‡è¿žç‰å¾…间隔(毫秒) |
| `MaxReconnect` | `0` | 最大é‡è¿žæ¬¡æ•°ï¼Œ`0` = æ— é™ |
| `Active` | —— | 当剿˜¯å¦å·²è¿žæŽ¥ï¼ˆåªè¯»ï¼‰ |
| `Items` | æ‡’åŠ è½½ | 扩展数æ®å—å…¸ï¼ˆé™„åŠ ä¸šåŠ¡çŠ¶æ€ï¼‰ |
| `Tracer` | `null` | APM 追踪器 |
### 五ã€WebClientX — HTTP 下载
```csharp
using NewLife.Web;
var wc = new WebClientX
{
Timeout = 60_000,
UserAgent = "MyApp/1.0",
};
// 下载到ç£ç›˜ï¼ˆå…ˆ .tmp åŽåŽŸåé‡å‘½å)
await wc.DownloadFileAsync("https://cdn.example.com/data.zip", @"d:\downloads\data.zip");
// 下载文本(带编ç 自动识别)
var html = await wc.DownloadStringAsync("https://example.com/");
// 通用请求(GET/POST)
var json = await wc.SendAsync("https://api.example.com/status");
var resp = await wc.SendAsync("https://api.example.com/data", jsonBody, "POST");
```
### å…ã€WebClientX — 目录探测与最新版本下载
```csharp
var wc = new WebClientX { Timeout = 120_000 };
// è§£æžç›®å½•页所有 <a href> 链接
var links = wc.GetLinks("http://files.example.com/release/");
// 按通é…符找最新版 → 下载到本地目录
var localFile = wc.DownloadLink(
"http://files.example.com/release/",
"MyApp*.zip",
@"d:\downloads\");
// 下载并自动解压(zip / tar.gz / 7z)
wc.DownloadLinkAndExtract(
"http://files.example.com/release/",
"MyApp*.zip",
@"d:\app\");
```
### 七ã€WebClientX — CDN 鉴æƒ
```csharp
// 阿里云 CDN é‰´æƒ A 型:URL?auth_key=timestamp-rand-uid-md5hash
var wc = new WebClientX
{
AuthKey = Environment.GetEnvironmentVariable("CDN_AUTH_KEY"),
};
await wc.DownloadFileAsync(
"https://cdn.example.com/configs/appsettings.json",
@"d:\app\appsettings.json");
```
### å…«ã€WebClientX — 网页抓å–(编ç 自动识别)
```csharp
var wc = new WebClientX();
// GetHtml è‡ªåŠ¨è¯»å– Content-Type / <meta charset> å†³å®šè§£ç æ–¹å¼
var html = wc.GetHtml("https://legacy.example.com/gbk-page.html");
// GetLinksInDirectory 过滤父目录 ../ 链接,åªè¿”回å项
var files = wc.GetLinksInDirectory("http://mirror.example.com/release/");
```
## é‡ç‚¹æ£€æŸ¥é¡¹
- [ ] `Add<T>()` 是å¦åœ¨ `Open()` / `OpenAsync()` **之å‰**调用?(管é“必须先于连接注册)
- [ ] `MaxReconnect` 是å¦è®¾ç½®äº†åˆç†ä¸Šé™ï¼ˆ`0` ä¸ºæ— é™é‡è¿žï¼ŒåŽå°æœåŠ¡é€šå¸¸ `0` å…许,客户端工具建议设é™ï¼‰ï¼Ÿ
- [ ] `Dispose()` 是å¦åœ¨ä¸å†ä½¿ç”¨æ—¶è°ƒç”¨ï¼ˆé˜²æ¢é‡è¿žå®šæ—¶å™¨æ³„æ¼ï¼‰ï¼Ÿ
- [ ] `SendMessageAsync` 是å¦ä¾èµ–ç®¡é“ `RequestReply` 匹é…ï¼Ÿæ— ç®¡é“æ—¶è¯¥æ–¹æ³•行为未定义。
- [ ] `WebClientX.DownloadFileAsync` 是å¦å¤„ç†äº†å¼‚常(`.tmp` 文件会ä¿ç•™ï¼Œéœ€æ¸…ç†é€»è¾‘)?
- [ ] CDN `AuthKey` 是å¦é€šè¿‡çŽ¯å¢ƒå˜é‡æ³¨å…¥è€Œéžç¡¬ç¼–ç ?
## è¾“å‡ºè¦æ±‚
- **长连接**:`NetClient`(`NewLife.Net`)—— 逿˜Žé‡è¿žã€ç®¡é“ç¼–è§£ç ã€äº‹ä»¶é©±åŠ¨æŽ¥æ”¶ã€‚
- **HTTP 下载**:`WebClientX`(`NewLife.Web`)—— 原å写盘ã€CDN 鉴æƒã€ç›®å½•探测。
- **ç®¡é“æ³¨å†Œ**:`client.Add<StandardCodec>()` 必须在 `Open()` å‰è°ƒç”¨ã€‚
- **主动关é—**:`Close()` 阻æ¢é‡è¿žï¼›`Dispose()` 释放所有资æºã€‚
## å‚考资料
- `NewLife.Core/Net/NetClient.cs`
- `NewLife.Core/Web/WebClientX.cs`
- 相关技能:`network-server-sessions`(æœåŠ¡ç«¯ï¼‰ã€`http-client-loadbalancer`(多端点 HTTP 客户端)ã€`pipeline-handler-model`(管é“ç¼–è§£ç )
|