跳到主要内容
版本:2.0.0

传输文件

定义

命名空间:TouchSocket.Dmtp.FileTransfer
程序集:TouchSocket.Dmtp.dll
程序集:TouchSocketPro.Dmtp.dll(多线程传输)

一、说明

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

本组件则是基于Dmtp,开辟了全双工对点文件传输。即,当客户端连接服务器以后,客户端可以向服务器请求、推送文件,服务器也能向客户端请求,推送文件。甚至,客户端之间,也可以互相请求,推送文件。

其特点包括:

  • 全双工对点文件传输。即:客户端、服务器、其他客户端三者之间,可以互相推送、请求文件。
  • 高性能、低GC。整个传输过程,将内存池用到极致,极大的减少不必要的GC。本地电脑实测传输速度达到1.2Gb/秒。
  • 全平台支持。Windows、Android、Unity3D(除webgl)全部支持。
  • 支持任意大小的文件传输(实测100Gb没有问题)。
  • 支持断点续传。
  • 支持传输限速。
  • 支持文件多链路、多线程传输

二、性能

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

三、产品应用场景

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

四、传输流程及名词介绍

4.1 响应流程

  1. 请求端(可能是客户端,也可能是服务器)调用Pull(请求)或Push(推送)。
  2. 响应方(可能是服务器,也可能是客户端)触发FileTransfering
  3. 返回文件信息,然后检验是否续传等,然后开始传输。
  4. 传输完成或异常。
  5. 响应方可能触发FileTransfered
  6. 请求端函数返回,FileOperator控制器状态改变。
注意

响应方必须在订阅的OnFileTransfering函数中,同意每一个传输(e.IsPermitOperation = true),不然会直接拒绝请求。当然如果是有意拒绝,则可以通过e.Message返回拒绝的信息。

注意

响应方订阅的OnFileTransfered事件的触发并不意味着完成传输,具体结果还要通过Result属性值进行判断。

提示

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

4.2 传输控制器

FileOperator是本次文件传输的操作器,主要用于获取传输进度、速度、状态以及取消传输等操作。

可配置参数:

(1)ResourcePath

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

提示

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

危险

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

(2)SavePath

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

提示

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

(3)ResourceInfo

已存在的资源信息,当上个传输失败时,可以保存其ResourceInfo,然后重新传输时赋值,即可尝试断点续传。

注意

当执行续传时,本次传输与前次传输间隔时间不应该超过响应默认值(60秒)。

(4)CompletedLength

已完成流长度。

(5)Speed 函数

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

注意

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

(6)Progress

传输进度,范围0-1。

(7) Result

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

(8) Token

CancellationToken类型的可取消令箭。

(9) Metadata

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

五、 传输文件

传输文件功能,是DmtpFileTransferFeature功能插件基于Dmtp协议提供的功能。所以,Dmtp服务器客户端都必须配置添加该功能插件。即调用UseDmtpFileTransfer

config.ConfigurePlugins(a =>
{
a.UseDmtpFileTransfer()//必须添加文件传输插件
//.SetRootPath("C:\\新建文件夹")//设置RootPath
.SetMaxSmallFileLength(1024 * 1024);//设置小文件的最大限制长度
a.Add<MyPlugin>();
})

声明MyPlugin插件,是为了方便处理OnDmtpFileTransferingOnDmtpFileTransfered函数。

internal class MyPlugin : PluginBase, IDmtpFileTransferingPlugin, IDmtpFileTransferedPlugin
{
private readonly ILog m_logger;

public MyPlugin(ILog logger)
{
this.m_logger = logger;
}

/// <summary>
/// 该方法,会在每个文件被请求(推送)结束时触发。传输不一定成功,具体信息需要从e.Result判断状态。
/// 其次,该方法也不一定会被执行,例如:在传输过程中,直接断网,则该方法将不会执行。
/// </summary>
/// <param name="client"></param>
/// <param name="e"></param>
/// <returns></returns>
public async Task OnDmtpFileTransfered(IDmtpActorObject client, FileTransferedEventArgs e)
{
//传输结束,但是不一定成功,甚至该方法都不一定会被触发,具体信息需要从e.Result判断状态。
this.m_logger.Info($"传输文件结束,请求类型={e.TransferType},文件名={e.ResourcePath},请求状态={e.Result}");
await e.InvokeNext();
}


/// <summary>
/// 该方法,会在每个文件被请求(推送)时第一时间触发。
/// 当请求文件时,可以重新指定请求的文件路径,即对e.ResourcePath直接赋值。
/// 当推送文件时,可以重新指定保存文件路径,即对e.SavePath直接赋值。
///
/// 注意:当文件夹不存在时,需要手动创建。
/// </summary>
/// <param name="client"></param>
/// <param name="e"></param>
/// <returns></returns>
public async Task OnDmtpFileTransfering(IDmtpActorObject client, FileTransferingEventArgs e)
{
foreach (var item in e.Metadata.Keys)
{
Console.WriteLine($"Key={item},Value={e.Metadata[item]}");
}
e.IsPermitOperation = true;//每次传输都需要设置true,表示允许传输
//有可能是上传,也有可能是下载
this.m_logger.Info($"请求传输文件,请求类型={e.TransferType},请求文件名={e.ResourcePath}");
await e.InvokeNext();
}
}

5.1 客户端向服务器请求文件

【客户端代码】

var filePath = "ClientPullFileFromService.Test";
var saveFilePath = "SaveClientPullFileFromService.Test";

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

var fileOperator = new FileOperator()//实例化本次传输的控制器,用于获取传输进度、速度、状态等。
{
SavePath = saveFilePath,//客户端本地保存路径
ResourcePath = filePath,//请求文件的资源路径
Metadata = metadata,//传递到服务器的元数据
Timeout = TimeSpan.FromSeconds(60),//传输超时时长
TryCount = 10,//当遇到失败时,尝试次数
FileSectionSize = 1024 * 512//分包大小,当网络较差时,应该适当减小该值
};

fileOperator.MaxSpeed=1024 * 1024;//设置最大限速为1Mb。

//此处的作用相当于Timer,定时每秒输出当前的传输进度和速度。
var 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.GetDmtpFileTransferActor().PullFile(fileOperator);

5.2 客户端向服务器推送文件

var filePath = "ClientPushFileFromService.Test";
var saveFilePath = "SaveClientPushFileFromService.Test";

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

var fileOperator = new FileOperator()//实例化本次传输的控制器,用于获取传输进度、速度、状态等。
{
SavePath = saveFilePath,//服务器本地保存路径
ResourcePath = filePath,//客户端本地即将上传文件的资源路径
Metadata = metadata,//传递到服务器的元数据
Timeout = TimeSpan.FromSeconds(60),//传输超时时长
TryCount = 10,//当遇到失败时,尝试次数
FileSectionSize = 1024 * 512//分包大小,当网络较差时,应该适当减小该值
};

fileOperator.MaxSpeed=1024 * 1024;//设置最大限速为1Mb。

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

loopAction.RunAsync();

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

5.3 服务器向客户端请求文件

服务器主动向客户端请求文件,必须通过Id,找到其SocketClient的派生类。

string targetId="targetId";
if (!service.TryGetSocketClient(targetId, out var socketClient))
{
throw new Exception($"没有找到Id={targetId}的客户端");
}

var filePath = "ServicePullFileFromClient.Test";
var saveFilePath = "SaveServicePullFileFromClient.Test";

var metadata = new Metadata();//传递到客户端的元数据
metadata.Add("1", "1");
metadata.Add("2", "2");

var fileOperator = new FileOperator()//实例化本次传输的控制器,用于获取传输进度、速度、状态等。
{
SavePath = saveFilePath,//服务器本地保存路径
ResourcePath = filePath,//请求客户端文件的资源路径
Metadata = metadata,//传递到客户端的元数据
Timeout = TimeSpan.FromSeconds(60),//传输超时时长
TryCount = 10,//当遇到失败时,尝试次数
FileSectionSize = 1024 * 512//分包大小,当网络较差时,应该适当减小该值
};

fileOperator.MaxSpeed=1024 * 1024;//设置最大限速为1Mb。

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

loopAction.RunAsync();

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

5.4 服务器向客户端推送文件

服务器主动向客户端推送文件,必须通过Id,找到其SocketClient的派生类。

if (!service.TryGetSocketClient(targetId, out var socketClient))
{
throw new Exception($"没有找到Id={targetId}的客户端");
}

var filePath = "ServicePushFileFromClient.Test";
var saveFilePath = "SaveServicePushFileFromClient.Test";

var metadata = new Metadata();//传递到客户端的元数据
metadata.Add("1", "1");
metadata.Add("2", "2");

var fileOperator = new FileOperator()//实例化本次传输的控制器,用于获取传输进度、速度、状态等。
{
SavePath = saveFilePath,//客户端本地保存路径
ResourcePath = filePath,//服务器文件的资源路径
Metadata = metadata,//传递到客户端的元数据
Timeout = TimeSpan.FromSeconds(60),//传输超时时长
TryCount = 10,//当遇到失败时,尝试次数
FileSectionSize = 1024 * 512//分包大小,当网络较差时,应该适当减小该值
};

fileOperator.MaxSpeed=1024 * 1024;//设置最大限速为1Mb。

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

loopAction.RunAsync();

//此方法会阻塞,直到传输结束,也可以使用PushFileAsync
IResult result = socketClient.GetDmtpFileTransferActor().PushFile(fileOperator);

5.5 客户端之间传输文件

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

此外,**响应中介(一般是服务器)**需要添加路由策略同意路由

【添加路由策略】

.ConfigureContainer(a =>
{
a.AddDmtpRouteService();//添加路由策略
})

【同意路由】

internal class MyPlugin : PluginBase, IDmtpRoutingPlugin
{
public async Task OnDmtpRouting(IDmtpActorObject client, PackageRouterEventArgs e)
{
e.IsPermitOperation = true;//允许路由
await e.InvokeNext();
}
}
注意

如果不添加DmtpRouteService策略,将不会转发任何路由请求,即意味着请求会被直接响应。

其他PullFile或者PushFile操作均和上述一致。

六、断点续传

本文件传输,支持断点续传。能够在传输过程中,暂停(实际上就是取消)传输,下次继续传输。

断点续传的使用步骤如下:

  1. 当本次传输失败、或主动取消时,可以保存fileOperator.ResourceInfo对象属性。
  2. 当下次再次请求传输时,可以将已保存的ResourceInfo对象,先赋值给fileOperator.ResourceInfo属性。那么如果保存路径一致,且文件大小符合续传要求,则可以继续传输。
//关于断点续传
//在执行完PullFile(fileOperator)或PushFile(fileOperator)时。只要返回的结果不是Success。
//那么就意味着传输没有完成。
//而续传的机制就是,在执行传输之前,如果fileOperator.ResourceInfo为空,则开始新的传输。如果不为空,则尝试续传。
//而对于失败的传输,未完成的信息都在fileOperator.ResourceInfo中。
//所以我们可以使用一个变量(或字典)来存fileOperator.ResourceInfo的值。
//亦或者可以把ResourceInfo的值持久化。
//然后在重新发起请求传输值前,先对fileOperator.ResourceInfo做有效赋值。即可尝试断点传输。

byte[] cacheBytes;//这就是持久化后的数据。你可以将此数据写入到文件或数据库。
using (var byteBlock=new ByteBlock())
{
fileOperator.ResourceInfo.Save(byteBlock);

cacheBytes = byteBlock.ToArray();
}

//然后想要续传的时候。先把缓存数据转为FileResourceInfo。
using (var byteBlock=new ByteBlock(cacheBytes))
{
var resourceInfo = new FileResourceInfo(byteBlock);
//然后把resourceInfo赋值给新建的FileOperator的ResourceInfo属性。
}
注意

本断点续传不验证文件唯一性,假如在续传前后文件大小不一致,则会导致续传失败。如果文件大小一致,但是内容已发送变化,则可能得到一个错误的文件。所以建议在续传时要么确定文件不会再变化,要么需要在传输完成后进行文件唯一性验证(例如MD5)。

提示

文件续传适用于所有传输类型,无论是客户端对服务器,还是服务器对客户端,还是客户端对客户端。

关于续传持久化

一般来说,续传信息是不需要持久化的。只需要使用一个变量(或字典存一下即可)。但是如果考虑进程退出,或程序崩溃等情况,则需要持久化续传信息。

七、取消传输

传输取消,指的是,在传输过程中,用户主动取消传输。

7.1 Token传递

使用方法非常简单,因为FileOperator中允许传入一个可取消令箭。所以只需要通过CancellationTokenSource,将Token传入,然后直接使用CancellationTokenSource取消即可。

var tokenSource = new CancellationTokenSource();

_=Task.Run(async () =>
{
//此处模拟五秒后自动取消传输
await Task.Delay(5000);
tokenSource.Cancel();
});

var fileOperator = new FileOperator()//实例化本次传输的控制器,用于获取传输进度、速度、状态等。
{
...
Token = tokenSource.Token
};

IResult result = socketClient.GetDmtpFileTransferActor().PullFile(fileOperator);

7.1 使用CancellationFileOperator

CancellationFileOperator是继承FileOperator并且已经实现取消传输的操作器。原理和Token一致,这里只是做了一次封装。

var fileOperator = new CancellationFileOperator()//实例化本次传输的控制器,用于获取传输进度、速度、状态等。
{
...
};

//模拟五秒后自动取消传输,或者直接Cancel
fileOperator.CancelAfter(TimeSpan.FromSeconds(5));

IResult result = socketClient.GetDmtpFileTransferActor().PullFile(fileOperator);
提示

取消传输断点续传结合,就能模拟暂停传输功能。这样,用户就可以在传输过程中,随时暂停和继续传输。并且没有暂停时长限制。

八、小文件传输

小文件传输是指,当传输文件小于设定大小(默认1024*1024字节)时的传输。

为什么要设立小文件传输?与常规文件传输相比,优点在哪里?

常规传输,建立一个传输通道,大约需要传输两端,往返通信4-6次。这在本地局域网中,显得无所谓。但是在互联网环境中,一次ping延迟平均50ms,那么建立一个传输,就大约需要200-300ms。这也就意味着,即使一个文件只有一字节,也需要200ms-300。所以,这明显是不合理的。所以又新增了小文件传输,只要文件在1Mb以内,仅往返1次,就可以完成传输。

8.1 拉取小文件

  1. 直接调用PullSmallFile或者PullSmallFileAsync,获取到实际的文件数据。
  2. 通过Save方法,将数据写入文件。也可以自行保存。
var filePath = "PullSmallFileFromService.Test";
var saveFilePath = "SavePullSmallFileFromService.Test";

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

//此方法会阻塞,直到传输结束,也可以使用PullSmallFileAsync
PullSmallFileResult result = client.GetDmtpFileTransferActor().PullSmallFile(filePath, metadata);
byte[] data = result.Value;//此处即是下载的小文件的实际数据
result.Save(saveFilePath,overwrite:true);//将数据保存到指定路径。

8.2 推送小文件

【推送文件】

  1. 直接调用PushSmallFile或者PushSmallFileAsync。
  2. 返回值即表示是否成功。
var filePath = "PushSmallFileFromService.Test";
var saveFilePath = "SavePushSmallFileFromService.Test";
if (!File.Exists(filePath))//创建测试文件
{
using (var stream = File.OpenWrite(filePath))
{
stream.SetLength(1024);
}
}

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

//此方法会阻塞,直到传输结束,也可以使用PullSmallFileAsync
var result = client.GetDmtpFileTransferActor().PushSmallFile(saveFilePath,new FileInfo(filePath), metadata);
if (result.IsSuccess())
{
//成功
}
提示

小文件传输也支持服务器主推至客户端,和客户端之间相互传输。

九、多线程文件传输

多线程文件传输,顾名思义,就是多个连接链路,共同传输一个文件。

多线程传输的优点是什么?和常规文件传输相比,场景有哪些不同?

首先,常规文件传输是基于单个连接链路的,所以,单个连接的传输速率上限,就是常规传输的上限。一般来说,局域网当中,单个连接即可占满所有带宽,所以这时候多线程传输和常规传输并无差别。但是,在云服务器,或者在有流量均衡算法的网络中,每个连接的最大速率不是带宽的最大速率,那么这时候,两个差距是比较大的。

例如,我自己租的一个单核云服务器,它的单个连接速率只有1Mb,但是弹性带宽却有10Mb。宏观表象就是,一个客户端连接时,可以用1Mb带宽,两个客户端连接时,就可以用2Mb。那么这时候,多线程传输就显得格外重要了。

9.1 创建多链路连接器

因为是多链路传输,所以,就必须建立多个客户端的连接到服务器。这里使用已经封装好的通信模型ClientFactory。

ClientFactory的通信模型使用的是一个主通信端+多个传输客户端。

对于客户端的配置,请详细参考创建DmtpClient工厂

var clientFactory = new TcpDmtpClientFactory()
{
MinCount = 5,
MaxCount = 10,
OnGetTransferConfig = () => //配置辅助通信
{
return new TouchSocketConfig()
.SetRemoteIPHost("127.0.0.1:7789")
.SetDmtpOption(new DmtpOption()
{
VerifyToken = "File"//连接验证口令。
})
.ConfigurePlugins(a =>
{
a.UseDmtpFileTransfer();
});
}
};
clientFactory.MainConfig//配置主通信
.SetRemoteIPHost("127.0.0.1:7789")
.SetDmtpOption(new DmtpOption()
{
VerifyToken = "Rpc"//连接验证口令。
})
.ConfigurePlugins(a =>
{
a.UseDmtpFileTransfer();
});

9.2 多线程请求文件

using var clientFactory = CreateClientFactory();
Result resultCon = clientFactory.CheckStatus();//检验连接状态,一般当主通行器连接时,即认为在连接状态。
if (!resultCon.IsSuccess())
{
//没有连接
return;
}
ConsoleLogger.Default.Info("开始从服务器下载文件");
var filePath = "MultithreadingClientPullFileFromService.Test";
var saveFilePath = "SaveMultithreadingClientPullFileFromService.Test";

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

var fileOperator = new MultithreadingFileOperator//实例化本次传输的控制器,用于获取传输进度、速度、
{
SavePath = saveFilePath,//客户端本地保存路径
ResourcePath = filePath,//请求文件的资源路径
Metadata = metadata,//传递到服务器的元数据
Timeout = TimeSpan.FromSeconds(60),//传输超时时长
TryCount = 10,//当遇到失败时,尝试次数
FileSectionSize = 1024 * 512,//分包大小,当网络较差时,应该适当减小该值
MultithreadingCount = 10//多线程数量
};

fileOperator.SetMaxSpeed(1024*1024);//设置最大限速为1Mb。

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

loopAction.RunAsync();

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

ConsoleLogger.Default.Info($"从服务器下载文件结束,{result}");

9.3 多线程推送文件

using var clientFactory = CreateClientFactory();
Result resultCon = clientFactory.CheckStatus();//检验连接状态,一般当主通行器连接时,即认为在连接状态。

if (!resultCon.IsSuccess())
{
//没有连接
return;
}
ConsoleLogger.Default.Info("开始向服务器推送文件");
var filePath = "MultithreadingClientPushFileFromService.Test";
var saveFilePath = "SaveMultithreadingClientPushFileFromService.Test";

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

var fileOperator = new MultithreadingFileOperator//实例化本次传输的控制器,用于获取传输进度、速度、状态等
{
SavePath = saveFilePath,//客户端本地保存路径
ResourcePath = filePath,//请求文件的资源路径
Metadata = metadata,//传递到服务器的元数据
Timeout = TimeSpan.FromSeconds(60),//传输超时时长
TryCount = 10,//当遇到失败时,尝试次数
FileSectionSize = 1024 * 512,//分包大小,当网络较差时,应该适当减小该值
MultithreadingCount = 10//多线程数量
};

fileOperator.SetMaxSpeed(1024*1024);//设置最大限速为1Mb。

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

loopAction.RunAsync();

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

9.4 客户端之间传输文件

该功能也支持客户端之间互相传输。使用方法基本一致,需要额外指定目标Id,以及获取传输的Id集合即可。

多线程的客户端之间传输文件,不像其他操作类型那么简单。因为除了需要指定目的Id外,还需要指定获取目标Id的,传输客户端的Id集合,不然,获取数据的时候,仍然会是单线程工作的。

【获取目标传输客户端的Id集合】 在TcpDmtpClientFactory属性中,有个OnFindTransferIds。通过实现该属性,使其能够获取到对应客户端的传输客户端Id集合(下列代码为模拟值,要具体实现该功能,还得自行实现)。

clientFactory.SetFindTransferIds((client, targetId) =>
{
//此处的操作不唯一,可能需要rpc实现。
//其目的比较简单,就是获取到targetId对应的主客户端的所有传输客户端的Id集合。
//这样就实现了多个客户端向多个客户端传输文件的目的。

return new string[] { targetId };//此处为模拟结果。
});
信息

多线程传输,目前暂不支持服务器主动向客户端传输。

本文示例Demo