LFTP Design
簡介
LFTP是一個采用python3實現的基于UDP傳輸協議的可靠文件傳輸工具
特點
- 基于UDP
-
- 采用
python3
編程語言,socket的類型均為socket(AF_INET,SOCK_DGRAM)
實現
- 采用
- 實現100%可靠性傳輸
-
- 使用SR(選擇重傳)協議保證所有報文都正確接收
- 實現流量控制
-
- 使用接收者通知發送者接收緩存大小來反饋發送窗口大小
- 實現擁塞控制
-
- 根據網絡情況動態調整發送窗口大小。
- 實現并發傳輸
-
- 使用多個服務線程,滿足多個用戶同時上傳或者下載的需求
- 支持大文件傳輸且不需要過多內存
-
- 切塊傳輸
具體設計
項目結構:
項目樹:
.
├── docs
│ ├── Design-doc.md
│ └── Test-doc.md
├── README.md
└── src├── Client│ └── client.py # 客戶端├── Helper│ ├── __init__.py│ ├── LFTPMessage.py # 封裝傳遞的包│ ├── LFTPRecvWindow.py # 接收窗口│ └── LFTPSendWindow.py # 發送端口├── Server│ └── server.py # 服務端└── Utils└── Log.py # 記錄日志
基本流程
流程參考FTP被動模式,首先客戶端會獲取一個隨機端口(python的socket指定),然后向我們指定定的服務端端口發送請求命令,然后服務端主控制線程會新開一個數據線程,分配一個數據傳輸端口,并且把端口號發送給客戶端,然后客戶端向這個數據傳輸端口獲取文件數據或者發送文件數據。這種方式保證了多個客戶端可以同時連接服務器進行數據傳輸。
客戶端上傳文件到服務端
具體工作過程:
- 客戶端向服務端請求發送文件,獲取服務端的數據端口
- 服務端接收到發送文件請求,從空閑的地址池中選擇一個端口,新開一個數據線程,在這個端口上監聽接收的數據
- 客戶端接收到服務端傳來的數據端口,向該數據端口發起連接
- 若連接成功,客戶端向服務端開始發送文件分組信息
- 服務端接收到相應的分組,發回相應的ACK確認(采用累積確認)
- 客戶端接收到相應的ACK,做出調整
- 客戶端接收到所有分組的ACK,結束進程
- 服務端超時5s(有可能需要ACK相應的信息,即使收到了全部的包)之后同樣關閉端口
客戶端從服務器下載文件
下載的過程和上傳的過程類似,只是在建立連接之后,由數據方,也就是服務端開始發數據分組
- 客戶端向服務端請求發送文件,獲取服務端的數據端口
- 服務端接收到發送文件請求,從空閑的地址池中選擇一個端口,新開一個數據線程,在這個端口上監聽接收的數據
- 客戶端接收到服務端傳來的數據端口,向該數據端口發起連接
- 若連接成功,服務端向客戶端開始發送文件分組信息
- 客戶端接收到相應的分組,發回相應的ACK確認(采用累積確認)
- 服務端接收到相應的ACK,做出調整(cwnd和rwnd)
- 服務端接收到所有分組的ACK,結束該服務線程,關閉端口
- 客戶端超時5s(有可能需要ACK相應的信息,即使收到了全部的包)之后結束進程
相關細節實現
文件相關信息的接收和發送
這里的文件相關信息指示的是文件名稱或文件大小等信息,原先打算在每個包的首部加上該字段,存儲文件相關信息,后來發現可以在三次握手期間將文件信息發與服務端(或者從服務端獲取該相關信息)
可靠性的實現
這里參考TCP的重傳和確認,采用SR(選擇重傳)和累積確認的結合方式,當收取到相應的數據包,接收方會發送ACK包,與SR不同的是,這里ACK包中的確認號ACKnum是選擇最小的未接收序列號。
擁塞控制實現
在接收到ACK的時候:
- 正確的ACK,增加窗口大小:
-
- 慢啟動階段:cwnd <= ssthresh: cwnd = cwnd + 1
- 擁塞避免階段:cwnd > ssthresh: cwnd = cwnd + 1/cwnd
- 冗余的ACK,三次冗余將重傳 + 減少窗口大小
-
- ssthresh = cwnd / 2
- cwnd = cwnd/2 + 3
- 超時,重傳 + 減少窗口大小
-
- ssthresh = cwnd / 2
- cwnd = 1
流量控制實現
在ACK確認分組信息頭部加上接收窗口空余數(可用數),發送方接收到該確認分組之后將根據該字段,動態調整發送窗口的長度
并發多客戶上傳或下載的實現
服務端會有一個主控制線程,每受到新的客戶端的請求(UPLOAD/DOWNLOAD),它都會從空閑端口池中取出空閑端口,新建一個數據線程去監聽該端口,然后主控制線程會告訴客戶端去跟新的數據線程連接并傳遞信息。
大文件讀寫的實現
python3讀取文件的函數file.read(size),已經自動幫我們做了緩存機制,所以我們可以直接進行讀寫而不必進行數據分塊處理,但需要注意的是,不能一次性read()整個文件,不然會將整個文件讀入內存中,大文件會直接將內存撐爆,而要使用read(size),通過大小為size的緩存,實現對文件的分塊讀取(每次只讀取size)。
測試
局域網下進行測試
服務端
命令行開啟服務(默認跑在123456端口)
$ python3 server.py
客戶端
命令錯誤會有相應的提示信息
發送數據
使用指令LFTP lsend 127.0.0.1 filepath
進行上傳相應的文件
這里我上傳了一個幾十MB的pdf文件:
上傳結束:
對比服務端和客戶端的文件大小:
可以發現客戶端和服務端的文件子節數相同,上傳成功
接收數據
使用指令LFTP lget 127.0.0.1 filename
進行下載相應的文件
這里我刪除剛才上傳的了的pdf文件,再進行下載:
下載結束:
對比服務端和客戶端的文件大小:
可以發現客戶端和服務端的文件子節數相同,下載成功
測試并行下載
上圖可以看出服務端支持兩個客戶端同時進行文件的下載
互聯網下進行測試
測試環境
服務器:騰訊云
配置:2核2G
帶寬:上行1Mbps,下行8Mbps
系統:ubuntu 16.04 64位
運行環境:Python3
公網IP:119.29.204.118 (廣州)
服務端
命令行開啟服務(默認跑在123456端口)
$ python3 server.py
客戶端
發送數據
使用指令LFTP lsend 119.29.204.118 filepath
進行上傳相應的文件
跟在局域網下相似,這里我上傳了一個幾十MB的pdf文件:
上傳結束
對比一下接收到的文件和源文件
可以發現客戶端和服務端的文件子節數相同,上傳成功
接收數據
使用指令LFTP lget 119。29.204.118 filename
進行下載相應的文件
這里我刪除剛才上傳的了的pdf文件,再進行下載:
下載結束:
對比服務端和客戶端的文件大小:
可以發現客戶端和服務端的文件子節數相同,下載成功
測試并行下載
在局域網下測試:
上圖可以看出服務端成功支持兩個客戶端同時進行文件的下載
擁塞控制測試
因為局域網內的帶寬過大,難以看出擁塞控制測試的效果,因此這里使用互聯網環境,在云服務器上1Mbps的帶寬下同時下載文件
首先讓一個客戶端先開啟下載,可以達到300KB/s的速度:
然后,再連接一個客戶端,可以看到,此時速度都降低到150KB/s的速度。約為原來的1/2:
流量控制測試
因為文件寫入的速度遠快于數據在網絡中的傳輸數度,流量控制并沒有很明顯的表現,所以此處并沒有流量控制測試的相關截圖。
大文件下載測試
這里在局域網下進行了大文件測試
可以看出,即使上傳超過4GB,內存并沒有過多占有,這里只是占用了 0.1% 的內存.