传输文件
一、说明
文件传输是每个框架都需要的功能,也是检验一个框架性能的非常重要的指标。
TouchRpc开辟了对点文件传输。即,当客户端连接服务器以后,两者可以任意,随时的互相发送文件。不仅如此,即使是客户端之间,可以发送文件。
下列示例仅演示由TcpTouchRpcClient到TcpTouchRpcService(实际上是TcpTouchRpcSocketClient)的操作。
对点之间可以任意pull(拉取)、push(推送)文件。接收对点可以订阅FileTransfering和FileTransfered事件,来获取相关信息,发起对点直接通过传输控制器或返回值获取传输信息。
二、性能
可以看到,下图正在上传一个Window的系统镜像文件,大约4.2Gb,传输速度已达到800Mb/s,GC基本上没有释放,性能非常强悍(中间有稍微停顿,因为程序在获取文件MD5值)。

三、产品应用场景
四、服务架构
- 其传输架构是基于Channel工作的。所以当在同一时间,可进行多个传输并行,且数据互不影响。
- 在文件传输时,每个连接端和服务器均是平等权利的,所以RRQM将其命名为对点。任意两个对点之间均可Pull(拉取或下载)或Push(推送或上传)文件,例如下图中,Client1、SocketClient1、Client2、SocketClient2四个互相为对点,均可自由传输文件。
五、传输文件
由TcpTouchRpcClient向TcpTouchRpcService发起Pull请求时,相当于由客户端从服务器下载文件。
下列示例仅演示Pull请求,Push请求操作一致。
5.1 响应流程
- 发起Pull请求。
- 接收对点(即此处的服务器)触发FileTransfering事件。
- 返回文件信息,然后检验是否续传等,然后开始接收。
- 接收完成或异常。
- 接收对点(即此处的服务器)触发FileTransfered事件。
- 请求端(此处的客户端)函数返回,控制器状态改变。
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);
}
}