23 GP
C# 使用TCP傳輸各類型檔案 (檔案類型與大小無限制,皆可傳送)
作者:貓貓風 ฅ●ω●ฅ│2020-01-09 15:25:50│巴幣:544│人氣:6620
.
本篇為 TCP 傳輸的進階應用
傳輸資料與類型都沒有限制,如果傳輸資料非常大,可能幾GB或TB
可以依照自身記憶體容量調整傳輸buffer的大小,加速傳輸速度
使用自定義的通訊協定進行資料交換
自定義的通訊封包如下
欄位名稱 初始字元 傳輸指令 資料長度 區別字元 資料內容
指令表
整體程式架構 與交握時序圖
實作程式碼
傳送端
- using System;
- using System.Collections.Generic;
- using System.ComponentModel;
- using System.Data;
- using System.Drawing;
- using System.Linq;
- using System.Text;
- using System.Windows.Forms;
- using System.IO;
- using System.Net.Sockets;
- using System.Net;
- using System.Threading;
-
- namespace TCP_big_file_transfer
- {
- public partial class Form1 : Form
- {
- public Form1()
- {
- InitializeComponent();
- }
-
- Thread _file_send; //宣告執行緒
-
- private void btn_send_Click(object sender, EventArgs e)
- {
- OpenFileDialog dialog = new OpenFileDialog();
- if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
- {
- progressBar_upload.Value = 0;
- progressBar_upload.Visible = true;
- lb_transfer_persent.Visible = true;
- lb_file_path.Text = dialog.FileName;
- //將檔案選取欄位結果帶入執行緒
- _file_send = new Thread(new ParameterizedThreadStart(object_param));
- _file_send.Start(dialog);
-
- }
- }
-
- private void object_param(object o)
- {
- trans_file((OpenFileDialog)o);
- }
-
- private void trans_file(OpenFileDialog dialog)
- {
- string Selected_file = dialog.FileName;
- string File_name = Path.GetFileName(Selected_file);
- FileStream fs = new FileStream(Selected_file, FileMode.Open);
- TcpClient tc = new TcpClient(txt_target_IP.Text,
- Convert.ToInt32(txt_target_port.Text));
- //使用此IP進行資料傳輸與接收
- NetworkStream ns = tc.GetStream();
- //將 command與檔名寫入傳輸buffer,示意接收端要開始進行檔案傳輸
- byte[] data_tosend = CreateDataPacket(Encoding.UTF8.GetBytes("125"),
- Encoding.UTF8.GetBytes(File_name));
- //將資料送出
- ns.Write(data_tosend, 0, data_tosend.Length);
- //刷新傳輸buffer (清空傳輸buffer用來接收資料)
- ns.Flush();
- Boolean loop_break = false;
- while (true)
- {
- if (ns.ReadByte() == 2)
- //如果資料流有資料 用第一個byte做判斷 2為初始封包byte
- {
- byte[] cmd_buffer = new byte[3];
- //從接收buffer讀取前三個Byte (前三byte為Command)
- ns.Read(cmd_buffer, 0, cmd_buffer.Length);
- //從接收buffer 接收端收到資料長度
- byte[] recv_data = ReadStream(ns);
- switch (Convert.ToInt32(Encoding.UTF8.GetString(cmd_buffer)))
- {
- case 126: //如果接收端有收到傳輸指令,會回應command 126
- //確認接收端收到資料長度
- long recv_file_pointer =
- long.Parse(Encoding.UTF8.GetString(recv_data));
- //如果資料長度不等於傳送檔案長度
- //表示還沒傳送完,繼續接收檔案
- if (recv_file_pointer != fs.Length)
- {
- //將指標移動到當前傳輸檔案流位置
- fs.Seek(recv_file_pointer, SeekOrigin.Begin);
-
- //確認此區塊檔案是否有超過 20 MB
- //如果有則最大為 20MB將資料內容寫入傳輸Buffer
- //如果小於 20MB,將當前大小資料內容寫入傳輸Buffer
- int temp_buffer_length = (int)
- (fs.Length - recv_file_pointer < 20000 ?
- fs.Length - recv_file_pointer : 20000);
- //依照長度制定暫時傳輸buffer
- byte[] temp_buffer = new byte[temp_buffer_length];
- //將檔案流的資料寫入暫時傳輸buffer
- fs.Read(temp_buffer, 0, temp_buffer.Length);
- //整合傳輸Command送給接收端
- //將Command與檔案資料利用網路資料流傳給接收端
- byte[] data_to_send =
- CreateDataPacket(Encoding.UTF8.GetBytes("127"),
- temp_buffer);
- ns.Write(data_to_send, 0, data_to_send.Length);
- ns.Flush();
- //更新目前接收進度
- //用檔案長度與當前接收長度換算剩餘要傳輸的百分比
- progressBar_upload.Invoke((MethodInvoker)delegate
- {
- progressBar_upload.Value =
- (int)Math.Ceiling((double)recv_file_pointer /
- (double)fs.Length * 100);
- lb_transfer_persent.Text = progressBar_upload.Value+"%";
- if (progressBar_upload.Value == 100)
- {
- progressBar_upload.Visible = false;
- lb_transfer_persent.Visible = false;
-
- }
- });
-
- }
- else
- {
- //如果收到接收端資料長度已經等於檔案長度,表示傳輸完成
- //送出Command 128讓接收端知道傳輸結束
- byte[] data_to_send =
- CreateDataPacket(Encoding.UTF8.GetBytes("128"),
- Encoding.UTF8.GetBytes("Close"));
- ns.Write(data_to_send, 0, data_to_send.Length);
- ns.Flush();
- fs.Close();
- loop_break = true;
- MessageBox.Show("Transfer OK", "Hint",
- MessageBoxButtons.OK,
- MessageBoxIcon.Information);
- }
- break;
- default:
- break;
- }
- }
- if (loop_break == true)
- {
- //傳輸完畢關閉資料流
- ns.Close();
- break;
- }
-
- }
- }
-
- public static string GetIpAddress()
- {
- string ip = "";
- IPHostEntry ipEntry = Dns.GetHostEntry(GetCompCode());
- IPAddress[] addr = ipEntry.AddressList;
- ip = addr[2].ToString();
- return ip;
- }
- public static string GetCompCode() // Get Computer Name
- {
- string strHostName = "";
- strHostName = Dns.GetHostName();
- return strHostName;
- }
-
- public byte[] ReadStream(NetworkStream ns)
- {
- byte[] data_buff = null;
-
- int b = 0;
- string buff_Length = "";
- //從讀取一個位元組,並依一個位元組將資料流中位置往前移
- // byte 4 = EOT(end of transmission) 程式定義結束字元
- while ((b = ns.ReadByte()) != 4) //command ~ 4 之前區間為資料長度
- {
- buff_Length += (char)b;
- }
- int data_Length = Convert.ToInt32(buff_Length); //找出資料長度buffer長度
- data_buff = new byte[data_Length];
- int byte_Read = 0;
- int byte_Offset = 0;
- //開始逐步接收到資料長度buffer為空
- while (byte_Offset < data_Length)
- {
- byte_Read = ns.Read(data_buff, byte_Offset, data_Length - byte_Offset);
- byte_Offset += byte_Read;
- }
-
- return data_buff;
- }
-
- private byte[] CreateDataPacket(byte[] cmd, byte[] data)
- {
- byte[] initialize = new byte[1]; //建立初始字元,標示資料開頭
- initialize[0] = 2; //STX
- byte[] separator = new byte[1]; //建立區別字,區分資料長度與資料
- separator[0] = 4; //EOT
- //將傳輸資料長度一併送出,用在接收資料時判斷是否接收完當前封包內容
- byte[] dataLength = Encoding.UTF8.GetBytes(Convert.ToString(data.Length));
- MemoryStream ms = new MemoryStream();
- ms.Write(initialize, 0, initialize.Length);
- ms.Write(cmd, 0, cmd.Length);
- ms.Write(dataLength, 0, dataLength.Length);
- ms.Write(separator, 0, separator.Length);
- ms.Write(data, 0, data.Length);
-
- return ms.ToArray();
- }
- }
- }
接收端
Class Main
- using System;
- using System.Collections.Generic;
- using System.ComponentModel;
- using System.Data;
- using System.Drawing;
- using System.Linq;
- using System.Text;
- using System.Windows.Forms;
- using System.Net;
- using System.Net.Sockets;
-
- namespace TCP_big_file_Reciever
- {
- public partial class Form1 : Form
- {
- public Form1()
- {
- InitializeComponent();
- }
-
- private void btn_start_Click(object sender, EventArgs e)
- {
- btn_start.Enabled = false;
- label4.Text = "Listening";
- String recieve_path = Application.StartupPath + "\\RECIEVE\\";
- TCPServer.SaveTo = recieve_path;
- TCPServer.Port = Convert.ToInt32(txt_target_port.Text);
- TCPServer obj_server = new TCPServer(this);
- System.Threading.Thread obj_thread = new
- System.Threading.Thread(obj_server.Startserver);
- obj_thread.Start();
- }
-
- public static string GetLocalIPAddress()
- {
- var host = Dns.GetHostEntry(Dns.GetHostName());
- foreach (var ip in host.AddressList)
- {
- if (ip.AddressFamily == AddressFamily.InterNetwork)
- {
- return ip.ToString();
- }
- }
- throw new Exception("No network adapters with an IPv4 address in the
- system!");
- }
- }
- }
class TCPServer
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.IO;
- using System.Net.Sockets;
- using System.Net;
- using System.Threading;
-
- namespace TCP_big_file_Reciever
- {
- class TCPServer
- {
- public static string data, SaveTo;
- public static int Port;
- TcpListener obj_server;
- Form1 _F1;
-
- public TCPServer(Form1 F1)
- {
- _F1 = F1;
- obj_server = new TcpListener(IPAddress.Parse(F1.txt_target_IP.Text), Port);
- }
-
- public void Startserver()
- {
- obj_server.Start();
- while (true)
- {
- TcpClient tc = obj_server.AcceptTcpClient();
- SocketHandler obj_hadler = new SocketHandler(tc,_F1);
- System.Threading.Thread obj_thread = new
- System.Threading.Thread(obj_hadler.ProcessSocketRequest);
- obj_thread.Start();
- }
- }
-
- class SocketHandler
- {
- NetworkStream ns;
- Form1 _F1;
- public SocketHandler(TcpClient tc,Form1 F1)
- {
- _F1 = F1;
- ns = tc.GetStream();
- }
-
- public void ProcessSocketRequest()
- {
- FileStream fs = null;
- long current_file_pointer = 0;
- Boolean loop_break = false;
-
- _F1.Invoke(new Action(() =>
- {
- _F1.label4.Text = "Recieveing";
- }
- ));
-
- while (true)
- {
- if (ns.ReadByte() == 2)
- {
- byte[] cmd_buffer = new byte[3];
- //接收傳送端Command確認要執行的動作
- ns.Read(cmd_buffer, 0, cmd_buffer.Length);
- //從資料流讀取傳送端送來的資料
- byte[] recv_data = ReadStream();
- switch (Convert.ToInt32(Encoding.UTF8.GetString(cmd_buffer)))
- {
- case 125: //Command 125 開始接收檔案
- {
- //已接收到的檔名作為建立檔案的名稱
- fs = new FileStream(@"" + SaveTo +
- Encoding.UTF8.GetString(recv_data)
- , FileMode.CreateNew);
- //傳送 Command 126示意接收端要繼續接收檔案 Command
- //後面接著目前接收到的資料長度
- byte[] data_to_send =
- CreateDataPacket(Encoding.UTF8.GetBytes("126"),
- Encoding.UTF8.GetBytes
- (Convert.ToString(current_file_pointer)));
- ns.Write(data_to_send, 0, data_to_send.Length);
- ns.Flush();
- }
- break;
- case 127: //Command: 127 接收檔案
- {
- //將檔案指標移動到當前接收位置
- fs.Seek(current_file_pointer, SeekOrigin.Begin);
- //從資料流獲取內容儲存至檔案流中
- fs.Write(recv_data, 0, recv_data.Length);
- //設定當前接收指標位置為目前所接收到的檔案長度
- current_file_pointer = fs.Position;
- //傳送繼續接收資料Command 126
- //接收到的資料長度給傳送端
- byte[] data_to_send =
- CreateDataPacket(Encoding.UTF8.GetBytes("126"),
- Encoding.UTF8.GetBytes
- (Convert.ToString(current_file_pointer)));
- ns.Write(data_to_send, 0, data_to_send.Length);
- ns.Flush();
- }
- break;
- case 128: //Command: 128 傳送端 傳輸檔案 完畢
- {
- fs.Close();
- loop_break = true;
- _F1.Invoke(new Action(() =>
- {
- _F1.label4.Text = "Complete";
- }
- ));
- Thread.Sleep(1000);
- _F1.Invoke(new Action(() =>
- {
- _F1.label4.Text = "Listening";
- }
- ));
- }
- break;
- default:
- break;
- }
- }
- if (loop_break == true)
- {
- ns.Close();
- break;
- }
- }
- }
-
- public byte[] ReadStream()
- {
- byte[] data_buff = null;
-
- int b = 0;
- string buff_Length = "";
- while ((b = ns.ReadByte()) != 4)
- {
- buff_Length += (char)b;
- }
- int data_Length = Convert.ToInt32(buff_Length);
- data_buff = new byte[data_Length];
- int byte_Read = 0;
- int byte_Offset = 0;
- while (byte_Offset < data_Length)
- {
- byte_Read = ns.Read(data_buff, byte_Offset, data_Length - byte_Offset);
- byte_Offset += byte_Read;
- }
-
- return data_buff;
- }
-
- private byte[] CreateDataPacket(byte[] cmd, byte[] data)
- {
- byte[] initialize = new byte[1];
- initialize[0] = 2;
- byte[] separator = new byte[1];
- separator[0] = 4;
- byte[] dataLength =
- Encoding.UTF8.GetBytes(Convert.ToString(data.Length));
- MemoryStream ms = new MemoryStream();
- ms.Write(initialize, 0, initialize.Length);
- ms.Write(cmd, 0, cmd.Length);
- ms.Write(dataLength, 0, dataLength.Length);
- ms.Write(separator, 0, separator.Length);
- ms.Write(data, 0, data.Length);
-
- return ms.ToArray();
- }
- }
- }
- }
執行結果
傳輸 40MB圖檔
檢視傳輸結果與檔案大小 正確無誤
傳送 100MB TXT
原始傳輸檔案內容
檢視傳輸結果與檔案大小 檔案內容正確無誤
傳送 700 MB ISO 檔
檢視傳輸結果與檔案大小 正確無誤
可執行安裝
有興趣可以嘗試傳送更大的檔案
手邊目前沒有超過1GB以上的檔案
以此架構傳送資料都可完整接收,不會有異常狀況發生
除非在傳輸過程中將程式關掉
相關創作
引用網址:https://home.gamer.com.tw/TrackBack.php?sn=4648317
All rights reserved. 版權所有,保留一切權利
相關創作
同標籤作品搜尋:C#|涼涼風
留言共 7 篇留言
透明:
是把大檔案分開來傳輸的概念嗎? 一次傳一段
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,接受端這裡填傳送端的ip,至於port我不清楚要怎麼用就只有隨便填一個數字,然後接受端這裡點開始class TCPServer的27行這裡會報錯
03-21 14:16
我要留言提醒:您尚未登入,請先
登入再留言
23喜歡★s1234567 可決定是否刪除您的留言,請勿發表違反站規文字。
前一篇:貓咪大戰爭 傳說49 古...
後一篇:貓咪大戰爭 古代神秘...