服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - C# - C#语言使用gRPC、protobuf(Google Protocol Buffers)实现文件传输功能

C#语言使用gRPC、protobuf(Google Protocol Buffers)实现文件传输功能

2022-10-12 13:26一只独行的猿 C#

这篇文章主要介绍了C#语言使用gRPC、protobuf(Google Protocol Buffers)实现文件传输功能,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  初识gRPC还是一位做JAVA的同事在项目中用到了它,为了C#的客户端程序和java的服务器程序进行通信和数据交换,当时还是对方编译成C#,我直接调用。

  后来,自己下来做了C#版本gRPC编写,搜了很多资料,但许多都是从入门开始?调用说“Say Hi!”这种官方标准的入门示例,然后遇到各种问题……

  关于gRPC和Protobuf介绍,就不介绍了,网络上一搜一大把,随便一抓都是标准的官方,所以直接从使用说起。

  gPRC源代码:https://github.com/grpc/grpc;

  protobuf的代码仓库:

github仓库地址:https://github.com/google/protobuf

Google下载protobuff下载地址:https://developers.google.com/protocol-buffers/docs/downloads。

1、新建解决方案

  分别在VS中新建解决方案:GrpcTest;再在解决方案中新建三个项目:GrpcClient、GrpcServer、GrpcService,对应的分别是客户端(wpf窗体程序)、服务端(控制台程序)、gRPC服务者(控制台程序)。在GrpcClient和GrpcServer项目中添加对GrpcService的引用。

  在VS中对3个项目添加工具包引用:右键点击“解决方案gRPCDemo”,点击“管理解决方案的NuGet程序包”,在浏览中分别搜索"Grpc"、"Grpc.Tools"、"Google.Protobuf",然后点击右面项目,全选,再点击安装(也可以用视图 -> 窗口 -> 程序包管理器控制台 中的"Install-Package Grpc"进行这一步,这里不提供这种方法,有兴趣自己百度)。

2、proto文件的语法

  对于使用gRPC的通信框架,需要使用到对应的通信文件。在gRPC中,使用到的是proto格式的文件,对应的自然有其相应的语法。本文不详细阐述该文件的语法,感兴趣可以去官网看标准的语法,这儿有一个链接,中文翻译比较全的https://www.codercto.com/a/45372.html。需要对其文章内的1.3进行补充下:

  • required:一个格式良好的消息一定要含有1个这种字段。表示该值是必须要设置的。
  • optional:消息格式中该字段可以有0个或1个值(不超过1个)。
  • repeated:在一个格式良好的消息中,这种字段可以重复任意多次(包括0次)。重复的值的顺序会被保留。表示该值可以重复,相当于java中的List。

  本示例项目实现文件传输,因此在项目GrpcService中添加一个FileTransfer.proto文件,文件内容如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
syntax = "proto3";
package GrpcService;
 
service FileTransfer{
 rpc FileDownload (FileRequest) returns (stream FileReply);
 rpc FileUpload (stream FileReply) returns(stream FileReturn);
}
 
//请求下载文件时,所需下载文件的文件名称集合
message FileRequest{
 repeated string FileNames=1;//文件名集合
 //repeated重复字段 类似链表;optional可有可无的字段;required必要设置字段
 string Mark = 2;//携带的包
}
 
//下载和上传文件时的应答数据
message FileReply{
 string FileName=1;//文件名
 int32 Block = 2;//标记---第几个数据
 bytes Content = 3;//数据
 string Mark = 4;//携带的包
 }
 
//数据上传时的返回值
message FileReturn{
 string FileName=1;//文件名
 string Mark = 2;//携带的包
}

3、编译proto文件为C#代码

  proto文件仅仅只是定义了相关的数据,如果需要在代码中使用该格式,就需要将它编译成C#代码文件。

    PS:网上可以找到的编译,需要下载相关的代码,见博文。其他的也较为繁琐,所以按照自己理解的来写了。注意,我的项目是放在D盘根目录下的。

  首先打开cmd窗口,然后在窗口中输入:D:\GrpcTest\packages\Grpc.Tools.2.32.0\tools\windows_x86\protoc.exe -ID:\GrpcTest\GrpcService --csharp_out D:\GrpcTest\GrpcService D:\GrpcTest\GrpcService\FileTransfer.proto --grpc_out D:\GrpcTest\GrpcService --plugin=protoc-gen-grpc=D:\GrpcTest\packages\Grpc.Tools.2.32.0\tools\windows_x86\grpc_csharp_plugin.exe

  输入上文后,按enter键,回车编译。

  命令解读:

  • D:\GrpcTest\packages\Grpc.Tools.2.32.0\tools\windows_x86\protoc.exe :调用的编译程序路径,注意版本不同路径稍有不一样。
  • -ID:\GrpcTest\GrpcService :-I 指定一个或者多个目录,用来搜索.proto文件的。所以上面那行的D:\GrpcTest\GrpcService\FileTransfer.proto 已经可以换成FileTransfer.proto了,因为-I已经指定了。注意:如果不指定,那就是当前目录。
  •  --csharp_out D:\GrpcTest\GrpcService D:\GrpcTest\GrpcService\FileTransfer.proto :(--csharp_out)生成C#代码、存放路径、文件。当然还能cpp_out、java_out、javanano_out、js_out、objc_out、php_out、python_out、ruby_out 这时候你就应该知道,可以支持多语言的,才用的,生成一些文件,然后给各个语言平台调用。参数1(D:\GrpcTest\GrpcService)是输出路径,参数2(D:\GrpcTest\GrpcService\FileTransfer.proto)是proto的文件名或者路径。
  •  --grpc_out D:\GrpcTest\GrpcService :grpc_out是跟服务相关,创建,调用,绑定,实现相关。生成的玩意叫xxxGrpc.cs。与前面的区别是csharp_out是输出类似于咱们平时写的实体类,接口,定义之类的。生成的文件叫xxx.cs
  • --plugin=protoc-gen-grpc=D:\GrpcTest\packages\Grpc.Tools.2.32.0\tools\windows_x86\grpc_csharp_plugin.exe :这个就是csharp的插件,python有python的,java有java的。

  编译后,会在新增两个文件(文件位置与你的输出位置有关),并将两个文件加入到GrpcService项目中去:

    C#语言使用gRPC、protobuf(Google Protocol Buffers)实现文件传输功能

4、编写服务端的文件传输服务

  在GrpcServer项目中,新建一个FileImpl并继承自GrpcService.FileTransfer.FileTransferBase,然后复写其方法FileDownload和FileUpload方法,以供客户端进行调用。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
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
105
106
107
108
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
160
161
162
163
164
165
166
167
168
169
170
171
172
/// <summary>
/// 文件传输类
/// </summary>
class FileImpl:GrpcService.FileTransfer.FileTransferBase
{
 /// <summary>
 /// 文件下载
 /// </summary>
 /// <param name="request">下载请求</param>
 /// <param name="responseStream">文件写入流</param>
 /// <param name="context">站点上下文</param>
 /// <returns></returns>
 public override async Task FileDownload(FileRequest request, global::Grpc.Core.IServerStreamWriter<FileReply> responseStream, global::Grpc.Core.ServerCallContext context)
 {
 List<string> lstSuccFiles = new List<string>();//传输成功的文件
 DateTime startTime = DateTime.Now;//传输文件的起始时间
 int chunkSize = 1024 * 1024;//每次读取的数据
 var buffer = new byte[chunkSize];//数据缓冲区
 FileStream fs = null;//文件流
 try
 {
  //reply.Block数字的含义是服务器和客户端约定的
  for (int i = 0; i < request.FileNames.Count; i++)
  {
  string fileName = request.FileNames[i];//文件名
  string filePath = Path.GetFullPath($".//Files\\{fileName}");//文件路径
  FileReply reply = new FileReply
  {
   FileName = fileName,
   Mark = request.Mark
  };//应答数据
  Console.WriteLine($"{request.Mark},下载文件:{filePath}");//写入日志,下载文件
  if (File.Exists(filePath))
  {
   fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read, chunkSize, useAsync: true);
 
   //fs.Length 可以告诉客户端所传文件大小
   int readTimes = 0;//读取次数
   while (true)
   {
   int readSise = fs.Read(buffer, 0, buffer.Length);//读取数据
   if (readSise > 0)//读取到了数据,有数据需要发送
   {
    reply.Block = ++readTimes;
    reply.Content = Google.Protobuf.ByteString.CopyFrom(buffer, 0, readSise);
    await responseStream.WriteAsync(reply);
   }
   else//没有数据了,就告诉对方,读取完了
   {
    reply.Block = 0;
    reply.Content = Google.Protobuf.ByteString.Empty;
    await responseStream.WriteAsync(reply);
    lstSuccFiles.Add(fileName);
    Console.WriteLine($"{request.Mark},完成发送文件:{filePath}");//日志,记录发送成功
    break;//跳出去
   }
   }
   fs?.Close();
  }
  else
  {
   Console.WriteLine($"文件【{filePath}】不存在。");//写入日志,文件不存在
   reply.Block = -1;//-1的标记为文件不存在
   await responseStream.WriteAsync(reply);//告诉客户端,文件状态
  }
  }
  //告诉客户端,文件传输完成
  await responseStream.WriteAsync(new FileReply
  {
  FileName = string.Empty,
  Block = -2,//告诉客户端,文件已经传输完成
  Content = Google.Protobuf.ByteString.Empty,
  Mark = request.Mark
  });
 }
 catch(Exception ex)
 {
  Console.WriteLine($"{request.Mark},发生异常({ex.GetType()}):{ex.Message}");
 }
 finally
 {
  fs?.Dispose();
 }
 Console.WriteLine($"{request.Mark},文件传输完成。共计【{lstSuccFiles.Count / request.FileNames.Count}】,耗时:{DateTime.Now - startTime}");
 }
 
 
 /// <summary>
 /// 上传文件
 /// </summary>
 /// <param name="requestStream">请求流</param>
 /// <param name="responseStream">响应流</param>
 /// <param name="context">站点上下文</param>
 /// <returns></returns>
 public override async Task FileUpload(global::Grpc.Core.IAsyncStreamReader<FileReply> requestStream, global::Grpc.Core.IServerStreamWriter<FileReturn> responseStream, global::Grpc.Core.ServerCallContext context)
 {
 List<string> lstFilesName = new List<string>();//文件名
 List<FileReply> lstContents = new List<FileReply>();//数据集合
 
 FileStream fs = null;
 DateTime startTime = DateTime.Now;//开始时间
 string mark = string.Empty;
 string savePath = string.Empty;
 try
 {
  //reply.Block数字的含义是服务器和客户端约定的
  while (await requestStream.MoveNext())//读取数据
  {
  var reply = requestStream.Current;
  mark = reply.Mark;
  if (reply.Block == -2)//传输完成
  {
   Console.WriteLine($"{mark},完成上传文件。共计【{lstFilesName.Count}】个,耗时:{DateTime.Now-startTime}");
   break;
  }
  else if (reply.Block == -1)//取消了传输
  {
   Console.WriteLine($"文件【{reply.FileName}】取消传输!");//写入日志
   lstContents.Clear();
   fs?.Close();//释放文件流
   if (!string.IsNullOrEmpty(savePath) && File.Exists(savePath))//如果传输不成功,删除该文件
   {
   File.Delete(savePath);
   }
   savePath = string.Empty;
   break;
  }
  else if(reply.Block==0)//文件传输完成
  {
   if (lstContents.Any())//如果还有数据,就写入文件
   {
   lstContents.OrderBy(c => c.Block).ToList().ForEach(c => c.Content.WriteTo(fs));
   lstContents.Clear();
   }
   lstFilesName.Add(savePath);//传输成功的文件
   fs?.Close();//释放文件流
   savePath = string.Empty;
 
   //告知客户端,已经完成传输
   await responseStream.WriteAsync(new FileReturn
   {
   FileName= reply.FileName,
   Mark=mark
   });
  }
  else
  {
   if(string.IsNullOrEmpty(savePath))//有新文件来了
   {
   savePath = Path.GetFullPath($".//Files\\{reply.FileName}");//文件路径
   fs = new FileStream(savePath, FileMode.Create, FileAccess.ReadWrite);
   Console.WriteLine($"{mark},上传文件:{savePath},{DateTime.UtcNow.ToString("HH:mm:ss:ffff")}");
   }
   lstContents.Add(reply);//加入链表
   if (lstContents.Count() >= 20)//每个包1M,20M为一个集合,一起写入数据。
   {
   lstContents.OrderBy(c => c.Block).ToList().ForEach(c => c.Content.WriteTo(fs));
   lstContents.Clear();
   }
  }
  }
 }
 catch(Exception ex)
 {
  Console.WriteLine($"{mark},发生异常({ex.GetType()}):{ex.Message}");
 }
 finally
 {
  fs?.Dispose();
 }
 }
}

  在main函数中添加服务:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Program
{
 static void Main(string[] args)
 {
 //提供服务
 Server server = new Server()
 {
  Services = {GrpcService.FileTransfer.BindService(new FileImpl())},
  Ports = {new ServerPort("127.0.0.1",50000,ServerCredentials.Insecure)}
 };
 //服务开始
 server.Start();
 
 while(Console.ReadLine().Trim().ToLower()!="exit")
 {
 
 }
 //结束服务
 server.ShutdownAsync();
 }
}

5、编写客户端的文件传输功能

  首先定义一个文件传输结果类TransferResult<T>,用于存放文件的传输结果。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/// <summary>
/// 传输结果
/// </summary>
/// <typeparam name="T"></typeparam>
class TransferResult<T>
{
 /// <summary>
 /// 传输是否成功
 /// </summary>
 public bool IsSuccessful { get; set; }
 /// <summary>
 /// 消息
 /// </summary>
 public string Message { get; set; }
 
 /// <summary>
 /// 标记类型
 /// </summary>
 public T Tag { get; set; } = default;
}

  然后在GrpcClinet项目中添加一个FileTransfer的类,并实现相关方法:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
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
105
106
107
108
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
160
161
162
163
164
165
166
167
168
169
170
171
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
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
class FileTransfer
{
 
 /// <summary>
 /// 获取通信客户端
 /// </summary>
 /// <returns>通信频道、客户端</returns>
 static (Channel, GrpcService.FileTransfer.FileTransferClient) GetClient()
 {
 //侦听IP和端口要和服务器一致
 Channel channel = new Channel("127.0.0.1", 50000, ChannelCredentials.Insecure);
 var client = new GrpcService.FileTransfer.FileTransferClient(channel);
 return (channel, client);
 }
 
 /// <summary>
 /// 下载文件
 /// </summary>
 /// <param name="fileNames">需要下载的文件集合</param>
 /// <param name="mark">标记</param>
 /// <param name="saveDirectoryPath">保存路径</param>
 /// <param name="cancellationToken">异步取消命令</param>
 /// <returns>下载任务(是否成功、原因、失败文件名)</returns>
 public static async Task<TransferResult<List<string>>> FileDownload(List<string> fileNames, string mark, string saveDirectoryPath, System.Threading.CancellationToken cancellationToken = new System.Threading.CancellationToken())
 {
 var result = new TransferResult<List<string>>() { Message = $"文件保存路径不正确:{saveDirectoryPath}" };
 if (!System.IO.Directory.Exists(saveDirectoryPath))
 {
  return await Task.Run(() => result);//文件路径不存在
 }
 if (fileNames.Count == 0)
 {
  result.Message = "未包含任何文件";
  return await Task.Run(() => result);//文件路径不存在
 }
 result.Message = "未能连接到服务器";
 FileRequest request = new FileRequest() { Mark = mark };//请求数据
 request.FileNames.AddRange(fileNames);//将需要下载的文件名赋值
 var lstSuccFiles = new List<string>();//传输成功的文件
 string savePath = string.Empty;//保存路径
 System.IO.FileStream fs = null;
 Channel channel = null;//申明通信频道
 GrpcService.FileTransfer.FileTransferClient client = null;
 DateTime startTime = DateTime.Now;
 try
 {
  (channel, client) = GetClient();
  using (var call = client.FileDownload(request))
  {
  List<FileReply> lstContents = new List<FileReply>();//存放接收的数据
  var reaponseStream = call.ResponseStream;
  //reaponseStream.Current.Block数字的含义是服务器和客户端约定的
  while (await reaponseStream.MoveNext(cancellationToken))//开始接收数据
  {
   if (cancellationToken.IsCancellationRequested)
   {
   break;
   }
   if (reaponseStream.Current.Block == -2)//说明文件已经传输完成了
   {
   result.Message = $"完成下载任务【{lstSuccFiles.Count}/{fileNames.Count}】,耗时:{DateTime.Now - startTime}";
   result.IsSuccessful = true;
   break;
   }
   else if (reaponseStream.Current.Block == -1)//当前文件传输错误
   {
   Console.WriteLine($"文件【{reaponseStream.Current.FileName}】传输失败!");//写入日志
   lstContents.Clear();
   fs?.Close();//释放文件流
   if (!string.IsNullOrEmpty(savePath) && File.Exists(savePath))//如果传输不成功,删除该文件
   {
    File.Delete(savePath);
   }
   savePath = string.Empty;
   }
   else if (reaponseStream.Current.Block == 0)//当前文件传输完成
   {
   if (lstContents.Any())//如果还有数据,就写入文件
   {
    lstContents.OrderBy(c => c.Block).ToList().ForEach(c => c.Content.WriteTo(fs));
    lstContents.Clear();
   }
   lstSuccFiles.Add(reaponseStream.Current.FileName);//传输成功的文件
   fs?.Close();//释放文件流
   savePath = string.Empty;
   }
   else//有文件数据过来
   {
   if (string.IsNullOrEmpty(savePath))//如果字节流为空,则说明时新的文件数据来了
   {
    savePath = Path.Combine(saveDirectoryPath, reaponseStream.Current.FileName);
    fs = new FileStream(savePath, FileMode.Create, FileAccess.ReadWrite);
   }
   lstContents.Add(reaponseStream.Current);//加入链表
   if (lstContents.Count() >= 20)//每个包1M,20M为一个集合,一起写入数据。
   {
    lstContents.OrderBy(c => c.Block).ToList().ForEach(c => c.Content.WriteTo(fs));
    lstContents.Clear();
   }
   }
  }
  }
  fs?.Close();//释放文件流
  if (!result.IsSuccessful &&!string.IsNullOrEmpty(savePath)&& File.Exists(savePath))//如果传输不成功,那么久删除该文件
  {
  File.Delete(savePath);
  }
 }
 catch (Exception ex)
 {
  if (cancellationToken.IsCancellationRequested)
  {
  fs?.Close();//释放文件流
  result.IsSuccessful = false;
  result.Message = $"用户取消下载。已完成下载【{lstSuccFiles.Count}/{fileNames.Count}】,耗时:{DateTime.Now - startTime}";
  }
  else
  {
  result.Message = $"文件传输发生异常:{ex.Message}";
  }
 }
 finally
 {
  fs?.Dispose();
 }
 result.Tag = fileNames.Except(lstSuccFiles).ToList();//获取失败文件集合
 //关闭通信、并返回结果
 return await channel?.ShutdownAsync().ContinueWith(t => result);
 }
 
 
 /// <summary>
 /// 文件上传
 /// </summary>
 /// <param name="filesPath">文件路径</param>
 /// <param name="mark">标记</param>
 /// <param name="cancellationToken">异步取消命令</param>
 /// <returns>下载任务(是否成功、原因、成功的文件名)</returns>
 public static async Task<TransferResult<List<string>>> FileUpload(List<string> filesPath, string mark, System.Threading.CancellationToken cancellationToken=new System.Threading.CancellationToken())
 {
 var result = new TransferResult<List<string>> { Message = "没有文件需要下载" };
 if (filesPath.Count == 0)
 {
  return await Task.Run(() => result);//没有文件需要下载
 }
 result.Message = "未能连接到服务器。";
 var lstSuccFiles = new List<string>();//传输成功的文件
 int chunkSize = 1024 * 1024;
 byte[] buffer = new byte[chunkSize];//每次发送的大小
 FileStream fs = null;//文件流
 Channel channel = null;//申明通信频道
 GrpcService.FileTransfer.FileTransferClient client = null;
 DateTime startTime = DateTime.Now;
 try
 {
  (channel, client) = GetClient();
  using(var stream=client.FileUpload())//连接上传文件的客户端
  {
  //reply.Block数字的含义是服务器和客户端约定的
  foreach (var filePath in filesPath)//遍历集合
  {
   if(cancellationToken.IsCancellationRequested)
   break;//取消了传输
   FileReply reply = new FileReply()
   {
   FileName=Path.GetFileName(filePath),
   Mark=mark
   };
   if(!File.Exists(filePath))//文件不存在,继续下一轮的发送
   {
   Console.WriteLine($"文件不存在:{filePath}");//写入日志
   continue;
   }
   fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read, chunkSize, useAsync: true);
   int readTimes = 0;
   while(true)
   {
   if (cancellationToken.IsCancellationRequested)
   {
    reply.Block = -1;//取消了传输
    reply.Content = Google.Protobuf.ByteString.Empty;
    await stream.RequestStream.WriteAsync(reply);//发送取消传输的命令
    break;//取消了传输
   }
   int readSize = fs.Read(buffer, 0, buffer.Length);//读取数据
   if(readSize>0)
   {
    reply.Block = ++readTimes;//更新标记,发送数据
    reply.Content = Google.Protobuf.ByteString.CopyFrom(buffer, 0, readSize);
    await stream.RequestStream.WriteAsync(reply);
   }
   else
   {
    Console.WriteLine($"完成文件【{filePath}】的上传。");
    reply.Block = 0;//传送本次文件发送结束的标记
    reply.Content = Google.Protobuf.ByteString.Empty;
    await stream.RequestStream.WriteAsync(reply);//发送结束标记
    //等待服务器回传
    await stream.ResponseStream.MoveNext(cancellationToken);
    if(stream.ResponseStream.Current!=null&&stream.ResponseStream.Current.Mark==mark)
    {
    lstSuccFiles.Add(filePath);//记录成功的文件
    }
    break;//发送下一个文件
   }
   }
   fs?.Close();
  }
  if (!cancellationToken.IsCancellationRequested)
  {
   result.IsSuccessful = true;
   result.Message = $"完成文件上传。共计【{lstSuccFiles.Count}/{filesPath.Count}】,耗时:{DateTime.Now - startTime}";
 
   await stream.RequestStream.WriteAsync(new FileReply
   {
   Block = -2,//传输结束
   Mark = mark
   }) ;//发送结束标记
  }
  }
 }
 catch(Exception ex)
 {
  if (cancellationToken.IsCancellationRequested)
  {
  fs?.Close();//释放文件流
  result.IsSuccessful = false;
  result.Message = $"用户取消了上传文件。已完成【{lstSuccFiles.Count}/{filesPath.Count}】,耗时:{DateTime.Now - startTime}";
  }
  else
  {
  result.Message = $"文件上传发生异常({ex.GetType()}):{ex.Message}";
  }
 }
 finally
 {
  fs?.Dispose();
 }
 Console.WriteLine(result.Message);
 result.Tag = lstSuccFiles;
 //关闭通信、并返回结果
 return await channel?.ShutdownAsync().ContinueWith(t => result);
 }
}

  现在可以在客户端窗体内进行调用了:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
private string GetFilePath()
{
 // Create OpenFileDialog
 Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog();
 
 // Set filter for file extension and default file extension
 dlg.Title = "选择文件";
 dlg.Filter = "所有文件(*.*)|*.*";
 dlg.FileName = "选择文件夹.";
 dlg.FilterIndex = 1;
 dlg.ValidateNames = false;
 dlg.CheckFileExists = false;
 dlg.CheckPathExists = true;
 dlg.Multiselect = false;//允许同时选择多个文件
 
 // Display OpenFileDialog by calling ShowDialog method
 Nullable<bool> result = dlg.ShowDialog();
 
 // Get the selected file name and display in a TextBox
 if (result == true)
 {
 // Open document
 return dlg.FileName;
 }
 
 return string.Empty;
}
// 打开文件
private void btnOpenUpload_Click(object sender, RoutedEventArgs e)
{
 lblUploadPath.Content = GetFilePath();
}
CancellationTokenSource uploadTokenSource;
//上传
private async void btnUpload_Click(object sender, RoutedEventArgs e)
{
 lblMessage.Content = string.Empty;
 
 uploadTokenSource = new CancellationTokenSource();
 List<string> fileNames = new List<string>();
 fileNames.Add(lblUploadPath.Content.ToString());
 var result = await ServerNet.FileTransfer.FileUpload(fileNames, "123", uploadTokenSource.Token);
 
 lblMessage.Content = result.Message;
 
 uploadTokenSource = null;
}
//取消上传
private void btnCancelUpload_Click(object sender, RoutedEventArgs e)
{
 uploadTokenSource?.Cancel();
}
 
 
//打开需要下载的文件
private void btnOpenDownload_Click(object sender, RoutedEventArgs e)
{
 txtDownloadPath.Text = GetFilePath();
}
//下载文件
private async void btnDownload_Click(object sender, RoutedEventArgs e)
{
 lblMessage.Content = string.Empty;
 
 downloadTokenSource = new CancellationTokenSource();
 List<string> fileNames = new List<string>();
 fileNames.Add(System.IO.Path.GetFileName(txtDownloadPath.Text));
 var result= await ServerNet.FileTransfer.FileDownload(fileNames, "123", Environment.CurrentDirectory, downloadTokenSource.Token);
 
 lblMessage.Content = result.Message;
 
 downloadTokenSource = null;
}
CancellationTokenSource downloadTokenSource;
//下载取消
private void btnCancelDownload_Click(object sender, RoutedEventArgs e)
{
 downloadTokenSource?.Cancel();
}

6、源代码

  https://files.cnblogs.com/files/pilgrim/GrpcTest.rar

总结

到此这篇关于C#语言使用gRPC、protobuf(Google Protocol Buffers)实现文件传输功能的文章就介绍到这了,更多相关c#文件传输内容请搜索服务器之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持服务器之家!

原文链接:https://www.cnblogs.com/pilgrim/archive/2020/10/23/13855661.html

延伸 · 阅读

精彩推荐
  • C#C#中多态性的实现

    C#中多态性的实现

    这篇文章主要介绍了C#中多态性的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编...

    LLLLL__5682022-08-24
  • C#C#处理Access中事务的方法

    C#处理Access中事务的方法

    这篇文章主要介绍了C#处理Access中事务的方法,涉及C#中事物的实现及使用技巧,具有一定参考借鉴价值,需要的朋友可以参考下...

    baggio709558611742021-10-29
  • C#C#版Windows服务安装卸载小工具

    C#版Windows服务安装卸载小工具

    这篇文章主要为大家推荐了一款C#版Windows服务安装卸载小工具,小巧灵活的控制台程序,希望大家喜欢,感兴趣的小伙伴们可以参考一下...

    韩天伟7592021-12-01
  • C#C#将图片存放到SQL SERVER数据库中的方法

    C#将图片存放到SQL SERVER数据库中的方法

    这篇文章主要介绍了C#将图片存放到SQL SERVER数据库中的方法,以实例形式较为详细的分析了C#保存图片到SQL Server数据库的具体步骤与相关技巧,具有一定参考...

    我心依旧11292021-10-27
  • C#C#中委托的基本用法总结

    C#中委托的基本用法总结

    以下是对C#中委托的基本用法进行了详细的总结分析,需要的朋友可以过来参考下。希望对大家有所帮助...

    C#教程网10312021-01-04
  • C#C# Email邮件发送功能 找回或重置密码功能

    C# Email邮件发送功能 找回或重置密码功能

    这篇文章主要为大家详细介绍了C# Email邮件发送功能,找回或重置密码功能,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    qq_3291533712122022-03-09
  • C#详解C# 泛型中的数据类型判定与转换

    详解C# 泛型中的数据类型判定与转换

    这篇文章主要介绍了C# 泛型中的数据类型判定与转换,文中讲解非常细致,代码帮助大家更好的理解和学习,感兴趣的朋友可以了解下...

    汐夜3682022-09-27
  • C#WindowsForm移动一个没有标题栏的窗口的方法

    WindowsForm移动一个没有标题栏的窗口的方法

    这篇文章主要介绍了WindowsForm移动一个没有标题栏的窗口的方法,文中讲解非常细致,代码帮助大家更好的理解和学习,感兴趣的朋友可以了解下...

    zhuanghamiao4282022-09-27