transferfile.mdx 9.1 KB
Newer Older
若汝棋茗 已提交
1 2 3 4 5
---
id: transferfile
title: 传输文件
---

若汝棋茗 已提交
6
## 一、说明
若汝棋茗 已提交
7

若汝棋茗 已提交
8
文件传输是每个框架都需要的功能,也是检验一个框架性能的非常重要的指标。
若汝棋茗 已提交
9

若汝棋茗 已提交
10
TouchRpc开辟了对点文件传输。即,当客户端连接服务器以后,两者可以任意,随时的互相发送文件。不仅如此,即使是客户端之间,可以发送文件。
若汝棋茗 已提交
11

若汝棋茗 已提交
12
下列示例仅演示由**TcpTouchRpcClient**到**TcpTouchRpcService**(实际上是**TcpTouchRpcSocketClient**)的操作。
若汝棋茗 已提交
13

若汝棋茗 已提交
14
对点之间可以任意pull(拉取)、push(推送)文件。**接收对点**可以订阅**FileTransfering**和**FileTransfered**事件,来获取相关信息,发起对点直接通过传输控制器或返回值获取传输信息。
若汝棋茗 已提交
15

若汝棋茗 已提交
16
## 二、性能
若汝棋茗 已提交
17

若汝棋茗 已提交
18
可以看到,下图正在上传一个Window的系统镜像文件,大约4.2Gb,传输速度已达到800Mb/s,GC基本上没有释放,性能非常强悍(中间有稍微停顿,因为程序在获取文件MD5值)。
若汝棋茗 已提交
19

若汝棋茗 已提交
20
<img src={require('../static/img/docs/transferfile-1.gif').default} />
若汝棋茗 已提交
21

若汝棋茗 已提交
22
## 三、产品应用场景
若汝棋茗 已提交
23

若汝棋茗 已提交
24 25
- 常规C/S应用使用场景:开发使用非常方便,连接验证,数据业务,文件传输等一系列功能完全集成。
- Unity游戏场景:性能卓越,功能丰富,使用方便。 <a name="h50Dz"></a>
若汝棋茗 已提交
26

若汝棋茗 已提交
27
## 四、服务架构
若汝棋茗 已提交
28

若汝棋茗 已提交
29 30
- 其传输架构是基于Channel工作的。所以当在同一时间,可进行多个传输并行,且数据互不影响。
- 在文件传输时,每个连接端和服务器均是平等权利的,所以RRQM将其命名为对点。任意两个对点之间均可Pull(拉取或下载)或Push(推送或上传)文件,例如下图中,Client1、SocketClient1、Client2、SocketClient2四个互相为对点,均可自由传输文件。
若汝棋茗 已提交
31 32


若汝棋茗 已提交
33
## 五、传输文件
若汝棋茗 已提交
34 35 36

由**TcpTouchRpcClient**向**TcpTouchRpcService**发起Pull请求时,相当于由客户端从服务器下载文件。

若汝棋茗 已提交
37 38 39
下列示例仅演示Pull请求,Push请求操作一致。

### 5.1 响应流程
若汝棋茗 已提交
40 41 42 43 44

1. 发起Pull请求。
2. 接收对点(即此处的服务器)触发**FileTransfering**事件。
3. 返回文件信息,然后检验是否续传等,然后开始接收。
4. 接收完成或异常。
若汝棋茗 已提交
45 46 47 48 49 50 51 52 53 54 55 56
5. 接收对点(即此处的服务器)触发**FileTransfered**事件。
6. 请求端(此处的客户端)函数返回,控制器状态改变。

### 5.2 请求配置

FileOperator是本次传输的请求操作器,主要用于获取传输进度、速度、状态以及取消传输等操作。**接收方的控制器从FileTransfering事件的参数e中获得。**

可配置参数:

#### (1)ResourcePath

资源路径,在上传时,表示发起端的文件路径。在下载时,表示请求的文件路径。当该值为相对路径时,会与接收对点的RootPath组合路径。当为绝对路径时,则会直接访问路径文件。
若汝棋茗 已提交
57

若汝棋茗 已提交
58
:::tip 提示
若汝棋茗 已提交
59

若汝棋茗 已提交
60
如果是下载行为,响应方可在在订阅的**OnFileTransfering**函数中,随意重定向请求的文件路径(e.ResourcePath)。
若汝棋茗 已提交
61

若汝棋茗 已提交
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
:::  

:::danger 危险

当以绝对路径访问时,对方可能会请求到服务器电脑的**所有文件**,所以最好在**OnFileTransfering**里面进行安全的判断后再放行。

:::  

#### (2)SavePath
保存路径,在上传时,表示需要保存的文件路径。在下载时,表示本地保存的文件路径。同样,当该值为相对路径时,会与接收对点的RootPath组合路径。当为绝对路径时,则会直接生效。

:::tip 提示

如果是上传行为,响应方可在在订阅的**OnFileTransfering**函数中,随意重定向文件的保存路径(e.SavePath)。

:::  

#### (3)Flags
可通过叠加位域的形式,尝试断点续传。

#### (4)CompletedLength
已完成流长度。

#### (5)Speed 函数
从上次获取到此次获得的速度。一般请每秒钟调用一次获取速度值。

:::caution 注意

当获取传输速度时,其值和获取时间完全相关。例如:假如实际每秒传输速度为100,当每隔一秒获取时,则为100.当每隔100毫秒获取时,则为10。

:::  

#### (6)Progress
传输进度,范围0-1。

#### (7) Result
获取传输状态以及状态信息。当ResultCode为Default时,意味着传输正在进行。

#### (8) Token
CancellationToken类型的可取消令箭。

#### (9) Metadata
string类型的键值对,用于和接收方交互数据。
若汝棋茗 已提交
105 106 107

<a name="JeQp8"></a>

若汝棋茗 已提交
108
### 5.3 示例代码
若汝棋茗 已提交
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159

**【服务器】**

```csharp
var service = new TouchSocketConfig()//配置
    .SetListenIPHosts(new IPHost[] { new IPHost(7789) })
    .UsePlugin()
    .ConfigureContainer(a =>
    {
        a.AddConsoleLogger();
        a.AddFileLogger();
    })
    .ConfigurePlugins(a =>
    {
        a.Add<MyPlugin>();
    })
    .SetVerifyToken("File")//连接验证口令。
    .BuildWithTcpTouchRpcService();//此处build相当于new TcpTouchRpcService,然后Setup,然后Start。
service.Logger.Info("服务器成功启动");
```

```csharp
class MyPlugin : TouchRpcPluginBase<TcpTouchRpcSocketClient>
{
    protected override void OnFileTransfering(TcpTouchRpcSocketClient client, FileOperationEventArgs e)
    {
        e.IsPermitOperation = true;//运行操作

        //有可能是上传,也有可能是下载
        client.Logger.Info($"有客户端请求传输文件,ID={client.ID},请求类型={e.TransferType},请求文件名={e.ResourcePath}");
    }

    protected override void OnFileTransfered(TcpTouchRpcSocketClient client, FileTransferStatusEventArgs e)
    {
        //传输结束,但是不一定成功,需要从e.Result判断状态。
        client.Logger.Info($"客户端传输文件结束,ID={client.ID},请求类型={e.TransferType},文件名={e.ResourcePath},请求状态={e.Result}");
    }

    protected override void OnHandshaked(TcpTouchRpcSocketClient client, VerifyOptionEventArgs e)
    {
        client.Logger.Info($"有客户端成功验证,ID={client.ID}");
    }

    protected override void OnDisconnected(TcpTouchRpcSocketClient client, ClientDisconnectedEventArgs e)
    {
        client.Logger.Info($"有客户端断开,ID={client.ID}");
        base.OnDisconnected(client, e);
    }
}
```

若汝棋茗 已提交
160 161 162 163 164 165 166 167 168 169 170 171
:::caution 注意

响应方必须在订阅的**OnFileTransfering**函数中,同意每一个传输(e.IsPermitOperation = true),不然请求方会直接失败。

:::  

:::caution 注意

响应方订阅的**FileTransfered**事件的触发并**不意味着完成传输**,具体结果还要通过**Result**属性值进行判断。 同时当**Result**为成功时,也有极小的概率失败。总之响应端无法100%得知传输的最终结果。

:::  

若汝棋茗 已提交
172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226
【客户端】

```csharp
TcpTouchRpcClient client = new TouchSocketConfig()
    .SetRemoteIPHost("127.0.0.1:7789")
    .SetVerifyToken("File")
    .UsePlugin()
    .ConfigureContainer(a => 
    {
        a.AddConsoleLogger();
        a.AddFileLogger();
    })
    .ConfigurePlugins(a =>
    {
        a.UseTouchRpcHeartbeat<TcpTouchRpcClient>();
    })
    .BuildWithTcpTouchRpcClient();

client.Logger.Info("连接成功");

Metadata metadata = new Metadata();//传递到服务器的元数据
metadata.Add("1", "1");
metadata.Add("2", "2");

FileOperator fileOperator = new FileOperator()//实例化本次传输的控制器,用于获取传输进度、速度、状态等。
{
    Flags = TransferFlags.BreakpointResume,//尝试断点续传,使用断点续传时,会验证MD5值
    SavePath = $@"Windows.iso",//保存路径
    ResourcePath = @"D:\System\Windows.iso",//请求路径
    Metadata= metadata//传递到服务器的元数据
};

fileOperator.Timeout = TimeSpan.FromSeconds(60);//当传输大文件,且启用断点续传时,服务器可能会先计算MD5,而延时响应,所以需要设置超时时间。

//此处的作用相当于Timer,定时每秒输出当前的传输进度和速度。
LoopAction loopAction = LoopAction.CreateLoopAction(-1, 1000, (loop) =>
{
    if (fileOperator.Result.ResultCode != ResultCode.Default)
    {
        loop.Dispose();
    }

    client.Logger.Info($"进度:{fileOperator.Progress},速度:{fileOperator.Speed()}");
});

loopAction.RunAsync();



//此方法会阻塞,直到传输结束,也可以使用PullFileAsync
IResult result = client.PullFile(fileOperator);

client.Logger.Info(result.ToString());
```

若汝棋茗 已提交
227
:::tip 提示
若汝棋茗 已提交
228

若汝棋茗 已提交
229
请求端返回的**result**,在成功时,可以100%表明传输的文件已在磁盘上。如果想进一步确定文件的准确性,还需要自行再验证MD5或者其他Hash算法。
若汝棋茗 已提交
230

若汝棋茗 已提交
231
:::  
若汝棋茗 已提交
232

若汝棋茗 已提交
233
<a name="qZvBF"></a>
若汝棋茗 已提交
234

若汝棋茗 已提交
235
## 六、客户端之间传输文件
若汝棋茗 已提交
236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253

该功能支持客户端之间传输文件,使用方法基本一致,只需要额外增加目标Id即可。

此外,**服务器**也需要同意路由。需要注意的是,使用该方式文件传输时,还会发起通道路由,所以,需要允许的路由应该还额外增加**通道类型**。

```csharp
internal class MyTouchRpcPlugin : TouchRpcPluginBase
{
    protected override void OnRouting(ITouchRpc client, PackageRouterEventArgs e)
    {
        if (e.RouterType== RouteType.PushFile||e.RouterType== RouteType.PullFile||e.RouterType== RouteType.CreateChannel)
        {
            e.IsPermitOperation = true;
        }
        base.OnRouting(client, e);
    }
}
```