1. 程式人生 > >C#實戰027:socket實現大檔案傳輸

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實現大檔案傳輸