引言
本項目的目的是實現兩個應用,通過網絡連接在不同的主機之間傳輸一個文件的功能。兩個應用應該分別利用 UDP 和 TCP 協議,以具有傳輸至少 1 MB 文件的能力。
實現和說明
源代碼
兩個應用都由單個程序實現,源代碼下載地址。
免費下載地址在 http://linux.linuxidc.com/
用戶名與密碼都是www.linuxidc.com
具體下載目錄在 /2013年資料/6月/10日/Java 使用 TCP 和 UDP 傳輸文件
說明
程序使用以下命令行進行編譯:
javac *.java
然後使用以下兩個命令行運行:
Receiver:
# java FileReceiver [protocol] [port]
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 使用經驗
實踐證明,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。