---
id: multithreadingfiletransfer
title: 多线程文件传输
---
import Tag from "@site/src/components/Tag.js";

## 一、说明 <Tag>Pro</Tag>

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

**多线程传输的优点是什么？和**[**常规文件传输**](./transferfile.mdx)**相比，场景有哪些不同？**

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

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

其次，多线程传输是无状态的，所以对于断线重连，换网重连等操作，是完全无感的。


## 二、使用

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

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

对于客户端的配置，请详细参考[创建TouchRpc客户端](./createtouchrpcclient.mdx)

```csharp
private TcpTouchRpcClientFactory CreateClientFactory()
{
    TcpTouchRpcClientFactory clientFactory = new TcpTouchRpcClientFactory()
    {
        MinCount = 5,
        MaxCount = 10,
        OnGetTransferConfig = () => //配置辅助通信
        {
            return new TouchSocketConfig()
               .SetRemoteIPHost("tcp://127.0.0.1:7789");
        }
    };
    clientFactory.MainConfig
                    .SetRemoteIPHost("tcp://127.0.0.1:7789");//配置主通信
    return clientFactory;
}
```

【拉取文件】

```csharp
TcpTouchRpcClientFactory clientFactory = CreateClientFactory();
var resultCon = clientFactory.CheckStatus();//检验主通信器连接状态。默认如果没有连接，则会建立。
if (resultCon.IsSuccess())
{
    var fileOperator = new MultithreadingFileOperator()
    {
        ResourcePath = path,//请求资源路径
        SavePath = savePath,//本地保存路径
    };
    //此处相当于Timer，每秒获取传输的速度和进度
	LoopAction loopAction = LoopAction.CreateLoopAction(1000, (loop) => 
	{
   	    if (fileOperator.IsEnd)
    	{
       	    loop.SafeDispose();
   	    }

   	     Console.WriteLine($"速度：{fileOperator.Speed()}，进度:{fileOperator.Progress}");
	});
	_=loopAction.RunAsync();

    Result result= await clientFactory.PullFileAsync(fileOperator);
    if (result.IsSuccess())
    {
        MessageBox.Show(result.ToString());
    }
}
else
{
    MessageBox.Show(resultCon.ToString());
}

```

【推送文件】

```csharp
TcpTouchRpcClientFactory clientFactory = CreateClientFactory();
var resultCon = clientFactory.CheckStatus();//检验主通信器连接状态。默认如果没有连接，则会建立。
if (resultCon.IsSuccess())
{
    var fileOperator = new MultithreadingFileOperator()
    {
        ResourcePath = path,
        SavePath = savePath,
    };
    
    //此处相当于Timer，每秒获取传输的速度和进度
	LoopAction loopAction = LoopAction.CreateLoopAction(1000, (loop) => 
	{
   	    if (fileOperator.IsEnd)
    	{
       	    loop.SafeDispose();
   	    }

   	     Console.WriteLine($"速度：{fileOperator.Speed()}，进度:{fileOperator.Progress}");
	});
	_=loopAction.RunAsync();

    Result result=await clientFactory.PushFileAsync(fileOperator);
    if (result.IsSuccess())
    {
        MessageBox.Show(result.ToString());
    }
}
else
{
    MessageBox.Show(resultCon.ToString());
}
```

## 三、客户端之间传输文件

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

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

此外，**服务器**也需要同意路由

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

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

```csharp
TcpTouchRpcClientFactory clientFactory = new TcpTouchRpcClientFactory()
{
    MinCount = 5,
    MaxCount = 10,
    OnGetMainConfig = () =>//配置主通信
    {
        return new TouchSocketConfig()
        .SetRemoteIPHost("tcp://127.0.0.1:7789")
        .SetVerifyToken("FileService");
    },
    OnGetTransferConfig = () => //配置辅助通信
    {
        return new TouchSocketConfig()
           .SetRemoteIPHost("tcp://127.0.0.1:7789")
           .SetVerifyToken("FileService");
    }
    ,
    OnFindTransferIds = (client,targetId) => 
    {
        //此处的操作不唯一，可能需要rpc实现。
        //其目的比较简单，就是获取到targetId对应的主客户端的所有传输客户端的Id集合。
        //这样就实现了多个客户端向多个客户端传输文件的目的。

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