mirror of
https://github.com/RRQM/TouchSocket.git
synced 2025-12-19 18:06:45 +08:00
文档:修复个别内容和实际版本不一致的问题
This commit is contained in:
@@ -122,11 +122,11 @@ Memory<byte> memory = byteBlock.TotalMemory;
|
||||
|
||||
`ByteBlock`的工作模式,就像是你向商店(`BytePool`)租借了一个杯子(`ByteBlock`),`TotalMemory`就是这个杯子本身。
|
||||
|
||||
于我们而言,只在乎杯中实际有多少水(`Span`、`Memory`),所以`ByteBlock.Len`就是水位线。所以无论何时,我们都不可以喝超过水位线的水。因为超过水位线的水,可能是上个租客留下的口水。
|
||||
于我们而言,只在乎杯中实际有多少水(`Span`、`Memory`),所以`byteBlock.Length`就是水位线。所以无论何时,我们都不可以喝超过水位线的水。因为超过水位线的水,可能是上个租客留下的口水。
|
||||
|
||||
同时,喝水的时候,还可以接力喝。比如先让张三(方法A)喝1口,那么我们可以把已喝的水做个标记(`Position`赋值为1),然后把杯子(`ByteBlock`)传递给李四(方法B),那么这时候,李四就只能按张三喝过的水继续喝,当然如果李四不嫌弃的话,也是可以从头再喝的。
|
||||
|
||||
最后就是杯子的归还(`Dispose`),以及杯中水的处理。在杯子未归还至前,杯子的所有属性(例如:`Len`、`Position`、`Span`、`Memory`)都代表的是当前的状态。而当杯子被归还时,这些属性将变得没有意义,尤其是`Memory`。因为`Memory`可以被其他对象引用,但是杯子归还后,`Memory`指向的内存块可能已经是新的申请人,这杯中的水,终究是变成了“前人的口水”。
|
||||
最后就是杯子的归还(`Dispose`),以及杯中水的处理。在杯子未归还至前,杯子的所有属性(例如:`Length`、`Position`、`Span`、`Memory`)都代表的是当前的状态。而当杯子被归还时,这些属性将变得没有意义,尤其是`Memory`。因为`Memory`可以被其他对象引用,但是杯子归还后,`Memory`指向的内存块可能已经是新的申请人,这杯中的水,终究是变成了“前人的口水”。
|
||||
|
||||
但是如果我们确实需要保存杯中水的话,可以使用`ToArray()`方法。其返回值是一个数组,它可以以新引用的方式保存,且与内存池没有任何关系了,所有的生命周期归`GC`管理。
|
||||
|
||||
@@ -144,7 +144,7 @@ using (var byteBlock = new ByteBlock())
|
||||
|
||||
byteBlock.SeekToStart();//将游标重置
|
||||
|
||||
var buffer = new byte[byteBlock.Len];//定义一个数组容器
|
||||
var buffer = new byte[byteBlock.Length];//定义一个数组容器
|
||||
var r = byteBlock.Read(buffer);//读取数据到容器,并返回读取的长度r
|
||||
}
|
||||
```
|
||||
|
||||
@@ -49,7 +49,7 @@ for (int bufferLength = 1; bufferLength < 1024 * 10; bufferLength += 1024)
|
||||
, bufferLength, (byteBlock, requestInfo) =>
|
||||
{
|
||||
//此处就是接收,如果是自定义适配器,可以将requestInfo强制转换为实际对象,然后判断数据的确定性
|
||||
if (byteBlock.Len!=5||(!byteBlock.ToArray().SequenceEqual(data)))
|
||||
if (byteBlock.Length!=5||(!byteBlock.ToArray().SequenceEqual(data)))
|
||||
{
|
||||
isSuccess = false;
|
||||
}
|
||||
|
||||
@@ -77,8 +77,8 @@ Dmtp像http和websocket一样,也是封装的应用层协议。它可以基于
|
||||
|
||||
### 4.1 连接注意事项
|
||||
|
||||
Dmtp拥有独立的连接机制,在连接前后会依次触发`IDmtpHandshakingPlugin`与`IDmtpHandshakedPlugin`插件。当完成连接时,其`IDmtpActor.IsHandshaked`方为`true`,此后才可以进行后续操作。
|
||||
Dmtp拥有独立的连接机制,在连接前后会依次触发`IDmtpHandshakingPlugin`与`IDmtpHandshakedPlugin`插件。当完成连接时,其`IDmtpActor.Online`方为`true`,此后才可以进行后续操作。
|
||||
|
||||
例如:Dmtp-Tcp组件,这是基于Tcp协议的Dmtp,因为它是继承实现,所以会拥有`Online`和`IsHandshaked`两个属性。其中Online仅仅表示已经建立Tcp连接,IsHandshaked才表示Dmtp是否完成握手。
|
||||
例如:Dmtp-Tcp组件,这是基于Tcp协议的Dmtp,因为它是继承实现,所以会拥有`Online`和`Online`两个属性。其中Online仅仅表示已经建立Tcp连接,IsHandshaked才表示Dmtp是否完成握手。
|
||||
|
||||
所以,在一些情况下,可能可能需要判断`IsHandshaked`完成,才能进行后续操作。
|
||||
所以,在一些情况下,可能可能需要判断`Online`完成,才能进行后续操作。
|
||||
@@ -622,8 +622,8 @@ internal sealed class DefaultSerializationSelector : ISerializationSelector
|
||||
return SerializeConvert.XmlDeserializeFromBytes(byteBlock.ReadBytesPackage(), parameterType);
|
||||
case (SerializationType)4:
|
||||
{
|
||||
var len = byteBlock.ReadInt32();
|
||||
var span = byteBlock.ReadToSpan(len);
|
||||
var Length = byteBlock.ReadInt32();
|
||||
var span = byteBlock.ReadToSpan(Length);
|
||||
return MemoryPackSerializer.Deserialize(parameterType, span);
|
||||
}
|
||||
default:
|
||||
@@ -876,7 +876,7 @@ Task task = Task.Run(() =>//这里必须用异步
|
||||
{
|
||||
foreach (var currentByteBlock in channel)
|
||||
{
|
||||
size += currentByteBlock.Len;//此处可以处理传递来的流数据
|
||||
size += currentByteBlock.Length;//此处可以处理传递来的流数据
|
||||
}
|
||||
status = channel.Status;//最后状态
|
||||
}
|
||||
@@ -910,7 +910,7 @@ public int RpcPushChannel(ICallContext callContext, int channelID)
|
||||
{
|
||||
foreach (var item in channel)
|
||||
{
|
||||
size += item.Len;//此处处理流数据
|
||||
size += item.Length;//此处处理流数据
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ title: 文件流池
|
||||
使用完成后,可以随时释放。
|
||||
|
||||
```csharp showLineNumbers
|
||||
int len = 0;
|
||||
int Length = 0;
|
||||
byte[] buffer = new byte[1024 * 1024];
|
||||
|
||||
using (var reader = FilePool.GetReader(path))
|
||||
@@ -31,11 +31,11 @@ using (var reader = FilePool.GetReader(path))
|
||||
{
|
||||
break;
|
||||
}
|
||||
len += r;
|
||||
Length += r;
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine(len);
|
||||
Console.WriteLine(Length);
|
||||
```
|
||||
|
||||
## 三、使用写
|
||||
|
||||
@@ -3,6 +3,8 @@ id: httpclient
|
||||
title: 创建HttpClient
|
||||
---
|
||||
|
||||
import CardLink from "@site/src/components/CardLink.js";
|
||||
|
||||
### 定义
|
||||
|
||||
命名空间:TouchSocket.Http <br/>
|
||||
@@ -172,4 +174,24 @@ using (var stream=File.OpenRead("TouchSocket.dll"))
|
||||
|
||||
:::
|
||||
|
||||
[本文示例Demo](https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/Http)
|
||||
## 六、传输文件
|
||||
|
||||
### 6.1 下载文件
|
||||
|
||||
```csharp {4} showLineNumbers
|
||||
//直接发起一个Get请求,然后写入到流中。
|
||||
using (var stream=File.Create("1.txt"))
|
||||
{
|
||||
await client.GetFileAsync("/WeatherForecast",stream);
|
||||
}
|
||||
```
|
||||
|
||||
### 6.2 上传文件
|
||||
|
||||
```csharp showLineNumbers
|
||||
await client.UploadFileAsync("/upfile", new FileInfo("filePath"));
|
||||
```
|
||||
|
||||
## 七、本文示例Demo
|
||||
|
||||
<CardLink link="https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/Http/HttpClientConsoleApp"/>
|
||||
@@ -5,6 +5,7 @@ title: 创建HttpService
|
||||
|
||||
import Tabs from "@theme/Tabs";
|
||||
import TabItem from "@theme/TabItem";
|
||||
import CardLink from "@site/src/components/CardLink.js";
|
||||
|
||||
### 定义
|
||||
|
||||
@@ -471,5 +472,7 @@ Https服务器,和http服务器几乎一样,只不过增加了一个Ssl的
|
||||
})
|
||||
```
|
||||
|
||||
## 十、本文示例Demo
|
||||
|
||||
[本文示例Demo](https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/Http)
|
||||
<CardLink link="https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/Http/HttpServiceConsoleApp"/>
|
||||
<CardLink link="https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/Http/HttpServiceForCorsConsoleApp"/>
|
||||
@@ -8,9 +8,29 @@ title: 静态页面插件
|
||||
命名空间:TouchSocket.Http <br/>
|
||||
程序集:[TouchSocket.Http.dll](https://www.nuget.org/packages/TouchSocket.Http)
|
||||
|
||||
## 静态网页托管插件仅服务器支持
|
||||
## 一、说明
|
||||
|
||||
**HttpStaticPagePlugin**静态网页托管插件,是用于Http的内容响应。
|
||||
静态页面功能是指`Web`服务器通过`HTTP`(超文本传输协议)或`HTTPS`(安全的超文本传输协议)向客户端(通常是浏览器)提供预定义的内容。这些内容通常包括HTML文件、CSS样式表、JavaScript脚本以及图片等多媒体资源。与动态页面不同,静态页面在服务器端不会进行任何处理或计算;它们是以文件形式存储在服务器上的,并且当用户请求时,服务器直接将这些文件发送给客户端。
|
||||
|
||||
## 二、静态页面的特点
|
||||
|
||||
1. **加载速度快**:由于服务器不需要执行任何脚本来生成页面内容,因此响应速度通常更快。
|
||||
2. **易于维护**:对于内容不经常变化的网站,使用静态页面可以减少维护成本,因为无需担心后端逻辑的更新和数据库管理等问题。
|
||||
3. **成本效益**:对于访问量较小的站点,使用静态页面可以节省服务器资源和带宽,降低运营成本。
|
||||
|
||||
## 三、静态页面的使用场景
|
||||
|
||||
- **个人博客**:如果博主主要发布文字内容,且更新频率不高,那么采用静态页面构建个人博客是一个不错的选择。
|
||||
- **企业介绍**:对于那些只需要展示公司信息、联系方式等基本内容的企业网站来说,静态页面能够满足需求同时保持简洁高效。
|
||||
- **项目展示**:艺术家、设计师等可以通过静态页面来创建个人作品集,以直观地向潜在客户展示自己的能力和风格。
|
||||
|
||||
总之,静态页面因其简单、快速和安全的特性,在特定的应用场景下具有明显优势。然而,对于需要频繁更新内容或实现复杂交互功能的网站,则可能更适合选择动态页面技术。
|
||||
|
||||
## 四、使用
|
||||
|
||||
### 4.1 常规使用
|
||||
|
||||
在创建`HttpService`实例后,只需要使用`UseHttpStaticPage`插件,然后指定根文件夹路径即可。
|
||||
|
||||
```csharp showLineNumbers
|
||||
var service = new HttpService();
|
||||
@@ -30,3 +50,67 @@ await service.StartAsync();
|
||||
|
||||
Console.WriteLine("Http服务器已启动");
|
||||
```
|
||||
|
||||
:::tip 提示
|
||||
|
||||
`UseHttpStaticPage`插件可以多次添加,也可以添加多个文件夹。
|
||||
|
||||
:::
|
||||
|
||||
### 4.2 请求资源定向
|
||||
|
||||
在使用`UseHttpStaticPage`插件时,可以指定请求资源定向。例如,当请求的`URL`为`/api`时,将重定向到`/api/index.html`。
|
||||
|
||||
```csharp {4-7} showLineNumbers
|
||||
a.UseHttpStaticPage()
|
||||
.SetNavigateAction(request =>
|
||||
{
|
||||
if (request.RelativeURL.EndsWith("/"))
|
||||
{
|
||||
return $"{request.RelativeURL}/index.html";
|
||||
}
|
||||
//此处可以设置重定向
|
||||
return request.RelativeURL;
|
||||
})
|
||||
.AddFolder("api/");
|
||||
```
|
||||
|
||||
:::info 信息
|
||||
|
||||
默认情况下,会将`/`、`/index`都定向到`/index.html`。
|
||||
|
||||
:::
|
||||
|
||||
### 4.3 响应配置
|
||||
|
||||
可以通过配置响应头,例如添加自定义头。
|
||||
|
||||
```csharp {2-5} showLineNumbers
|
||||
a.UseHttpStaticPage()
|
||||
.SetResponseAction(response =>
|
||||
{
|
||||
//可以设置响应头
|
||||
})
|
||||
.AddFolder("api/");
|
||||
```
|
||||
|
||||
### 4.4 配置ContentType
|
||||
|
||||
默认情况下,会将常见的文件后缀名映射到对应的`ContentType`。例如,`.html`文件会被映射为`text/html`。详情请见[ContentTypeMapper](https://gitee.com/RRQM_Home/TouchSocket/blob/master/src/TouchSocket.Http/StaticPage/FileExtensionContentTypeProvider.cs)。
|
||||
|
||||
但是,也可以通过配置`ContentTypeMapper`来覆盖默认的映射关系。
|
||||
|
||||
```csharp {2-5} showLineNumbers
|
||||
a.UseHttpStaticPage()
|
||||
.SetContentTypeProvider(mapper =>
|
||||
{
|
||||
mapper.Add(".txt", "text/plain");
|
||||
})
|
||||
.AddFolder("api/");
|
||||
```
|
||||
|
||||
:::info 信息
|
||||
|
||||
在一些浏览器中,可能会出现编码错误的问题,可以通过设置`ContentTypeMapper`来修正。例如,将`.txt`文件映射为`text/plain; charset=UTF-8`,这样就不会出现乱码了。
|
||||
|
||||
:::
|
||||
|
||||
@@ -38,7 +38,7 @@ adapter.ReceivedCallBack = (byteBlock, requestInfo) =>
|
||||
{
|
||||
//此处会回调接收的最终触发,例如:此处使用的固定包头,会解析4+n的数据为n。
|
||||
|
||||
if (byteBlock.Len == 4)
|
||||
if (byteBlock.Length == 4)
|
||||
{
|
||||
receivedCallBack = true;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ id: ipackage
|
||||
title: 包序列化模式
|
||||
---
|
||||
|
||||
import CardLink from "@site/src/components/CardLink.js";
|
||||
|
||||
### 定义
|
||||
|
||||
命名空间:TouchSocket.Core <br/>
|
||||
@@ -11,7 +13,7 @@ title: 包序列化模式
|
||||
|
||||
## 一、说明
|
||||
|
||||
包序列化模式是为了解决**极限序列化**的问题。常规序列化的瓶颈,主要是反射、表达式树、创建对象等几个方面,这几个问题在运行时阶段,都没有一个好的解决方案。目前在net6以后,微软大力支持源生成,这使得这类问题得到了很大程度的解决。但是对于老项目,或者无法使用net6和vs2022以上的项目,是无法使用的。所以,这时候包序列化模式就显得非常需要了。
|
||||
包序列化模式是为了解决**极限序列化**的问题。常规序列化的瓶颈,主要是反射、表达式树、创建对象等几个方面,这几个问题在运行时阶段,都没有一个好的解决方案。目前在`net6`以后,微软大力支持源生成,这使得这类问题得到了很大程度的解决。所以,这时候包序列化模式就显得非常需要了。
|
||||
|
||||
## 二、特点
|
||||
|
||||
@@ -23,9 +25,8 @@ title: 包序列化模式
|
||||
|
||||
### 2.2 缺点
|
||||
|
||||
1. 在源生成无法使用时,手动编写代码比较麻烦。
|
||||
2. 不支持跨语言。
|
||||
3. 类型版本兼容性比较差,简单来说就是高版本只能新增属性,不能删除属性,不能修改属性类型(如果类型长度一致,则可以修改类型,例如:`int` -> `float`)。
|
||||
1. 不支持跨语言。
|
||||
2. 类型版本兼容性比较差,简单来说就是高版本只能新增属性,不能删除属性,不能修改属性类型(如果类型长度一致,则可以修改类型,例如:`int` -> `float`)。
|
||||
|
||||
## 三、使用
|
||||
|
||||
@@ -33,7 +34,7 @@ title: 包序列化模式
|
||||
|
||||
例如:
|
||||
|
||||
下列类型MyClass,有一个Int类属性和一个string类属性。
|
||||
下列类型`MyClass`,有一个`int`类属性和一个`string`类属性。
|
||||
|
||||
```csharp showLineNumbers
|
||||
public class MyClass
|
||||
@@ -43,9 +44,9 @@ public class MyClass
|
||||
}
|
||||
```
|
||||
|
||||
我们可以使用包序列化模式,将MyClass序列化成二进制流,或者反序列化成MyClass。
|
||||
我们可以使用包序列化模式,将`MyClass`序列化成二进制流,或者反序列化成`MyClass`。
|
||||
|
||||
那么首先需要实现IPackage接口(或者继承PackageBase),然后依次将属性写入到ByteBlock中,或者从ByteBlock中读取属性。
|
||||
那么首先需要实现`IPackage`接口(或者继承`PackageBase`),然后依次将属性写入到`ByteBlock`中,或者从`ByteBlock`中读取属性。
|
||||
|
||||
```csharp {9-10,16-17} showLineNumbers
|
||||
public class MyClass:PackageBase
|
||||
@@ -71,9 +72,9 @@ public class MyClass:PackageBase
|
||||
|
||||
### 3.2 数组(列表)类型
|
||||
|
||||
对于数组、列表等类型,需要先判断是否为null,然后再写入有效值。
|
||||
对于数组、列表等类型,需要先判断是否为`null`,然后再写入有效值。
|
||||
|
||||
如果有效值是自定义类型,则也需要实现IPackage接口,然后依次写入。
|
||||
如果有效值是自定义类型,则也需要实现`IPackage`接口,然后依次写入。
|
||||
|
||||
```csharp {5,22} showLineNumbers
|
||||
public class MyArrayClass : PackageBase
|
||||
@@ -119,7 +120,7 @@ public class MyArrayClass : PackageBase
|
||||
|
||||
### 3.3 字典类型
|
||||
|
||||
字典类型基本上和数组类似,也是先判断是否为null,然后再写入有效值。
|
||||
字典类型基本上和数组类似,也是先判断是否为`null`,然后再写入有效值。
|
||||
|
||||
```csharp {5,23} showLineNumbers
|
||||
public class MyDictionaryClass : PackageBase
|
||||
@@ -172,7 +173,7 @@ public class MyDictionaryClass : PackageBase
|
||||
|
||||
### 4.1 使用内存块
|
||||
|
||||
使用内存块,使用ByteBlock类。
|
||||
使用内存块,使用`ByteBlock`类。
|
||||
|
||||
```csharp {12,20} showLineNumbers
|
||||
//声明内存大小。
|
||||
@@ -201,7 +202,7 @@ using (var byteBlock = new ByteBlock(1024 * 64))
|
||||
|
||||
### 4.2 使用值类型内存块
|
||||
|
||||
使用值类型内存块,使用ValueByteBlock类。
|
||||
使用值类型内存块,使用`ValueByteBlock`类。
|
||||
|
||||
```csharp {15,23} showLineNumbers
|
||||
//声明内存大小。
|
||||
@@ -238,10 +239,12 @@ finally
|
||||
|
||||
## 五、使用源生成
|
||||
|
||||
如果源生成可用(一般指vs2019最新版和vs2022,Rider),使用源代码生成方式,可以实现**自动**的打包和解包。
|
||||
如果源生成可用(一般指`vs2019`最新版和`vs2022`,`Rider`),使用源代码生成方式,可以实现**自动**的打包和解包。
|
||||
|
||||
例如上述类型,我们只需要使用`GeneratorPackage`特性标记即可。
|
||||
|
||||
### 5.1 生成特性
|
||||
|
||||
```csharp {5} showLineNumbers
|
||||
/// <summary>
|
||||
/// 使用源生成包序列化。
|
||||
@@ -357,13 +360,165 @@ namespace PackageConsoleApp
|
||||
|
||||
:::
|
||||
|
||||
## 五、性能评测
|
||||
## 六、源生成Member配置
|
||||
|
||||
### 6.1 成员可见性
|
||||
|
||||
默认情况下,使用源代码生成方式时,只会将公共成员(公共属性、private set属性和公共字段)进行打包和解包。如果需要对某些成员进行过滤,或者对私有成员进行强制,可以使用`PackageMember`特性进行配置。
|
||||
|
||||
【忽略成员】
|
||||
|
||||
```csharp {4} showLineNumbers
|
||||
[GeneratorPackage]
|
||||
internal partial class MyGeneratorPackage : PackageBase
|
||||
{
|
||||
[PackageMember(Behavior = PackageBehavior.Ignore)]
|
||||
public int P1 { get; set; }
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
【强制成员】
|
||||
|
||||
```csharp {5} showLineNumbers
|
||||
[GeneratorPackage]
|
||||
internal partial class MyGeneratorPackage : PackageBase
|
||||
{
|
||||
...
|
||||
[PackageMember(Behavior = PackageBehavior.Include)]
|
||||
private int P8;
|
||||
}
|
||||
```
|
||||
|
||||
### 6.2 成员顺序
|
||||
|
||||
`Package`的工作逻辑是读取成员,然后按照成员名**顺序**进行打包。
|
||||
|
||||
但是有时候希望可以自定义顺序,目的是为了更好的兼容性。
|
||||
|
||||
则可以先使用`PackageMember`特性对成员进行排序。
|
||||
|
||||
|
||||
```csharp {4,7,10} showLineNumbers
|
||||
[GeneratorPackage]
|
||||
internal partial class MyGeneratorIndexPackage : PackageBase
|
||||
{
|
||||
[PackageMember(Index = 2)]
|
||||
public int P1 { get; private set; }
|
||||
|
||||
[PackageMember(Index = 0)]
|
||||
public string P2 { get; set; }
|
||||
|
||||
[PackageMember(Index = 1)]
|
||||
public char P3 { get; set; }
|
||||
}
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>源生成的代码</summary>
|
||||
<div>
|
||||
|
||||
```csharp showLineNumbers
|
||||
/*
|
||||
此代码由SourceGenerator工具直接生成,非必要请不要修改此处代码
|
||||
*/
|
||||
#pragma warning disable
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using TouchSocket.Core;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PackageConsoleApp
|
||||
{
|
||||
partial class MyGeneratorIndexPackage
|
||||
{
|
||||
public override void Package<TByteBlock>(ref TByteBlock byteBlock)
|
||||
{
|
||||
byteBlock.WriteString(P2);
|
||||
byteBlock.WriteChar(P3);
|
||||
byteBlock.WriteInt32(P1);
|
||||
}
|
||||
|
||||
public override void Unpackage<TByteBlock>(ref TByteBlock byteBlock)
|
||||
{
|
||||
P2 = byteBlock.ReadString();
|
||||
P3 = byteBlock.ReadChar();
|
||||
P1 = byteBlock.ReadInt32();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</div>
|
||||
</details>
|
||||
|
||||
|
||||
### 6.3 自定义类型转换
|
||||
|
||||
`Package`在源生成打包时,是支持自定义类型的,但是要求是自定义的类型也必须实现`IPackage`接口。
|
||||
|
||||
但是,有时候,有些成员类型是已存在的第三方类型,所以就需要使用自定义转换器来实现。
|
||||
|
||||
例如:对于`Rectangle`类型,这是一个在`System.Drawing`中记录矩形的数据类型。
|
||||
|
||||
```csharp {4} showLineNumbers
|
||||
[GeneratorPackage]
|
||||
internal partial class MyGeneratorConvertPackage : PackageBase
|
||||
{
|
||||
public Rectangle P1 { get; set; }
|
||||
}
|
||||
```
|
||||
|
||||
默认情况下,是无法使用源生成打包的。
|
||||
|
||||
这时候就需要自定转换器。
|
||||
|
||||
首先,新建一个类,继承`FastBinaryConverter<T>`,指定泛型为`Rectangle`,然后实现`Read`和`Write`方法。
|
||||
|
||||
```csharp showLineNumbers
|
||||
class RectangleConverter : FastBinaryConverter<Rectangle>
|
||||
{
|
||||
protected override Rectangle Read<TByteBlock>(ref TByteBlock byteBlock, Type type)
|
||||
{
|
||||
var rectangle = new Rectangle(byteBlock.ReadInt32(), byteBlock.ReadInt32(), byteBlock.ReadInt32(), byteBlock.ReadInt32());
|
||||
return rectangle;
|
||||
}
|
||||
|
||||
protected override void Write<TByteBlock>(ref TByteBlock byteBlock, in Rectangle obj)
|
||||
{
|
||||
byteBlock.WriteInt32(obj.X);
|
||||
byteBlock.WriteInt32(obj.Y);
|
||||
byteBlock.WriteInt32(obj.Width);
|
||||
byteBlock.WriteInt32(obj.Height);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
:::info 信息
|
||||
|
||||
在实现时,不需要考虑对象为`null`的情况,无论是类还是结构体,都会自动判断。所以,触发到转换器时,一定是不为`null`。
|
||||
|
||||
:::
|
||||
|
||||
然后,在成员中,添加`[PackageMember(Converter =typeof(RectangleConverter))]`即可。
|
||||
|
||||
```csharp {4} showLineNumbers
|
||||
[GeneratorPackage]
|
||||
internal partial class MyGeneratorConvertPackage : PackageBase
|
||||
{
|
||||
[PackageMember(Converter =typeof(RectangleConverter))]
|
||||
public Rectangle P1 { get; set; }
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## 七、性能评测
|
||||
|
||||
基准测试表明:
|
||||
|
||||
包序列化模式比MemoryPack快30%。
|
||||
比json方式快了20倍多。
|
||||
比微软的json快了近10倍。
|
||||
包序列化模式比`MemoryPack`快30%。
|
||||
比`json`方式快了20倍多。
|
||||
比微软的`json`快了近10倍。
|
||||
比微软的二进制快了近100倍。
|
||||
|
||||
```csharp showLineNumbers
|
||||
@@ -377,3 +532,7 @@ namespace PackageConsoleApp
|
||||
| FastBinarySerialize | .NET 6.0 | .NET 6.0 | 7.531 ms | 0.0194 ms | 0.0162 ms | 4.57 | 0.03 | 578.1250 | - | 8.7 MB | 1.14 |
|
||||
| SystemBinarySerialize | .NET 6.0 | .NET 6.0 | 253.637 ms | 1.5709 ms | 1.3118 ms | 153.95 | 1.04 | 28000.0000 | 1000.0000 | 420.51 MB | 55.11 |
|
||||
```
|
||||
|
||||
## 八、本文示例Demo
|
||||
|
||||
<CardLink link="https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/Core/PackageConsoleApp"/>
|
||||
@@ -3,6 +3,8 @@ id: packageadapter
|
||||
title: 内置包适配器
|
||||
---
|
||||
|
||||
import CardLink from "@site/src/components/CardLink.js";
|
||||
|
||||
### 定义
|
||||
|
||||
命名空间:TouchSocket.Core <br/>
|
||||
@@ -19,6 +21,7 @@ title: 内置包适配器
|
||||
| FixedSizePackageAdapter | 固定长度数据处理适配器 |固定长度数据处理适配器是将发送的数据通过分割、填补的操作,以达到每次发送、接收的数据都是固定的长度来处理粘包、分包问题。这种方案一般适用于机械臂,机器人控制等场景。 |
|
||||
| TerminatorPackageAdapter | 终止因子数据处理适配器 |终止因子数据处理适配器是通过**特殊字符或数值**的方式,来达到处理粘包、分包的目的。可随意设置分割因子的值,以及编码方式。不仅如此,还有异常数据设置,在达到设定值时,如果还没有发现分割因子,则抛弃数据。其稳定性仅次于固定包头,且使用场景也比较广泛。|
|
||||
| PeriodPackageAdapter | 周期数据处理适配器 |周期数据处理适配器是通过**时间周期**的方式,来处理分包的目的(不包括粘包)。可处理任意数据。但是这也只是一定程度的处理。|
|
||||
| JsonPackageAdapter | Json格式数据处理适配器 |Json格式数据处理适配器,是一个非常不错的解决**纯Json字符串**粘、分包的方案,它能将符合Json标准的数据准确地分割出来。并且能把其中的杂质数据一起提取出来。|
|
||||
|
||||
|
||||
## 二、特点
|
||||
@@ -48,6 +51,13 @@ title: 内置包适配器
|
||||
2. 只能解决分包问题,无法解决粘包问题。
|
||||
3. 处理效率会有一定延迟。
|
||||
|
||||
### 2.5 Json格式数据处理适配器
|
||||
|
||||
1. 能够处理任意标准Json数据。
|
||||
2. 能够提取出信息中的杂质数据。
|
||||
3. 支持单个Object数据、或者Array数据。
|
||||
4. 支持类型嵌套格式。
|
||||
|
||||
## 三、算法解释
|
||||
|
||||
### 3.1 固定包头算法
|
||||
@@ -68,6 +78,11 @@ title: 内置包适配器
|
||||
|
||||
周期数据处理适配器,就是通过判断收到数据的时间间隔,将极短时间内收到的数据进行合并。能够一定程度的解决分包问题。
|
||||
|
||||
### 3.5 Json格式数据处理算法
|
||||
|
||||
Json格式数据处理算法,就是对接收的字符串进行大括号和中括号的计数,当成对的括号组合,来确定一个完整的json数据。
|
||||
|
||||
|
||||
## 四、使用
|
||||
|
||||
### 4.1 使用固定包头适配器
|
||||
@@ -344,6 +359,73 @@ private static async Task<TcpService> CreateService()
|
||||
|
||||
:::
|
||||
|
||||
### 4.5 使用Json格式数据处理适配器
|
||||
|
||||
客户端与服务器均适用。下列以服务器为例。
|
||||
|
||||
步骤
|
||||
|
||||
1. TouchSocketConfig配置中设置,同时指定数据的长度。
|
||||
2. 通过Received(事件、方法、插件)中的IRequestInfo,强制转为JsonPackage,然后读取数据。
|
||||
|
||||
```csharp {7,31} showLineNumbers
|
||||
private static async Task<TcpClient> CreateClient()
|
||||
{
|
||||
var client = new TcpClient();
|
||||
//载入配置
|
||||
await client.SetupAsync(new TouchSocketConfig()
|
||||
.SetRemoteIPHost("127.0.0.1:7789")
|
||||
.SetTcpDataHandlingAdapter(()=>new JsonPackageAdapter(Encoding.UTF8))
|
||||
.ConfigureContainer(a =>
|
||||
{
|
||||
a.AddConsoleLogger();//添加一个日志注入
|
||||
}));
|
||||
|
||||
await client.ConnectAsync();//调用连接,当连接不成功时,会抛出异常。
|
||||
client.Logger.Info("客户端成功连接");
|
||||
return client;
|
||||
}
|
||||
|
||||
private static async Task<TcpService> CreateService()
|
||||
{
|
||||
var service = new TcpService();
|
||||
service.Received = (client, e) =>
|
||||
{
|
||||
//从客户端收到信息
|
||||
var mes = e.ByteBlock.Span.ToString(Encoding.UTF8);
|
||||
client.Logger.Info($"已从{client.Id}接收到信息:{mes}");
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
|
||||
await service.SetupAsync(new TouchSocketConfig()//载入配置
|
||||
.SetListenIPHosts("tcp://127.0.0.1:7789", 7790)//同时监听两个地址
|
||||
.SetTcpDataHandlingAdapter(()=>new JsonPackageAdapter(Encoding.UTF8))
|
||||
.ConfigureContainer(a =>
|
||||
{
|
||||
a.AddConsoleLogger();//添加一个控制台日志注入(注意:在maui中控制台日志不可用)
|
||||
})
|
||||
.ConfigurePlugins(a =>
|
||||
{
|
||||
//a.Add();//此处可以添加插件
|
||||
}));
|
||||
await service.StartAsync();//启动
|
||||
service.Logger.Info("服务器已启动");
|
||||
return service;
|
||||
}
|
||||
```
|
||||
|
||||
:::tip 提示
|
||||
|
||||
Json格式数据处理适配器不对发送的数据做处理,仅仅对接收到的数据做处理。
|
||||
|
||||
:::
|
||||
|
||||
:::tip 提示
|
||||
|
||||
该适配器,客户端与服务器均适用。
|
||||
|
||||
:::
|
||||
|
||||
## 五、可设置参数
|
||||
|
||||
| 属性 | 描述 |默认值 |
|
||||
@@ -355,4 +437,6 @@ private static async Task<TcpService> CreateService()
|
||||
| CacheTimeout | 缓存超时时间。 |1秒|
|
||||
| UpdateCacheTimeWhenRev | 是否在收到数据时,即刷新缓存时间。当设为true时,将弱化CacheTimeout的作用,只要一直有数据,则缓存不会过期。当设为false时,则在CacheTimeout的时效内。必须完成单个缓存的数据 |true|
|
||||
|
||||
[本文示例Demo](https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/Adapter/PackageAdapterConsoleApp)
|
||||
## 六、本文示例Demo
|
||||
|
||||
<CardLink link="https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/Adapter/PackageAdapterConsoleApp"/>
|
||||
|
||||
@@ -6,7 +6,7 @@ title: 入门指南
|
||||
|
||||
## 一、说明
|
||||
|
||||
**TouchSocket(Pro)**系是基于`.Net`发布的程序集系列,所以它可以被用于对应`.Net`版本的`C#`、`F#`、`VB.net`等语言项目。
|
||||
**TouchSocket(Pro)** 系是基于`.Net`发布的程序集系列,所以它可以被用于对应`.Net`版本的`C#`、`F#`、`VB.net`等语言项目。
|
||||
|
||||
它支持您的项目是以下类型:
|
||||
- 控制台
|
||||
|
||||
@@ -399,7 +399,7 @@ await service.StartAsync();
|
||||
|
||||
:::danger 注意
|
||||
|
||||
当接收数据时,ByteBlock与RequestInfo的值会根据适配器类型不同而不同。并且,当数据存于ByteBlock时,其实际的数据长度是ByteBlock.Length(Len)。而不是ByteBlock.Buffer.Length
|
||||
当接收数据时,ByteBlock与RequestInfo的值会根据适配器类型不同而不同。并且,当数据存于ByteBlock时,其实际的数据长度是ByteBlock.Length(Length)。而不是ByteBlock.Buffer.Length
|
||||
|
||||
:::
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ config.UsePlugin()
|
||||
.ConfigurePlugins(a =>
|
||||
{
|
||||
a.Add<TLVPlugin>()//使用插件,相当于自动设置适配器,并且主动回应Ping。
|
||||
.SetLengthType(FixedHeaderType.Int);//设置支持的最大数据类型,该值还受SetMaxPackageSize影响。
|
||||
.SetLengthype(FixedHeaderType.Int);//设置支持的最大数据类型,该值还受SetMaxPackageSize影响。
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ title: 创建WebSocket客户端
|
||||
---
|
||||
|
||||
import Tag from "@site/src/components/Tag.js";
|
||||
import CardLink from "@site/src/components/CardLink.js";
|
||||
|
||||
### 定义
|
||||
|
||||
@@ -22,7 +23,7 @@ import Tag from "@site/src/components/Tag.js";
|
||||
| IWebSocketHandshakedPlugin | 当成功握手响应之后 |
|
||||
| IWebSocketReceivedPlugin | 当收到Websocket的数据报文 |
|
||||
| IWebSocketClosingPlugin | 当收到关闭请求时,如果对方直接断开连接,此方法则不会触发。 |
|
||||
| IWebSocketClosedPlugin | 当WebSocket连接断开时触发,无论是否正常断开。但如果是断网等操作,可能不会立即执行,需要结合心跳操作和CheckClear插件来进行清理。 |
|
||||
| IWebSocketClosedPlugin | 当WebSocket连接断开时触发,无论是否正常断开。但如果是断网等操作,可能不会立即执行,需要结合心跳操作和`UseCheckClear`插件来进行清理。 |
|
||||
|
||||
## 三、创建客户端
|
||||
|
||||
@@ -71,17 +72,17 @@ Console.WriteLine("连接成功");
|
||||
|
||||
:::caution 注意
|
||||
|
||||
当使用域名连接时,TargetHost为域名,例如连接到IPHost("wss://baidu.com")时,TargetHost应当填写:baidu.com
|
||||
当使用域名连接时,`TargetHost`为域名,例如连接到`IPHost("wss://baidu.com")`时,`TargetHost`应当填写:`baidu.com`
|
||||
|
||||
:::
|
||||
|
||||
## 四、连接服务器
|
||||
|
||||
WebSessionClient可以使用默认配置直接连接到服务器,同时也支持使用多种方法定义连接。
|
||||
`WebSocketClient`可以使用默认配置直接连接到服务器,同时也支持使用多种方法定义连接。
|
||||
|
||||
### 4.1 直接连接
|
||||
|
||||
使用url直接建立连接,这一般是服务器也只是普通的ws服务器的情况下。
|
||||
使用`url`直接建立连接,这一般是服务器也只是普通的`ws`服务器的情况下。
|
||||
|
||||
```csharp showLineNumbers
|
||||
var client = new WebSocketClient();
|
||||
@@ -98,7 +99,7 @@ client.Logger.Info("通过ws://127.0.0.1:7789/ws连接成功");
|
||||
|
||||
### 4.2 带Query参数连接
|
||||
|
||||
带Query参数连接,实际上还是通过url直接连接。
|
||||
带`Query`参数连接,实际上还是通过`url`直接连接。
|
||||
|
||||
```csharp showLineNumbers
|
||||
var client = new WebSocketClient();
|
||||
@@ -116,7 +117,7 @@ client.Logger.Info("通过ws://127.0.0.1:7789/wsquery?token=123456连接成功")
|
||||
|
||||
### 4.3 使用特定Header连接
|
||||
|
||||
一般的,当某些服务器安全级别较高时,可能会定制特定的header用于验证连接。
|
||||
一般的,当某些服务器安全级别较高时,可能会定制特定的`header`用于验证连接。
|
||||
|
||||
```csharp showLineNumbers
|
||||
var client = new WebSocketClient();
|
||||
@@ -141,13 +142,13 @@ client.Logger.Info("通过ws://127.0.0.1:7789/wsheader连接成功");
|
||||
|
||||
:::tip 提示
|
||||
|
||||
实际上OnWebSocketHandshaking就是插件委托,也可以自己封装到插件使用。
|
||||
实际上`OnWebSocketHandshaking`就是插件委托,也可以自己封装到插件使用。
|
||||
|
||||
:::
|
||||
|
||||
### 4.4 使用Post方式连接
|
||||
|
||||
WebSocket默认情况下是基于GET方式连接的,但是在一些更特殊的情况下,需要以POST,甚至其他方式连接,那么可以使用以下方式实现。
|
||||
`WebSocket`默认情况下是基于`Get`方式连接的,但是在一些更特殊的情况下,需要以`Post`,甚至其他方式连接,那么可以使用以下方式实现。
|
||||
|
||||
```csharp showLineNumbers
|
||||
using var client = new WebSocketClient();
|
||||
@@ -172,40 +173,41 @@ client.Logger.Info("通过ws://127.0.0.1:7789/postws连接成功");
|
||||
|
||||
:::tip 提示
|
||||
|
||||
使用此方式时,基本上就能完全定制请求连接了。比如一些Cookie等。
|
||||
使用此方式时,基本上就能完全定制请求连接了。比如一些`Cookie`等。
|
||||
|
||||
:::
|
||||
|
||||
|
||||
## 五、发送数据
|
||||
|
||||
因为客户端是从**HttpClientBase**派生,则可以直接使用**扩展方法**,进行发送。
|
||||
客户端定义了一些发送方法,方便开发者快速发送数据。
|
||||
|
||||
### 5.1 发送文本类消息
|
||||
|
||||
```csharp showLineNumbers
|
||||
client.SendAsync("Text");
|
||||
await client.SendAsync("Text");
|
||||
```
|
||||
|
||||
### 5.2 发送二进制消息
|
||||
|
||||
```csharp showLineNumbers
|
||||
client.SendAsync(new byte[10]);
|
||||
await client.SendAsync(new byte[10]);
|
||||
```
|
||||
|
||||
### 5.3 直接发送自定义构建的数据帧
|
||||
```csharp showLineNumbers
|
||||
WSDataFrame frame=new WSDataFrame();
|
||||
frame.Opcode= WSDataType.Text;
|
||||
frame.FIN= true;
|
||||
frame.RSV1= true;
|
||||
frame.RSV2= true;
|
||||
frame.RSV3= true;
|
||||
frame.AppendText("I");
|
||||
frame.AppendText("Love");
|
||||
frame.AppendText("U");
|
||||
|
||||
client.SendAsync(frame);
|
||||
using (var frame = new WSDataFrame())
|
||||
{
|
||||
frame.Opcode = WSDataType.Text;
|
||||
frame.FIN = true;
|
||||
frame.RSV1 = true;
|
||||
frame.RSV2 = true;
|
||||
frame.RSV3 = true;
|
||||
frame.AppendText("I");
|
||||
frame.AppendText("Love");
|
||||
frame.AppendText("U");
|
||||
await client.SendAsync(frame);
|
||||
}
|
||||
```
|
||||
:::info 备注
|
||||
|
||||
@@ -218,26 +220,28 @@ client.SendAsync(frame);
|
||||
### 6.1 订阅Received事件实现
|
||||
|
||||
```csharp showLineNumbers
|
||||
client.Received = (c, e) =>
|
||||
client.Received = async (c, e) =>
|
||||
{
|
||||
switch (e.DataFrame.Opcode)
|
||||
{
|
||||
switch (e.DataFrame.Opcode)
|
||||
{
|
||||
case WSDataType.Cont:
|
||||
break;
|
||||
case WSDataType.Text:
|
||||
break;
|
||||
case WSDataType.Binary:
|
||||
break;
|
||||
case WSDataType.Close:
|
||||
break;
|
||||
case WSDataType.Ping:
|
||||
break;
|
||||
case WSDataType.Pong:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
case WSDataType.Cont:
|
||||
break;
|
||||
case WSDataType.Text:
|
||||
break;
|
||||
case WSDataType.Binary:
|
||||
break;
|
||||
case WSDataType.Close:
|
||||
break;
|
||||
case WSDataType.Ping:
|
||||
break;
|
||||
case WSDataType.Pong:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
await e.InvokeNext();
|
||||
};
|
||||
```
|
||||
|
||||
### 6.2 使用插件实现 <Tag>推荐</Tag>
|
||||
@@ -354,7 +358,7 @@ using (var client = GetClient())
|
||||
|
||||
:::info 信息
|
||||
|
||||
`ReadAsync`的方式是属于**同步不阻塞**的接收方式(和当下Aspnetcore模式一样)。他不会单独占用线程,只会阻塞当前`Task`。所以可以大量使用,不需要考虑性能问题。同时,`ReadAsync`的好处就是单线程访问上下文,这样在处理ws分包时是非常方便的。
|
||||
`ReadAsync`的方式是属于**同步不阻塞**的接收方式。他不会单独占用线程,只会阻塞当前`Task`。所以可以大量使用,不需要考虑性能问题。同时,`ReadAsync`的好处就是单线程访问上下文,这样在处理ws分包时是非常方便的。
|
||||
|
||||
:::
|
||||
|
||||
@@ -612,15 +616,15 @@ public class MyWebSocketPlugin : PluginBase, IWebSocketReceivedPlugin
|
||||
|
||||
### 7.1 握手机制
|
||||
|
||||
`WebSocket`拥有独立的握手机制,直接获取`IsHandshaked`属性即可。
|
||||
`WebSocket`拥有独立的握手机制,直接获取`Online`属性即可。
|
||||
|
||||
### 7.2 Ping机制
|
||||
|
||||
`WebSocket`有自己的`Ping`、`Pong`机制。所以直接调用已有方法即可。
|
||||
|
||||
```csharp showLineNumbers
|
||||
client.Ping();
|
||||
client.Pong();
|
||||
await client.PingAsync();
|
||||
await client.PongAsync();
|
||||
```
|
||||
|
||||
:::tip 建议
|
||||
@@ -631,21 +635,31 @@ client.Pong();
|
||||
|
||||
### 7.3 断线重连
|
||||
|
||||
`WebSocket`断线重连,可以直接使用[Tcp断线重连](./reconnection.mdx)插件。
|
||||
`WebSocket`断线重连,可以直接使用插件。
|
||||
|
||||
```csharp showLineNumbers
|
||||
.ConfigurePlugins(a =>
|
||||
{
|
||||
a.UseReconnection();
|
||||
a.UseWebSocketReconnection();
|
||||
})
|
||||
```
|
||||
|
||||
## 八、关闭连接
|
||||
|
||||
关闭Websocket,应该发送关闭报文。
|
||||
在使用`WebSocket`时,如果想主动关闭连接,可以使用`CloseAsync`方法,同时可以携带一个关闭原因。
|
||||
|
||||
默认关闭状态码为1000。意为:正常关闭。
|
||||
|
||||
```csharp showLineNumbers
|
||||
myWSClient.Close("close");
|
||||
await webSocket.CloseAsync("关闭");
|
||||
```
|
||||
|
||||
[本文示例Demo](https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/WebSocket/WebSocketConsoleApp)
|
||||
如果你想使用其他状态码,可以参考如下代码。
|
||||
|
||||
```csharp showLineNumbers
|
||||
await webSocket.CloseAsync(WebSocketCloseStatus.EndpointUnavailable,"关闭");//状态码为1001,意为:服务端不可用。
|
||||
```
|
||||
|
||||
## 九、本文示例Demo
|
||||
|
||||
<CardLink link="https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/WebSocket/WebSocketConsoleApp"/>
|
||||
|
||||
@@ -3,6 +3,8 @@ id: websocketservice
|
||||
title: 创建WebSocket服务器
|
||||
---
|
||||
|
||||
import CardLink from "@site/src/components/CardLink.js";
|
||||
|
||||
### 定义
|
||||
|
||||
命名空间:TouchSocket.Http.WebSockets <br/>
|
||||
@@ -838,17 +840,18 @@ await webSocket.SendAsync(new byte[10]);
|
||||
### 6.5 直接发送自定义构建的数据帧
|
||||
|
||||
```csharp showLineNumbers
|
||||
WSDataFrame frame=new WSDataFrame();
|
||||
frame.Opcode= WSDataType.Text;
|
||||
frame.FIN= true;
|
||||
frame.RSV1= true;
|
||||
frame.RSV2= true;
|
||||
frame.RSV3= true;
|
||||
frame.AppendText("I");
|
||||
frame.AppendText("Love");
|
||||
frame.AppendText("U");
|
||||
|
||||
await webSocket.SendAsync(frame);
|
||||
using (var frame = new WSDataFrame())
|
||||
{
|
||||
frame.Opcode = WSDataType.Text;
|
||||
frame.FIN = true;
|
||||
frame.RSV1 = true;
|
||||
frame.RSV2 = true;
|
||||
frame.RSV3 = true;
|
||||
frame.AppendText("I");
|
||||
frame.AppendText("Love");
|
||||
frame.AppendText("U");
|
||||
await webSocket.SendAsync(frame);
|
||||
}
|
||||
```
|
||||
:::info 备注
|
||||
|
||||
@@ -886,12 +889,23 @@ while (true)
|
||||
}
|
||||
```
|
||||
|
||||
### 6.9 关闭连接
|
||||
|
||||
## 七、关闭连接
|
||||
|
||||
在使用`WebSocket`时,如果想主动关闭连接,可以使用`CloseAsync`方法,同时可以携带一个关闭原因。
|
||||
|
||||
默认关闭状态码为1000。意为:正常关闭。
|
||||
|
||||
```csharp showLineNumbers
|
||||
await webSocket.CloseAsync("关闭");
|
||||
```
|
||||
|
||||
如果你想使用其他状态码,可以参考如下代码。
|
||||
|
||||
```csharp showLineNumbers
|
||||
await webSocket.CloseAsync(WebSocketCloseStatus.EndpointUnavailable,"关闭");//状态码为1001,意为:服务端不可用。
|
||||
```
|
||||
|
||||
[本文示例Demo](https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/WebSocket/WebSocketConsoleApp)
|
||||
## 八、本文示例Demo
|
||||
|
||||
<CardLink link="https://gitee.com/RRQM_Home/TouchSocket/tree/master/examples/WebSocket/WebSocketConsoleApp"/>
|
||||
@@ -15,9 +15,9 @@
|
||||
"typecheck": "tsc"
|
||||
},
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "^3.6.1",
|
||||
"@docusaurus/preset-classic": "^3.6.1",
|
||||
"@docusaurus/theme-mermaid": "^3.6.1",
|
||||
"@docusaurus/core": "^3.6.2",
|
||||
"@docusaurus/preset-classic": "^3.6.2",
|
||||
"@docusaurus/theme-mermaid": "^3.6.2",
|
||||
"@easyops-cn/docusaurus-search-local": "^0.40.1",
|
||||
"@giscus/react": "^3.0.0",
|
||||
"@mdx-js/react": "^3.0.0",
|
||||
@@ -29,9 +29,9 @@
|
||||
"react-dom": "^18.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/module-type-aliases": "^3.6.1",
|
||||
"@docusaurus/tsconfig": "^3.6.1",
|
||||
"@docusaurus/types": "^3.6.1",
|
||||
"@docusaurus/module-type-aliases": "^3.6.2",
|
||||
"@docusaurus/tsconfig": "^3.6.2",
|
||||
"@docusaurus/types": "^3.6.2",
|
||||
"typescript": "~5.2.2"
|
||||
},
|
||||
"browserslist": {
|
||||
@@ -54,4 +54,4 @@
|
||||
"@docusaurus/core": "^3.2.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user