創作內容

23 GP

C# 使用TCP傳輸各類型檔案 (檔案類型與大小無限制,皆可傳送)

作者:貓貓風 ฅ●ω●ฅ│2020-01-09 15:25:50│巴幣:544│人氣:6494
.












本篇為 TCP 傳輸的進階應用

傳輸資料與類型都沒有限制,如果傳輸資料非常大,可能幾GB或TB

可以依照自身記憶體容量調整傳輸buffer的大小,加速傳輸速度

使用自定義的通訊協定進行資料交換

自定義的通訊封包如下

欄位名稱         初始字元          傳輸指令           資料長度         區別字元      資料內容

指令表



整體程式架構 與交握時序圖



實作程式碼

傳送端


  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.ComponentModel;  
  4. using System.Data;  
  5. using System.Drawing;  
  6. using System.Linq;  
  7. using System.Text;  
  8. using System.Windows.Forms;  
  9. using System.IO;  
  10. using System.Net.Sockets;  
  11. using System.Net;  
  12. using System.Threading;  
  13.   
  14. namespace TCP_big_file_transfer  
  15. {  
  16.     public partial class Form1 : Form  
  17.     {  
  18.         public Form1()  
  19.         {  
  20.             InitializeComponent();  
  21.         }  
  22.   
  23.         Thread _file_send;   //宣告執行緒  
  24.   
  25.         private void btn_send_Click(object sender, EventArgs e)  
  26.         {  
  27.             OpenFileDialog dialog = new OpenFileDialog();  
  28.             if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)  
  29.             {  
  30.                 progressBar_upload.Value = 0;  
  31.                 progressBar_upload.Visible = true;  
  32.                 lb_transfer_persent.Visible = true;  
  33.                 lb_file_path.Text = dialog.FileName;  
  34.                 //將檔案選取欄位結果帶入執行緒  
  35.                 _file_send = new Thread(new ParameterizedThreadStart(object_param));   
  36.                 _file_send.Start(dialog);  
  37.   
  38.             }  
  39.         }  
  40.   
  41.         private void object_param(object o)  
  42.         {  
  43.             trans_file((OpenFileDialog)o);  
  44.         }  
  45.   
  46.         private void trans_file(OpenFileDialog dialog)  
  47.         {  
  48.             string Selected_file = dialog.FileName;  
  49.             string File_name = Path.GetFileName(Selected_file);  
  50.             FileStream fs = new FileStream(Selected_file, FileMode.Open);  
  51.             TcpClient tc = new TcpClient(txt_target_IP.Text,
  52.             Convert.ToInt32(txt_target_port.Text));  
  53.             //使用此IP進行資料傳輸與接收  
  54.             NetworkStream ns = tc.GetStream();  
  55.             //將 command與檔名寫入傳輸buffer,示意接收端要開始進行檔案傳輸  
  56.             byte[] data_tosend = CreateDataPacket(Encoding.UTF8.GetBytes("125"),
  57.             Encoding.UTF8.GetBytes(File_name));  
  58.             //將資料送出  
  59.             ns.Write(data_tosend, 0, data_tosend.Length);  
  60.             //刷新傳輸buffer (清空傳輸buffer用來接收資料)  
  61.             ns.Flush();  
  62.             Boolean loop_break = false;  
  63.             while (true)  
  64.             {  
  65.                 if (ns.ReadByte() == 2)
  66.                 //如果資料流有資料 用第一個byte做判斷  2為初始封包byte  
  67.                 {  
  68.                     byte[] cmd_buffer = new byte[3];  
  69.                     //從接收buffer讀取前三個Byte (前三byte為Command)   
  70.                     ns.Read(cmd_buffer, 0, cmd_buffer.Length);  
  71.                     //從接收buffer 接收端收到資料長度  
  72.                     byte[] recv_data = ReadStream(ns);  
  73.                     switch (Convert.ToInt32(Encoding.UTF8.GetString(cmd_buffer)))  
  74.                     {  
  75.                         case 126:  //如果接收端有收到傳輸指令,會回應command 126  
  76.                             //確認接收端收到資料長度  
  77.                             long recv_file_pointer =
  78.                             long.Parse(Encoding.UTF8.GetString(recv_data));  
  79.                             //如果資料長度不等於傳送檔案長度
  80.                             //表示還沒傳送完,繼續接收檔案  
  81.                             if (recv_file_pointer != fs.Length)  
  82.                             {  
  83.                                 //將指標移動到當前傳輸檔案流位置  
  84.                                 fs.Seek(recv_file_pointer, SeekOrigin.Begin);  
  85.                                 
  86.                                 //確認此區塊檔案是否有超過 20 MB
  87.                                 //如果有則最大為 20MB將資料內容寫入傳輸Buffer  
  88.                                 //如果小於 20MB,將當前大小資料內容寫入傳輸Buffer  
  89.                                 int temp_buffer_length = (int)
  90.                                 (fs.Length - recv_file_pointer < 20000 ?
  91.                                 fs.Length - recv_file_pointer : 20000);  
  92.                                 //依照長度制定暫時傳輸buffer  
  93.                                 byte[] temp_buffer = new byte[temp_buffer_length];  
  94.                                 //將檔案流的資料寫入暫時傳輸buffer  
  95.                                 fs.Read(temp_buffer, 0, temp_buffer.Length);  
  96.                                 //整合傳輸Command送給接收端  
  97.                                 //將Command與檔案資料利用網路資料流傳給接收端  
  98.                                 byte[] data_to_send =
  99.                                 CreateDataPacket(Encoding.UTF8.GetBytes("127"),
  100.                                 temp_buffer);  
  101.                                 ns.Write(data_to_send, 0, data_to_send.Length);  
  102.                                 ns.Flush();  
  103.                                 //更新目前接收進度  
  104.                                 //用檔案長度與當前接收長度換算剩餘要傳輸的百分比  
  105.                                 progressBar_upload.Invoke((MethodInvoker)delegate  
  106.                                 {  
  107.                                     progressBar_upload.Value =
  108.                                     (int)Math.Ceiling((double)recv_file_pointer /
  109.                                     (double)fs.Length * 100);  
  110.                                     lb_transfer_persent.Text = progressBar_upload.Value+"%";  
  111.                                     if (progressBar_upload.Value == 100)  
  112.                                     {  
  113.                                         progressBar_upload.Visible = false;  
  114.                                         lb_transfer_persent.Visible = false;  
  115.   
  116.                                     }  
  117.                                 });  
  118.                                   
  119.                             }  
  120.                             else  
  121.                             {  
  122.                                 //如果收到接收端資料長度已經等於檔案長度,表示傳輸完成  
  123.                                 //送出Command 128讓接收端知道傳輸結束  
  124.                                 byte[] data_to_send =
  125.                                 CreateDataPacket(Encoding.UTF8.GetBytes("128"),
  126.                                 Encoding.UTF8.GetBytes("Close"));  
  127.                                 ns.Write(data_to_send, 0, data_to_send.Length);  
  128.                                 ns.Flush();  
  129.                                 fs.Close();  
  130.                                 loop_break = true;  
  131.                                 MessageBox.Show("Transfer OK", "Hint",
  132.                                 MessageBoxButtons.OK,
  133.                                 MessageBoxIcon.Information);  
  134.                             }  
  135.                             break;  
  136.                         default:  
  137.                             break;  
  138.                     }  
  139.                 }  
  140.                 if (loop_break == true)  
  141.                 {  
  142.                     //傳輸完畢關閉資料流  
  143.                     ns.Close();  
  144.                     break;  
  145.                 }  
  146.   
  147.             }  
  148.         }  
  149.   
  150.         public static string GetIpAddress()  
  151.         {  
  152.             string ip = "";  
  153.             IPHostEntry ipEntry = Dns.GetHostEntry(GetCompCode());  
  154.             IPAddress[] addr = ipEntry.AddressList;  
  155.             ip = addr[2].ToString();  
  156.             return ip;  
  157.         }  
  158.         public static string GetCompCode()  // Get Computer Name  
  159.         {  
  160.             string strHostName = "";  
  161.             strHostName = Dns.GetHostName();  
  162.             return strHostName;  
  163.         }  
  164.   
  165.         public byte[] ReadStream(NetworkStream ns)  
  166.         {  
  167.             byte[] data_buff = null;  
  168.   
  169.             int b = 0;  
  170.             string buff_Length = "";  
  171.             //從讀取一個位元組,並依一個位元組將資料流中位置往前移  
  172.             // byte 4 = EOT(end of transmission) 程式定義結束字元  
  173.             while ((b = ns.ReadByte()) != 4) //command ~ 4 之前區間為資料長度  
  174.             {  
  175.                 buff_Length += (char)b;  
  176.             }  
  177.             int data_Length = Convert.ToInt32(buff_Length); //找出資料長度buffer長度  
  178.             data_buff = new byte[data_Length];  
  179.             int byte_Read = 0;  
  180.             int byte_Offset = 0;  
  181.             //開始逐步接收到資料長度buffer為空  
  182.             while (byte_Offset < data_Length)  
  183.             {  
  184.                 byte_Read = ns.Read(data_buff, byte_Offset, data_Length - byte_Offset);  
  185.                 byte_Offset += byte_Read;  
  186.             }  
  187.   
  188.             return data_buff;  
  189.         }  
  190.   
  191.         private byte[] CreateDataPacket(byte[] cmd, byte[] data)  
  192.         {  
  193.             byte[] initialize = new byte[1]; //建立初始字元,標示資料開頭  
  194.             initialize[0] = 2;   //STX
  195.             byte[] separator = new byte[1]; //建立區別字,區分資料長度與資料  
  196.             separator[0] = 4;  //EOT
  197.             //將傳輸資料長度一併送出,用在接收資料時判斷是否接收完當前封包內容  
  198.             byte[] dataLength = Encoding.UTF8.GetBytes(Convert.ToString(data.Length));  
  199.             MemoryStream ms = new MemoryStream();  
  200.             ms.Write(initialize, 0, initialize.Length);  
  201.             ms.Write(cmd, 0, cmd.Length);  
  202.             ms.Write(dataLength, 0, dataLength.Length);  
  203.             ms.Write(separator, 0, separator.Length);  
  204.             ms.Write(data, 0, data.Length);  
  205.   
  206.             return ms.ToArray();  
  207.         }  
  208.     }  
  209. }  

接收端

Class Main


  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.ComponentModel;  
  4. using System.Data;  
  5. using System.Drawing;  
  6. using System.Linq;  
  7. using System.Text;  
  8. using System.Windows.Forms;  
  9. using System.Net;  
  10. using System.Net.Sockets;  
  11.   
  12. namespace TCP_big_file_Reciever  
  13. {  
  14.     public partial class Form1 : Form  
  15.     {  
  16.         public Form1()  
  17.         {  
  18.             InitializeComponent();  
  19.         }  
  20.   
  21.         private void btn_start_Click(object sender, EventArgs e)  
  22.         {  
  23.             btn_start.Enabled = false;  
  24.             label4.Text = "Listening";  
  25.             String recieve_path = Application.StartupPath + "\\RECIEVE\\";  
  26.             TCPServer.SaveTo = recieve_path;  
  27.             TCPServer.Port = Convert.ToInt32(txt_target_port.Text);  
  28.             TCPServer obj_server = new TCPServer(this);  
  29.             System.Threading.Thread obj_thread = new
  30.             System.Threading.Thread(obj_server.Startserver);  
  31.             obj_thread.Start();  
  32.         }  
  33.   
  34.         public static string GetLocalIPAddress()  
  35.         {  
  36.             var host = Dns.GetHostEntry(Dns.GetHostName());  
  37.             foreach (var ip in host.AddressList)  
  38.             {  
  39.                 if (ip.AddressFamily == AddressFamily.InterNetwork)  
  40.                 {  
  41.                     return ip.ToString();  
  42.                 }  
  43.             }  
  44.             throw new Exception("No network adapters with an IPv4 address in the
  45.             system!");  
  46.         }  
  47.     }  
  48. }  

class TCPServer

  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Text;  
  5. using System.IO;  
  6. using System.Net.Sockets;  
  7. using System.Net;  
  8. using System.Threading;  
  9.   
  10. namespace TCP_big_file_Reciever  
  11. {  
  12.     class TCPServer  
  13.     {  
  14.         public static string data, SaveTo;  
  15.         public static int Port;  
  16.         TcpListener obj_server;  
  17.         Form1 _F1;  
  18.   
  19.         public TCPServer(Form1 F1)  
  20.         {  
  21.             _F1 = F1;  
  22.             obj_server = new TcpListener(IPAddress.Parse(F1.txt_target_IP.Text), Port);  
  23.         }  
  24.   
  25.         public void Startserver()  
  26.         {  
  27.             obj_server.Start();  
  28.             while (true)  
  29.             {  
  30.                 TcpClient tc = obj_server.AcceptTcpClient();  
  31.                 SocketHandler obj_hadler = new SocketHandler(tc,_F1);  
  32.                 System.Threading.Thread obj_thread = new
  33.                 System.Threading.Thread(obj_hadler.ProcessSocketRequest);  
  34.                 obj_thread.Start();  
  35.             }  
  36.         }  
  37.   
  38.         class SocketHandler  
  39.         {  
  40.             NetworkStream ns;  
  41.             Form1 _F1;  
  42.             public SocketHandler(TcpClient tc,Form1 F1)  
  43.             {  
  44.                 _F1 = F1;  
  45.                 ns = tc.GetStream();  
  46.             }  
  47.   
  48.             public void ProcessSocketRequest()  
  49.             {  
  50.                 FileStream fs = null;  
  51.                 long current_file_pointer = 0;  
  52.                 Boolean loop_break = false;  
  53.   
  54.                 _F1.Invoke(new Action(() =>  
  55.                     {  
  56.                         _F1.label4.Text = "Recieveing";  
  57.                     }  
  58.                 ));  
  59.   
  60.                 while (true)  
  61.                 {  
  62.                     if (ns.ReadByte() == 2)  
  63.                     {  
  64.                         byte[] cmd_buffer = new byte[3];  
  65.                         //接收傳送端Command確認要執行的動作  
  66.                         ns.Read(cmd_buffer, 0, cmd_buffer.Length);  
  67.                         //從資料流讀取傳送端送來的資料  
  68.                         byte[] recv_data = ReadStream();  
  69.                         switch (Convert.ToInt32(Encoding.UTF8.GetString(cmd_buffer)))  
  70.                         {  
  71.                             case 125: //Command 125 開始接收檔案  
  72.                                 {  
  73.                                     //已接收到的檔名作為建立檔案的名稱  
  74.                                     fs = new FileStream(@"" + SaveTo +
  75.                                     Encoding.UTF8.GetString(recv_data)
  76.                                     , FileMode.CreateNew);  
  77.                                     //傳送 Command 126示意接收端要繼續接收檔案  Command
  78.                                     //後面接著目前接收到的資料長度  
  79.                                     byte[] data_to_send =
  80.                                     CreateDataPacket(Encoding.UTF8.GetBytes("126"),
  81.                                     Encoding.UTF8.GetBytes
  82.                                     (Convert.ToString(current_file_pointer)));  
  83.                                     ns.Write(data_to_send, 0, data_to_send.Length);  
  84.                                     ns.Flush();  
  85.                                 }  
  86.                                 break;  
  87.                             case 127:  //Command: 127 接收檔案  
  88.                                 {  
  89.                                     //將檔案指標移動到當前接收位置  
  90.                                     fs.Seek(current_file_pointer, SeekOrigin.Begin);  
  91.                                     //從資料流獲取內容儲存至檔案流中  
  92.                                     fs.Write(recv_data, 0, recv_data.Length);  
  93.                                     //設定當前接收指標位置為目前所接收到的檔案長度  
  94.                                     current_file_pointer = fs.Position;  
  95.                                     //傳送繼續接收資料Command 126
  96.                                     //接收到的資料長度給傳送端  
  97.                                     byte[] data_to_send =
  98.                                     CreateDataPacket(Encoding.UTF8.GetBytes("126"),
  99.                                     Encoding.UTF8.GetBytes
  100.                                     (Convert.ToString(current_file_pointer)));  
  101.                                     ns.Write(data_to_send, 0, data_to_send.Length);  
  102.                                     ns.Flush();  
  103.                                 }  
  104.                                 break;  
  105.                             case 128: //Command: 128 傳送端 傳輸檔案 完畢  
  106.                                 {  
  107.                                     fs.Close();  
  108.                                     loop_break = true;  
  109.                                     _F1.Invoke(new Action(() =>  
  110.                                     {  
  111.                                         _F1.label4.Text = "Complete";  
  112.                                     }  
  113.                                     ));  
  114.                                     Thread.Sleep(1000);  
  115.                                     _F1.Invoke(new Action(() =>  
  116.                                     {  
  117.                                         _F1.label4.Text = "Listening";  
  118.                                     }  
  119.                                     ));  
  120.                                 }  
  121.                                 break;  
  122.                             default:  
  123.                                 break;  
  124.                         }  
  125.                     }  
  126.                     if (loop_break == true)  
  127.                     {  
  128.                         ns.Close();  
  129.                         break;  
  130.                     }  
  131.                 }  
  132.             }  
  133.   
  134.             public byte[] ReadStream()  
  135.             {  
  136.                 byte[] data_buff = null;  
  137.   
  138.                 int b = 0;  
  139.                 string buff_Length = "";  
  140.                 while ((b = ns.ReadByte()) != 4)  
  141.                 {  
  142.                     buff_Length += (char)b;  
  143.                 }  
  144.                 int data_Length = Convert.ToInt32(buff_Length);  
  145.                 data_buff = new byte[data_Length];  
  146.                 int byte_Read = 0;  
  147.                 int byte_Offset = 0;  
  148.                 while (byte_Offset < data_Length)  
  149.                 {  
  150.                     byte_Read = ns.Read(data_buff, byte_Offset, data_Length - byte_Offset);  
  151.                     byte_Offset += byte_Read;  
  152.                 }  
  153.   
  154.                 return data_buff;  
  155.             }  
  156.   
  157.             private byte[] CreateDataPacket(byte[] cmd, byte[] data)  
  158.             {  
  159.                 byte[] initialize = new byte[1];  
  160.                 initialize[0] = 2;  
  161.                 byte[] separator = new byte[1];  
  162.                 separator[0] = 4;  
  163.                 byte[] dataLength =
  164.                 Encoding.UTF8.GetBytes(Convert.ToString(data.Length));  
  165.                 MemoryStream ms = new MemoryStream();  
  166.                 ms.Write(initialize, 0, initialize.Length);  
  167.                 ms.Write(cmd, 0, cmd.Length);  
  168.                 ms.Write(dataLength, 0, dataLength.Length);  
  169.                 ms.Write(separator, 0, separator.Length);  
  170.                 ms.Write(data, 0, data.Length);  
  171.   
  172.                 return ms.ToArray();  
  173.             }  
  174.         }  
  175.     }  
  176. }  

執行結果

傳輸 40MB圖檔




檢視傳輸結果與檔案大小  正確無誤



傳送 100MB TXT


原始傳輸檔案內容

檢視傳輸結果與檔案大小  檔案內容正確無誤


傳送 700 MB ISO 檔



檢視傳輸結果與檔案大小  正確無誤


可執行安裝



有興趣可以嘗試傳送更大的檔案

手邊目前沒有超過1GB以上的檔案

以此架構傳送資料都可完整接收,不會有異常狀況發生

除非在傳輸過程中將程式關掉


相關創作







引用網址:https://home.gamer.com.tw/TrackBack.php?sn=4648317
All rights reserved. 版權所有,保留一切權利

相關創作

同標籤作品搜尋:C#|涼涼風

留言共 7 篇留言

樂小呈

01-09 17:08

貓貓風 ฅ●ω●ฅ
喵 (?01-09 17:10
透明
是把大檔案分開來傳輸的概念嗎? 一次傳一段

01-09 23:51

貓貓風 ฅ●ω●ฅ
對呀 很大的檔案本來就不可能一次傳完,就算是PC的記憶體也有限制01-09 23:55

請問: 程式輸入後出現下列錯誤訊息怎麼樣才能排除?
'Form1.txt_target_IP' 和 F1.labl4.Text 由於其保護層級之故,所以無法存取

12-21 16:16


問題己解決
將 txt_target_IP 和 label4 改成 public

12-21 20:47

屏東李國毅
請問 為何傳送檔案後,transfer和receiver的程式都會自動閃退,沒有跳出傳送成功的提示

07-28 13:26

曾泉
想請問一下,這個程式只能在自己的電腦互傳嗎?我是樂傳送到疊別的電腦上接收端會出錯

03-20 16:30

貓貓風 ฅ●ω●ฅ
改IP跟PORT03-20 21:02
曾泉
我有改ip,接受端這裡填傳送端的ip,至於port我不清楚要怎麼用就只有隨便填一個數字,然後接受端這裡點開始class TCPServer的27行這裡會報錯

03-21 14:16

我要留言提醒:您尚未登入,請先登入再留言

23喜歡★s1234567 可決定是否刪除您的留言,請勿發表違反站規文字。

前一篇:貓咪大戰爭 傳說49 古... 後一篇:貓咪大戰爭 古代神秘...

追蹤私訊切換新版閱覽

作品資料夾

a86189642祝福
看到的人會變得幸福哦!看更多我要大聲說6小時前


face基於日前微軟官方表示 Internet Explorer 不再支援新的網路標準,可能無法使用新的應用程式來呈現網站內容,在瀏覽器支援度及網站安全性的雙重考量下,為了讓巴友們有更好的使用體驗,巴哈姆特即將於 2019年9月2日 停止支援 Internet Explorer 瀏覽器的頁面呈現和功能。
屆時建議您使用下述瀏覽器來瀏覽巴哈姆特:
。Google Chrome(推薦)
。Mozilla Firefox
。Microsoft Edge(Windows10以上的作業系統版本才可使用)

face我們了解您不想看到廣告的心情⋯ 若您願意支持巴哈姆特永續經營,請將 gamer.com.tw 加入廣告阻擋工具的白名單中,謝謝 !【教學】