test: 博客美化中……

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

 

 
posted @ 2016-11-02 14:15  前程明亮  阅读(3683)  评论(0编辑  收藏  举报