zoukankan      html  css  js  c++  java
  • 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次)。重复的值的顺序会被保留。表示该值可以重复,相当于C#中的List。

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

    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:GrpcTestpackagesGrpc.Tools.2.32.0 oolswindows_x86protoc.exe -ID:GrpcTestGrpcService --csharp_out D:GrpcTestGrpcService D:GrpcTestGrpcServiceFileTransfer.proto --grpc_out D:GrpcTestGrpcService --plugin=protoc-gen-grpc=D:GrpcTestpackagesGrpc.Tools.2.32.0 oolswindows_x86grpc_csharp_plugin.exe

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

      命令解读:

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

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

        

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

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

    /// <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();
            }
        }
    }
    View Code

      在main函数中添加服务:

    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>,用于存放文件的传输结果。

    /// <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的类,并实现相关方法:

    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();
                            }
                        }
                    }
    
                    if (cancellationToken.IsCancellationRequested)
                    {
                        fs?.Close();//释放文件流
                        result.IsSuccessful = false;
                        result.Message = $"用户取消下载。已完成下载【{lstSuccFiles.Count}/{fileNames.Count}】,耗时:{DateTime.Now - startTime}";
                    }
                }
                fs?.Close();//释放文件流
                if (!result.IsSuccessful && !string.IsNullOrEmpty(savePath) && File.Exists(savePath))//如果传输不成功,那么久删除该文件
                {
                    File.Delete(savePath);
                }
            }
            catch (Exception ex)
            {
                if (!cancellationToken.IsCancellationRequested)
                {
                    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)
                    {
                        fs?.Close();//释放文件流
                        result.IsSuccessful = false;
                        result.Message = $"用户取消了上传文件。已完成【{lstSuccFiles.Count}/{filesPath.Count}】,耗时:{DateTime.Now - startTime}";
                    }
                    else
                    {
                        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)
                {
                    result.Message = $"文件上传发生异常({ex.GetType()}):{ex.Message}";
                }
            }
            finally
            {
                fs?.Dispose();
            }
            Console.WriteLine(result.Message);
            result.Tag = lstSuccFiles;
            //关闭通信、并返回结果
            return await channel?.ShutdownAsync().ContinueWith(t => result);
        }
    }
    View Code

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

    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

  • 相关阅读:
    Windows ETW 学习与使用三
    暗云Ⅳ对SATA磁盘MBR Hook探索
    msahci代码调试备份
    mimikatz使用命令记录
    Windows ETW 学习与使用一
    RabbitMQ 实现延迟队列
    Redis 脱坑指南
    浅析 ThreadLocal
    IDEA2020.2.3破解
    用友NC 模块 简写(瞎猜的)
  • 原文地址:https://www.cnblogs.com/pilgrim/p/13855661.html
Copyright © 2011-2022 走看看