1. 程式人生 > 程式設計 >C#語言使用gRPC、protobuf(Google Protocol Buffers)實現檔案傳輸功能

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檔案,檔案內容如下:

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方法,以供客戶端進行呼叫。

/// <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,buffer.Length);//讀取資料
   if (readSise > 0)//讀取到了資料,有資料需要傳送
   {
    reply.Block = ++readTimes;
    reply.Content = Google.Protobuf.ByteString.CopyFrom(buffer,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函式中新增服務:

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",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,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,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,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,buffer.Length);//讀取資料
   if(readSize>0)
   {
    reply.Block = ++readTimes;//更新標記,傳送資料
    reply.Content = Google.Protobuf.ByteString.CopyFrom(buffer,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);
 }
}

  現在可以在客戶端窗體內進行呼叫了:

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,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#檔案傳輸內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!