C#實戰027:socket實現大檔案傳輸
前面寫了一個單檔案傳輸的,後來發現當傳送的檔案大於設定的快取空間時,檔案就會出現丟包的現象,導致檔案無法使用,所以為了適應大檔案的傳輸,這裡我將程式碼進行了下修改,實現大檔案傳輸。
不過socket實現大檔案傳輸有個缺點,由於傳輸過程是通過位元組快取傳送,接受也是讀寫位元組,導致整個傳輸過程效率不高,我嘗試了一個169MB的視訊檔案傳輸,雖然傳完了,但是耗時將近1小時。
因為計算機快取有限,所以不可能開啟太大的快取來快取資料的,所以當我們要傳送檔案較大的檔案時我們就要進行分段處理,分段讀取,分段傳送儲存。大家可以看到我們的兩個視窗都是同步的,一遍在讀取中,一遍就在寫入中。如此反覆的使用同一塊快取進行資料傳遞。
首先我們先進行迴圈讀取檔案資訊,這裡有個重要的就是做好標記,之前寫 C#實戰026:socket實現單檔案傳輸時就有提到,通過在第一個位元組做標記來區分我們傳送的資訊是什麼資訊,這個規則自己定義,只要客戶端和服務端同步即可
這裡我們把0定義成資訊傳送,1定義成檔案傳送,2定義成檔案頭資訊傳送
首先我們先需要把我們要傳送的檔案資訊拋給伺服器,這裡主要需要檔案的檔名和檔案大小,這裡我們只要在用檔案流讀取檔案的時候將這些資料提取出來即可:
//1. 第一步:傳送一個檔案,表示檔名和長度,讓客戶端知道檔案大小 string fileName = Path.GetFileName(filePath);//提取檔名 Console.WriteLine("傳送的檔名是:" + fileName);//檢視獲取檔名是否正確 long fileLength = fsRead.Length;//獲取檔案長度 Console.WriteLine("傳送的檔案長度為:"+fileLength);//檢視檔案長度是否正確 string totalMsg = string.Format("{0}-{1}", fileName, fileLength);//將檔名和檔案長度存入一條資料中 byte[] buffer = Encoding.UTF8.GetBytes(totalMsg); //將字串轉換成位元組陣列 byte[] newBuffer = new byte[buffer.Length + 1];//新建位元組陣列,增加一個位元組空間 newBuffer[0] = 2;//將第一個位元組標記成2,代表為檔案頭資訊 Buffer.BlockCopy(buffer, 0, newBuffer, 1, buffer.Length);//偏移複製位元組陣列 socketClient.Send(newBuffer);//傳送檔案檔名和長度發過去
既然是迴圈寫入,我們就要記錄當前檔案的資料大小和已讀取的資料資訊,這樣迴圈才有終點,定義一個5M快取區。
byte[] Filebuffer = new byte[1024 * 1024 * 5];//定義5MB的快取空間(1024位元組(b)=1千位元組(kb))
int readLength = 0; //定義讀取的長度
bool firstRead = true;//定義首次讀取的狀態
long sentFileLength = 0;//定義傳送的長度
接下里解釋對檔案進行分包傳送了,這裡唯一要注意的就是第一次傳送的時候要為檔案價格標記,也就是第一個資料包前加標記,這樣服務端才好去識別該資料是什麼資料,然後做對應的處理。
while (readLength> 0 && sentFileLength < fileLength)
{
sentFileLength += readLength;//計算已讀取檔案大小
//第一次傳送的位元組流上加個字首1
if (firstRead)
{
byte[] firstBuffer = new byte[readLength + 1];//這個操作同樣也是用來標記檔案的
firstBuffer[0] = 1;//將第一個位元組標記成1,代表為檔案
Buffer.BlockCopy(buffer, 0, firstBuffer, 1, readLength);//偏移複製位元組陣列
socketClient.Send(firstBuffer, 0, readLength + 1, SocketFlags.None);
Console.WriteLine("第一次讀取資料成功,在前面新增一個標記");//傳送檔案資料包
firstRead = false;//切換狀態,避免再次進入
continue;
}
socketClient.Send(buffer, 0, readLength, SocketFlags.None);//繼續傳送剩下的資料包
Console.WriteLine("{0}: 已傳送資料:{1}/{2}", socketClient.RemoteEndPoint, sentFileLength, fileLength);//檢視傳送進度
}
fsRead.Close();//關閉檔案流
Console.WriteLine("傳送完成");//提示傳送完畢
接下來在可以在服務端來接受資料了,同樣在處理資料的時候要把第一次資料分開,因為第一組資料中新增一個標記符,所以我們在寫資料的時候要擷取標記後面的資料。
if (buffer[0] == 1)//1對應檔案資訊
{
SaveFileDialog sfDialog = new SaveFileDialog();//建立SaveFileDialog例項
string spath = @"C:\Users\admin\Desktop";//制定儲存路徑
string savePath = Path.Combine(spath, recStr);//獲取儲存路徑及檔名
int rec = 0;//定義獲取接受資料的長度初始值
long recFileLength = 0;
bool firstWrite = true;
using (FileStream fs = new FileStream(savePath, FileMode.Create, FileAccess.Write))
{
while (recFileLength < fileLength)//判斷讀取檔案長度是否小於總檔案長度
{
if (firstWrite)//第一次寫入時
{
fs.Write(buffer, 1, firstRcv - 1);//擷取位元組資料寫入檔案中
fs.Flush();//清空快取資訊
recFileLength += firstRcv - 1;//記錄已獲取的資料大小
firstWrite = false;//切換狀態
}
else
{
rec = socketServer.Receive(buffer);//繼續接收檔案並存入快取
fs.Write(buffer, 0, rec);//將快取中的資料寫入檔案中
fs.Flush();//清空快取資訊
recFileLength += rec;//繼續記錄已獲取的資料大小
}
Console.WriteLine("{0}: 已接收資料:{1}/{2}", socketServer.RemoteEndPoint, recFileLength, fileLength);//檢視已接受資料進度
}
fs.Close();
}
Console.WriteLine("儲存成功!!!!");
}
我把原始碼傳在CSDN了,有興趣的可以下載: Socket傳輸大檔案(傳送與接收原始碼)
首發百度經驗 : C#實戰027:socket實現大檔案傳輸