天天看點

Java 使用 TCP 和 UDP 傳輸檔案

 引言

  本項目的目的是實作兩個應用,通過網絡連接配接在不同的主機之間傳輸一個檔案的功能。兩個應用應該分别利用 udp 和 tcp 協定,以具有傳輸至少 1 mb 檔案的能力。

  實作和說明

  源代碼

  兩個應用都由單個程式實作,源代碼下載下傳位址。

  說明

  程式使用以下指令行進行編譯:

  javac *.java

  然後使用以下兩個指令行運作:

receiver:

sender:

# java filesender [protocol] [host] [port] [filename]

  其中 [protocol] 參數可以是 "udp" 或者 "tcp",但 sender 和 receiver 必須一緻。

  檔案将會在 receiver 啟動的目錄下生成,預設指定名為 "received-[filename]"。

  tcp 實作

  實作概述

  在 tcp 實作中,receiver 打開了一個 serversocket,并對定義好的端口進行監聽。sender 啟動後将會為監聽者 receiver 打開一個新的 socket,這導緻了 socket 兩端 inputstream 和 outputstream 對象的建立。

  一個包含了檔案名和檔案大小的初始資訊将由 sender 發送給 receiver。這樣 receiver 可以使用一個有意義的名字來存儲接收到的檔案,并可以判斷什麼時候檔案完全傳輸完畢。此資訊并不是必須的,當 receiver 無法接收檔案時停止 sender 占用不必要的帶寬。

  檔案通過一個 fileinputstream 對象對它的讀取進行傳輸,然後将資料寫到一個 socket 傳回的 outputstream 對象。為提高應用效率,每次讀取和中繼的資料是 8 kb,使用一個位元組數組作為緩存。

  tcp 使用經驗

  udp 實作

  udp 檔案傳輸的實作使用的是标準 java datagram 類:datagrampacket 和 datagramsocket。

  當 receiver 被執行時,它打開一個指定端口号的 socket 并等待,監聽傳入的資料包。sender 啟動後,它打開一個連接配接到指定主機和端口的 socket,并傳輸包含有檔案名以及将要傳輸檔案大小等資訊的單個 packet。當這個 package  發送以後,這個 socket 将等待并監聽 package。

  基于接收到的初始 package,receiver 為檔案建立一 outputstream 對象,并給監聽着的 sender 發送一個含有 "ok" 單詞的 package。收到這個 "ok" 包以後,sender 開始讀取檔案内容,并将其通過 udp 資料包發送,每次含有 512 位元組的塊。receiver 将這些塊按照接收到的次序寫入檔案,并重複接收,直到接收到的位元組達到它所期望數字。之後程式終止。

  udp 使用經驗

  udp 是一種不可靠的傳輸連續資料的協定。這意味着傳輸過程中會有丢包,而且接收到包的次序也是随機的。上面的例子并沒有解決檔案傳輸中的這些問題。這意味着以上應用在其每次運作時(所得到的檔案)并不是正确的和完整的。以下是關于兩個經常發生的問題的原因以及可行解決方案的描述。

  如果在檔案傳輸過程中兩個包接收順序錯誤,而寫入檔案的順序是按接收順序來的。這将造成接收檔案損壞。對于這種問題的解決方案是每次傳輸時定義一個序列号。這可以讓 receiver 按照正确的順序來存儲這些包,不管它們到達的先後次序。

  如果傳輸檔案時出現丢包,receiver 将不能收到它所期望數量的資料。在上面的示例中,這會導緻 receiver 繼續運作,等待剩餘的資料。對于這個問題一個可行的解決方案是,receiver 在給定時間跨度之後進行每次傳輸,調用逾時。但為了使此次請求具有目的性,我們要像上面說的那樣為包擴充序列号。否則我們無法接收到給定數量的資料,并局限于請求檔案的完整傳輸。

  另一個關于這兩個問題的解決方案,在每次正确接收包之後再向 sender 發起接收請求。這個方法消除了丢包的可能性,但卻會使傳輸異常緩慢。

  結語

  上面的實作讓檔案在主機之間傳輸變得可行。但如果使用的是 udp 協定的話,我們就無法保證檔案的完整性和接收(順序)的正确性。我們對解決這些問題進行了大體說明,但具體在實際的檔案傳輸中,對這些問題的最簡單的解決方案就是,用 tcp 取代 udp。

最新内容請見作者的github頁:http://qaseven.github.io/