[原創]python之socket-ftp


今天來講講ftp文件下載,感覺挺有趣的,知道吧,就那種看到新文件生成,而自己寫的代碼也不多,那種成就感!

一、需求:

 

  客戶端發送指令給服務端,服務端根據指令找到相應文件,發送給客戶端

  分析:

    PS:encode() decode()默認是utf-8

   Ftp server
    1.讀取文件名
    2.檢測文件是否存在
    3.打開文件
    4.檢測文件大小
    5.發送文件大小給客戶端
    6.等客戶端確認 #防止粘包
    7.開始邊讀邊發數據
    8.發送md5
      客戶端的md5與服務器端的md5對比,相同即文件傳輸過程沒有改變

 

 

二、知識鋪墊

 

  舊的知識不用就會忘記,Come On! 正式講ftp前先看下面的兩個點:

1. os.path.isfile(path)

  如果path是一個存在的文件,則返回True, 否則返回False

注:運行文件是test.py, test_bb.py與test.py在同一個目錄下

import os a = os.path.isfile("E:\\hello.py") print(a) b = os.path.isfile("hello.py") print(b) c = os.path.isfile("test_bb.py") print(c)

運行結果:

True
False
True

上面是我自己測試的,可以總結一下:

  os.path.isfile(path) 中path是一個路徑下的文件;也可以是與測試文件在同一目錄下的文件名

 

2.md5

   做了下面的測試,m是md5對象,最后輸出結果是一樣的,意味着一點一點加密與一起加密最后的結果是一樣的,為什么要這么做??因為如果要傳輸的文件幾G,那肯定是一行一行傳輸的,一行一行加密的

 

三、開始打碼

 

服務端:

 1 import socket  2 import os  3 import hashlib  4 
 5 server = socket.socket()  6 server.bind(("localhost", 9998))  7 
 8 server.listen(5)  9 
10 while True: 11     conn,addr = server.accept() 12     print("new conn:", addr) 13 
14     while True: 15         print("等待新指令") 16         data = conn.recv(1024) 17         if not data: 18             print("客戶端已端開") 19             break
20         cmd, filename = data.decode().split() 21         if os.path.isfile(filename):  #如果是文件
22             f = open(filename, "rb") 23             m = hashlib.md5()  # 創建md5對象
24             file_size = os.stat(filename).st_size  #獲取文件大小
25             conn.send(str(file_size).encode())     #發送文件大小
26             conn.recv(1024)       #接收客戶端確認
27             for line in f: 28                 conn.send(line)    #發送數據
29  m.update(line) 30             print(cmd, filename) 31             print("file md5", m.hexdigest()) 32  f.close() 33             conn.send(m.hexdigest().encode())  #發送md5
34 
35 server.close()

 

客戶端:

 1 import socket  2 import hashlib  3 
 4 client = socket.socket()  5 
 6 client.connect(("localhost", 9998))  7 
 8 while True:  9     cmd = input(">>>:").strip() 10     if len(cmd) == 0: 11         continue
12     if cmd.startswith("get"): 13         client.send(cmd.encode())    #客戶端發送指令
14         receive_file_size = client.recv(1024) 15         print("server file size",receive_file_size.decode()) 16         client.send("准備好接收文件了".encode())   #客戶端發送確認
17 
18         receive_size = 0 19         file_total_size = int(receive_file_size.decode()) 20         filename = cmd.split()[1] 21         f = open(filename + ".new", "wb")   #新文件,沒有的話會創建
22         m = hashlib.md5()       #生成md5對象
23 
24         while receive_size < file_total_size: 25             data = client.recv(1024) 26             receive_size += len(data) 27  m.update(data) 28             f.write(data)       #寫到文件
29         else: 30             new_file_md5 = m.hexdigest() #根據收到文件生成的md5
31             print("file recv done") 32             print("receive_size:", receive_size) 33             print("total_file_size:", file_total_size) 34  f.close() 35         receive_file_md5 = client.recv(1024) 36         print("server file md5:", receive_file_md5) 37         print("client file md5:", new_file_md5) 38 
39 
40 client.close()

 

如果看不大懂,可以先看我上一篇博文socket-ssh。

OK, 這樣就可以了,可以了嗎?嗎?廢話不多說,測試一下:

 

客戶端

 1 C:\Python34\python3.exe C:/Users/Administrator/PycharmProjects/laonanhai/day9/ftp_client.py  2 >>>:get test.py  3 server file size 477
 4 file recv done  5 receive_size: 477
 6 total_file_size: 477
 7 server file md5: b'18e84dd5d7b345db59526b1a35d07ef2'
 8 client file md5: 18e84dd5d7b345db59526b1a35d07ef2  9 >>>:get tes 10 server file size 39
11 file recv done 12 receive_size: 71
13 total_file_size: 39      #天吶,客戶端卡住了!!

 

服務端

C:\Python34\python3.exe C:/Users/Administrator/PycharmProjects/laonanhai/day9/ftp_server.py new conn: ('127.0.0.1', 62944) 等待新指令 get test.py file md5 18e84dd5d7b345db59526b1a35d07ef2 等待新指令 get tes file md5 c21eff88569c5ca26d8019bd891c27e9 等待新指令
View Code

 

四、問題分析

 

  看了上面的例子,覺得你們應該給我一個贊,我可是搞了很久,才終於搞出一個有粘包的測試。

 

OK,分析一下上面的問題。

客戶端執行第一個命令是正常的,但是執行get tes就卡住了。為什么呢??再分析一下,服務端發給客戶端的文件大小是39個字節,但是客戶端收到的卻是71個字節,My God,這意味着什么?很有可能粘包不是么!啥,你們不信,好。爸爸證明給你們看:

 

首先,打開原tes文件:

1 rgioknjt 2 bjfoikvj 3 bkitjmbvki 4 gvbtj

再打開運行后生成的新文件tes.new:

1 rgioknjt 2 bjfoikvj 3 bkitjmbvki 4 gvbtj 5 c21eff88569c5ca26d8019bd891c27e9

  

  什么情況?tes.new多了一行,怎么那么像md5? OK,打開服務端看下,My God!多了的一行就是服務器端(因為粘包)發給客戶端的md5,而客戶端沒有收到服務端的md5,就一直卡在 receive_file_md5 = client.recv(1024) 這里

服務端conn.send(line),接着又send(m.hexdigest())有可能產生粘包

 

五、ftp優化

 

  知道BUG所在后,怎么優化改進呢?客戶端再給服務器一次響應??但你不會覺得這樣來回交互太麻煩嗎?

接下來提供一個方法:

  客戶端已經知道接收多少數據,那讓客戶端接收文件時正好接收這些數據就可以了。EG:原本是收5M,但服務端發了5.1M,多的0.1M是md5,那在循環收文件時,收到5M就不再收,循環之后再recv就是md5了

嗯,很好,具體怎么實現呢?

客戶端來說,只有最后一次可能粘包,EG:服務端傳文件大小是50000KB,客戶端收文件倒數直到第二次共收到49800,還剩下200,客戶端最后一次還是收1024,這時若服務器(粘包)有發多余的數據就超了,就產生粘包了!

解決方法:客戶端最后一次判斷還剩多少未接收,直接收剩下的,不再收1024

 

優化代碼:

 

1         while receive_size < file_total_size: 2 
3             if file_total_size - receive_size > 1024:  #要收不止一次
4                 size = 1024
5             else:  #最后一次,剩多少收多少
6                 size = file_total_size - receive_size 7                 print("最后一次收:", size) 8             data = client.recv(size)

 

 

測試:

>>>:get tes server file size 39 最后一次收: 39 file recv done 39 39 server file md5: b'c21eff88569c5ca26d8019bd891c27e9' client file md5: c21eff88569c5ca26d8019bd891c27e9 >>>:

OK,無粘包,成功!

 

 

五、源碼:

 

server:

import socket import os import hashlib server = socket.socket() server.bind(("localhost", 9999)) server.listen(5) while True: conn,addr = server.accept() print("new conn:", addr) while True: print("等待新指令") data = conn.recv(1024) if not data: print("客戶端已端開") break cmd, filename = data.decode().split() if os.path.isfile(filename):  #如果是文件
            f = open(filename, "rb") m = hashlib.md5()  # 創建md5對象
            file_size = os.stat(filename).st_size  #獲取文件大小
            conn.send(str(file_size).encode())     #發送文件大小
            conn.recv(1024)       #接收客戶端確認
            for line in f: conn.send(line) #發送數據
 m.update(line) print("file md5", m.hexdigest()) f.close() conn.send(m.hexdigest().encode()) #發送md5

        print(cmd,filename) server.close()
View Code

 

client:

import socket import hashlib client = socket.socket() client.connect(("localhost", 9999)) while True: cmd = input(">>>:").strip() if len(cmd) == 0: continue
    if cmd.startswith("get"): client.send(cmd.encode()) #客戶端發送指令
        receive_file_size = client.recv(1024) print("server file size",receive_file_size.decode()) client.send("准備好接收文件了".encode())   #客戶端發送確認
 receive_size = 0 file_total_size = int(receive_file_size.decode()) filename = cmd.split()[1] f = open(filename + ".new", "wb")   #新文件,沒有的話會創建
        m = hashlib.md5()       #生成md5對象

        while receive_size < file_total_size: if file_total_size - receive_size > 1024:  #要收不止一次
                size = 1024
            else:  #最后一次,剩多少收多少
                size = file_total_size - receive_size print("最后一次收:", size) data = client.recv(size) receive_size += len(data) m.update(data) f.write(data) #寫到文件
        else: new_file_md5 = m.hexdigest() #根據收到文件生成的md5
            print("file recv done") print(receive_size, file_total_size) f.close() receive_file_md5 = client.recv(1024) print("server file md5:", receive_file_md5) print("client file md5:", new_file_md5) client.close()
View Code

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM