初识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 。

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

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

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

  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项目中去:

    

 

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

  1. /// <summary>
  2. /// 文件传输类
  3. /// </summary>
  4. class FileImpl:GrpcService.FileTransfer.FileTransferBase
  5. {
  6. /// <summary>
  7. /// 文件下载
  8. /// </summary>
  9. /// <param name="request">下载请求</param>
  10. /// <param name="responseStream">文件写入流</param>
  11. /// <param name="context">站点上下文</param>
  12. /// <returns></returns>
  13. public override async Task FileDownload(FileRequest request, global::Grpc.Core.IServerStreamWriter<FileReply> responseStream, global::Grpc.Core.ServerCallContext context)
  14. {
  15. List<string> lstSuccFiles = new List<string>();//传输成功的文件
  16. DateTime startTime = DateTime.Now;//传输文件的起始时间
  17. int chunkSize = 1024 * 1024;//每次读取的数据
  18. var buffer = new byte[chunkSize];//数据缓冲区
  19. FileStream fs = null;//文件流
  20. try
  21. {
  22. //reply.Block数字的含义是服务器和客户端约定的
  23. for (int i = 0; i < request.FileNames.Count; i++)
  24. {
  25. string fileName = request.FileNames[i];//文件名
  26. string filePath = Path.GetFullPath($".//Files\\{fileName}");//文件路径
  27. FileReply reply = new FileReply
  28. {
  29. FileName = fileName,
  30. Mark = request.Mark
  31. };//应答数据
  32. Console.WriteLine($"{request.Mark},下载文件:{filePath}");//写入日志,下载文件
  33. if (File.Exists(filePath))
  34. {
  35. fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read, chunkSize, useAsync: true);
  36. //fs.Length 可以告诉客户端所传文件大小
  37. int readTimes = 0;//读取次数
  38. while (true)
  39. {
  40. int readSise = fs.Read(buffer, 0, buffer.Length);//读取数据
  41. if (readSise > 0)//读取到了数据,有数据需要发送
  42. {
  43. reply.Block = ++readTimes;
  44. reply.Content = Google.Protobuf.ByteString.CopyFrom(buffer, 0, readSise);
  45. await responseStream.WriteAsync(reply);
  46. }
  47. else//没有数据了,就告诉对方,读取完了
  48. {
  49. reply.Block = 0;
  50. reply.Content = Google.Protobuf.ByteString.Empty;
  51. await responseStream.WriteAsync(reply);
  52. lstSuccFiles.Add(fileName);
  53. Console.WriteLine($"{request.Mark},完成发送文件:{filePath}");//日志,记录发送成功
  54. break;//跳出去
  55. }
  56. }
  57. fs?.Close();
  58. }
  59. else
  60. {
  61. Console.WriteLine($"文件【{filePath}】不存在。");//写入日志,文件不存在
  62. reply.Block = -1;//-1的标记为文件不存在
  63. await responseStream.WriteAsync(reply);//告诉客户端,文件状态
  64. }
  65. }
  66. //告诉客户端,文件传输完成
  67. await responseStream.WriteAsync(new FileReply
  68. {
  69. FileName = string.Empty,
  70. Block = -2,//告诉客户端,文件已经传输完成
  71. Content = Google.Protobuf.ByteString.Empty,
  72. Mark = request.Mark
  73. });
  74. }
  75. catch(Exception ex)
  76. {
  77. Console.WriteLine($"{request.Mark},发生异常({ex.GetType()}):{ex.Message}");
  78. }
  79. finally
  80. {
  81. fs?.Dispose();
  82. }
  83. Console.WriteLine($"{request.Mark},文件传输完成。共计【{lstSuccFiles.Count / request.FileNames.Count}】,耗时:{DateTime.Now - startTime}");
  84. }
  85. /// <summary>
  86. /// 上传文件
  87. /// </summary>
  88. /// <param name="requestStream">请求流</param>
  89. /// <param name="responseStream">响应流</param>
  90. /// <param name="context">站点上下文</param>
  91. /// <returns></returns>
  92. public override async Task FileUpload(global::Grpc.Core.IAsyncStreamReader<FileReply> requestStream, global::Grpc.Core.IServerStreamWriter<FileReturn> responseStream, global::Grpc.Core.ServerCallContext context)
  93. {
  94. List<string> lstFilesName = new List<string>();//文件名
  95. List<FileReply> lstContents = new List<FileReply>();//数据集合
  96. FileStream fs = null;
  97. DateTime startTime = DateTime.Now;//开始时间
  98. string mark = string.Empty;
  99. string savePath = string.Empty;
  100. try
  101. {
  102. //reply.Block数字的含义是服务器和客户端约定的
  103. while (await requestStream.MoveNext())//读取数据
  104. {
  105. var reply = requestStream.Current;
  106. mark = reply.Mark;
  107. if (reply.Block == -2)//传输完成
  108. {
  109. Console.WriteLine($"{mark},完成上传文件。共计【{lstFilesName.Count}】个,耗时:{DateTime.Now-startTime}");
  110. break;
  111. }
  112. else if (reply.Block == -1)//取消了传输
  113. {
  114. Console.WriteLine($"文件【{reply.FileName}】取消传输!");//写入日志
  115. lstContents.Clear();
  116. fs?.Close();//释放文件流
  117. if (!string.IsNullOrEmpty(savePath) && File.Exists(savePath))//如果传输不成功,删除该文件
  118. {
  119. File.Delete(savePath);
  120. }
  121. savePath = string.Empty;
  122. break;
  123. }
  124. else if(reply.Block==0)//文件传输完成
  125. {
  126. if (lstContents.Any())//如果还有数据,就写入文件
  127. {
  128. lstContents.OrderBy(c => c.Block).ToList().ForEach(c => c.Content.WriteTo(fs));
  129. lstContents.Clear();
  130. }
  131. lstFilesName.Add(savePath);//传输成功的文件
  132. fs?.Close();//释放文件流
  133. savePath = string.Empty;
  134. //告知客户端,已经完成传输
  135. await responseStream.WriteAsync(new FileReturn
  136. {
  137. FileName= reply.FileName,
  138. Mark=mark
  139. });
  140. }
  141. else
  142. {
  143. if(string.IsNullOrEmpty(savePath))//有新文件来了
  144. {
  145. savePath = Path.GetFullPath($".//Files\\{reply.FileName}");//文件路径
  146. fs = new FileStream(savePath, FileMode.Create, FileAccess.ReadWrite);
  147. Console.WriteLine($"{mark},上传文件:{savePath},{DateTime.UtcNow.ToString("HH:mm:ss:ffff")}");
  148. }
  149. lstContents.Add(reply);//加入链表
  150. if (lstContents.Count() >= 20)//每个包1M,20M为一个集合,一起写入数据。
  151. {
  152. lstContents.OrderBy(c => c.Block).ToList().ForEach(c => c.Content.WriteTo(fs));
  153. lstContents.Clear();
  154. }
  155. }
  156. }
  157. }
  158. catch(Exception ex)
  159. {
  160. Console.WriteLine($"{mark},发生异常({ex.GetType()}):{ex.Message}");
  161. }
  162. finally
  163. {
  164. fs?.Dispose();
  165. }
  166. }
  167. }

View Code

  在main函数中添加服务:

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

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

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

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

  1. class FileTransfer
  2. {
  3. /// <summary>
  4. /// 获取通信客户端
  5. /// </summary>
  6. /// <returns>通信频道、客户端</returns>
  7. static (Channel, GrpcService.FileTransfer.FileTransferClient) GetClient()
  8. {
  9. //侦听IP和端口要和服务器一致
  10. Channel channel = new Channel("127.0.0.1", 50000, ChannelCredentials.Insecure);
  11. var client = new GrpcService.FileTransfer.FileTransferClient(channel);
  12. return (channel, client);
  13. }
  14. /// <summary>
  15. /// 下载文件
  16. /// </summary>
  17. /// <param name="fileNames">需要下载的文件集合</param>
  18. /// <param name="mark">标记</param>
  19. /// <param name="saveDirectoryPath">保存路径</param>
  20. /// <param name="cancellationToken">异步取消命令</param>
  21. /// <returns>下载任务(是否成功、原因、失败文件名)</returns>
  22. public static async Task<TransferResult<List<string>>> FileDownload(List<string> fileNames, string mark, string saveDirectoryPath, System.Threading.CancellationToken cancellationToken = new System.Threading.CancellationToken())
  23. {
  24. var result = new TransferResult<List<string>>() { Message = $"文件保存路径不正确:{saveDirectoryPath}" };
  25. if (!System.IO.Directory.Exists(saveDirectoryPath))
  26. {
  27. return await Task.Run(() => result);//文件路径不存在
  28. }
  29. if (fileNames.Count == 0)
  30. {
  31. result.Message = "未包含任何文件";
  32. return await Task.Run(() => result);//文件路径不存在
  33. }
  34. result.Message = "未能连接到服务器";
  35. FileRequest request = new FileRequest() { Mark = mark };//请求数据
  36. request.FileNames.AddRange(fileNames);//将需要下载的文件名赋值
  37. var lstSuccFiles = new List<string>();//传输成功的文件
  38. string savePath = string.Empty;//保存路径
  39. System.IO.FileStream fs = null;
  40. Channel channel = null;//申明通信频道
  41. GrpcService.FileTransfer.FileTransferClient client = null;
  42. DateTime startTime = DateTime.Now;
  43. try
  44. {
  45. (channel, client) = GetClient();
  46. using (var call = client.FileDownload(request))
  47. {
  48. List<FileReply> lstContents = new List<FileReply>();//存放接收的数据
  49. var reaponseStream = call.ResponseStream;
  50. //reaponseStream.Current.Block数字的含义是服务器和客户端约定的
  51. while (await reaponseStream.MoveNext(cancellationToken))//开始接收数据
  52. {
  53. if (cancellationToken.IsCancellationRequested)
  54. {
  55. break;
  56. }
  57. if (reaponseStream.Current.Block == -2)//说明文件已经传输完成了
  58. {
  59. result.Message = $"完成下载任务【{lstSuccFiles.Count}/{fileNames.Count}】,耗时:{DateTime.Now - startTime}";
  60. result.IsSuccessful = true;
  61. break;
  62. }
  63. else if (reaponseStream.Current.Block == -1)//当前文件传输错误
  64. {
  65. Console.WriteLine($"文件【{reaponseStream.Current.FileName}】传输失败!");//写入日志
  66. lstContents.Clear();
  67. fs?.Close();//释放文件流
  68. if (!string.IsNullOrEmpty(savePath) && File.Exists(savePath))//如果传输不成功,删除该文件
  69. {
  70. File.Delete(savePath);
  71. }
  72. savePath = string.Empty;
  73. }
  74. else if (reaponseStream.Current.Block == 0)//当前文件传输完成
  75. {
  76. if (lstContents.Any())//如果还有数据,就写入文件
  77. {
  78. lstContents.OrderBy(c => c.Block).ToList().ForEach(c => c.Content.WriteTo(fs));
  79. lstContents.Clear();
  80. }
  81. lstSuccFiles.Add(reaponseStream.Current.FileName);//传输成功的文件
  82. fs?.Close();//释放文件流
  83. savePath = string.Empty;
  84. }
  85. else//有文件数据过来
  86. {
  87. if (string.IsNullOrEmpty(savePath))//如果字节流为空,则说明时新的文件数据来了
  88. {
  89. savePath = Path.Combine(saveDirectoryPath, reaponseStream.Current.FileName);
  90. fs = new FileStream(savePath, FileMode.Create, FileAccess.ReadWrite);
  91. }
  92. lstContents.Add(reaponseStream.Current);//加入链表
  93. if (lstContents.Count() >= 20)//每个包1M,20M为一个集合,一起写入数据。
  94. {
  95. lstContents.OrderBy(c => c.Block).ToList().ForEach(c => c.Content.WriteTo(fs));
  96. lstContents.Clear();
  97. }
  98. }
  99. }
  100. }
  101. fs?.Close();//释放文件流
  102. if (!result.IsSuccessful &&!string.IsNullOrEmpty(savePath)&& File.Exists(savePath))//如果传输不成功,那么久删除该文件
  103. {
  104. File.Delete(savePath);
  105. }
  106. }
  107. catch (Exception ex)
  108. {
  109. if (cancellationToken.IsCancellationRequested)
  110. {
  111. fs?.Close();//释放文件流
  112. result.IsSuccessful = false;
  113. result.Message = $"用户取消下载。已完成下载【{lstSuccFiles.Count}/{fileNames.Count}】,耗时:{DateTime.Now - startTime}";
  114. }
  115. else
  116. {
  117. result.Message = $"文件传输发生异常:{ex.Message}";
  118. }
  119. }
  120. finally
  121. {
  122. fs?.Dispose();
  123. }
  124. result.Tag = fileNames.Except(lstSuccFiles).ToList();//获取失败文件集合
  125. //关闭通信、并返回结果
  126. return await channel?.ShutdownAsync().ContinueWith(t => result);
  127. }
  128. /// <summary>
  129. /// 文件上传
  130. /// </summary>
  131. /// <param name="filesPath">文件路径</param>
  132. /// <param name="mark">标记</param>
  133. /// <param name="cancellationToken">异步取消命令</param>
  134. /// <returns>下载任务(是否成功、原因、成功的文件名)</returns>
  135. public static async Task<TransferResult<List<string>>> FileUpload(List<string> filesPath, string mark, System.Threading.CancellationToken cancellationToken=new System.Threading.CancellationToken())
  136. {
  137. var result = new TransferResult<List<string>> { Message = "没有文件需要下载" };
  138. if (filesPath.Count == 0)
  139. {
  140. return await Task.Run(() => result);//没有文件需要下载
  141. }
  142. result.Message = "未能连接到服务器。";
  143. var lstSuccFiles = new List<string>();//传输成功的文件
  144. int chunkSize = 1024 * 1024;
  145. byte[] buffer = new byte[chunkSize];//每次发送的大小
  146. FileStream fs = null;//文件流
  147. Channel channel = null;//申明通信频道
  148. GrpcService.FileTransfer.FileTransferClient client = null;
  149. DateTime startTime = DateTime.Now;
  150. try
  151. {
  152. (channel, client) = GetClient();
  153. using(var stream=client.FileUpload())//连接上传文件的客户端
  154. {
  155. //reply.Block数字的含义是服务器和客户端约定的
  156. foreach (var filePath in filesPath)//遍历集合
  157. {
  158. if(cancellationToken.IsCancellationRequested)
  159. break;//取消了传输
  160. FileReply reply = new FileReply()
  161. {
  162. FileName=Path.GetFileName(filePath),
  163. Mark=mark
  164. };
  165. if(!File.Exists(filePath))//文件不存在,继续下一轮的发送
  166. {
  167. Console.WriteLine($"文件不存在:{filePath}");//写入日志
  168. continue;
  169. }
  170. fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read, chunkSize, useAsync: true);
  171. int readTimes = 0;
  172. while(true)
  173. {
  174. if (cancellationToken.IsCancellationRequested)
  175. {
  176. reply.Block = -1;//取消了传输
  177. reply.Content = Google.Protobuf.ByteString.Empty;
  178. await stream.RequestStream.WriteAsync(reply);//发送取消传输的命令
  179. break;//取消了传输
  180. }
  181. int readSize = fs.Read(buffer, 0, buffer.Length);//读取数据
  182. if(readSize>0)
  183. {
  184. reply.Block = ++readTimes;//更新标记,发送数据
  185. reply.Content = Google.Protobuf.ByteString.CopyFrom(buffer, 0, readSize);
  186. await stream.RequestStream.WriteAsync(reply);
  187. }
  188. else
  189. {
  190. Console.WriteLine($"完成文件【{filePath}】的上传。");
  191. reply.Block = 0;//传送本次文件发送结束的标记
  192. reply.Content = Google.Protobuf.ByteString.Empty;
  193. await stream.RequestStream.WriteAsync(reply);//发送结束标记
  194. //等待服务器回传
  195. await stream.ResponseStream.MoveNext(cancellationToken);
  196. if(stream.ResponseStream.Current!=null&&stream.ResponseStream.Current.Mark==mark)
  197. {
  198. lstSuccFiles.Add(filePath);//记录成功的文件
  199. }
  200. break;//发送下一个文件
  201. }
  202. }
  203. fs?.Close();
  204. }
  205. if (!cancellationToken.IsCancellationRequested)
  206. {
  207. result.IsSuccessful = true;
  208. result.Message = $"完成文件上传。共计【{lstSuccFiles.Count}/{filesPath.Count}】,耗时:{DateTime.Now - startTime}";
  209. await stream.RequestStream.WriteAsync(new FileReply
  210. {
  211. Block = -2,//传输结束
  212. Mark = mark
  213. }) ;//发送结束标记
  214. }
  215. }
  216. }
  217. catch(Exception ex)
  218. {
  219. if (cancellationToken.IsCancellationRequested)
  220. {
  221. fs?.Close();//释放文件流
  222. result.IsSuccessful = false;
  223. result.Message = $"用户取消了上传文件。已完成【{lstSuccFiles.Count}/{filesPath.Count}】,耗时:{DateTime.Now - startTime}";
  224. }
  225. else
  226. {
  227. result.Message = $"文件上传发生异常({ex.GetType()}):{ex.Message}";
  228. }
  229. }
  230. finally
  231. {
  232. fs?.Dispose();
  233. }
  234. Console.WriteLine(result.Message);
  235. result.Tag = lstSuccFiles;
  236. //关闭通信、并返回结果
  237. return await channel?.ShutdownAsync().ContinueWith(t => result);
  238. }
  239. }

View Code

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

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

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

版权声明:本文为pilgrim原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/pilgrim/p/13855661.html