跳到主要内容
版本:1.3.9

传输文件

一、说明

文件传输是每个框架都需要的功能,也是检验一个框架性能的非常重要的指标。

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

下列示例仅演示由TcpTouchRpcClientTcpTouchRpcService(实际上是TcpTouchRpcSocketClient)的操作。

对点之间可以任意pull(拉取)、push(推送)文件。接收对点可以订阅FileTransferingFileTransfered事件,来获取相关信息,发起对点直接通过传输控制器或返回值获取传输信息。

二、性能

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

三、产品应用场景

  • 常规C/S应用使用场景:开发使用非常方便,连接验证,数据业务,文件传输等一系列功能完全集成。
  • Unity游戏场景:性能卓越,功能丰富,使用方便。

四、服务架构

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

五、传输文件

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

下列示例仅演示Pull请求,Push请求操作一致。

5.1 响应流程

  1. 发起Pull请求。
  2. 接收对点(即此处的服务器)触发FileTransfering事件。
  3. 返回文件信息,然后检验是否续传等,然后开始接收。
  4. 接收完成或异常。
  5. 接收对点(即此处的服务器)触发FileTransfered事件。
  6. 请求端(此处的客户端)函数返回,控制器状态改变。

5.2 请求配置

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

可配置参数:

(1)ResourcePath

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

提示

如果是下载行为,响应方可在在订阅的OnFileTransfering函数中,随意重定向请求的文件路径(e.ResourcePath)。

危险

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

(2)SavePath

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

提示

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

(3)Flags

可通过叠加位域的形式,尝试断点续传。

(4)CompletedLength

已完成流长度。

(5)Speed 函数

从上次获取到此次获得的速度。一般请每秒钟调用一次获取速度值。

注意

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

(6)Progress

传输进度,范围0-1。

(7) Result

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

(8) Token

CancellationToken类型的可取消令箭。

(9) Metadata

string类型的键值对,用于和接收方交互数据。

5.3 示例代码

【服务器】

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

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

注意

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

【客户端】

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());
提示

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

六、客户端之间传输文件

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

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

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);
}
}