• [原创]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:Python34python3.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:Python34python3.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
     
  • 相关阅读:
    学Maven,这篇万余字的教程,真的够用了!
    15 个优秀开源的 Spring Boot 学习项目,一网打尽!
    Spring Boot2 系列教程(三十)Spring Boot 整合 Ehcache
    800java面试题
    Nginx开发从入门到精通
    曹工说mini-dubbo(1)--为了实践动态代理,我写了个简单的rpc框架
    redis好书推荐
    一步一步学solr--windows下solr5.4.0环境搭建
    Redis
    8 个最好的 jQuery 树形 Tree 插件
  • 原文地址:https://www.cnblogs.com/0zcl/p/6022774.html
Copyright © 2020-2023  润新知