[原创]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