当前位置: 首页 > news >正文

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

 

 

相关文章:

  • 信息收集的一些感悟
  • 【EXLIBRIS】维柯的书名
  • springboot 连接池wait_timeout超时设置
  • vista英文版语言包安装
  • 鼠年来喽
  • Webservice 接口开发简单例子
  • Silverlight 可以支持windows2000了
  • 龙威零式_团队项目例会记录_14
  • 曾经的熟悉,如今的陌生
  • BlueDroid介绍 【转】
  • NT的密码究竟放在哪
  • Linux桌面版横评:六、红旗桌面5.0
  • HBase跨版本数据迁移总结
  • 旧城镇文兴小学远程教育网点学校工作领导组
  • Struts2 中#、@、%和$符号的用途
  • “Material Design”设计规范在 ComponentOne For WinForm 的全新尝试!
  • CentOS 7 修改主机名
  • Hexo+码云+git快速搭建免费的静态Blog
  • JS实现简单的MVC模式开发小游戏
  • mysql中InnoDB引擎中页的概念
  • session共享问题解决方案
  • Stream流与Lambda表达式(三) 静态工厂类Collectors
  • Sublime text 3 3103 注册码
  • uva 10370 Above Average
  • 基于Vue2全家桶的移动端AppDEMO实现
  • 面试遇到的一些题
  • 浅谈Kotlin实战篇之自定义View图片圆角简单应用(一)
  • 如何设计一个比特币钱包服务
  • 项目实战-Api的解决方案
  • 小程序开发之路(一)
  • 小而合理的前端理论:rscss和rsjs
  • 优化 Vue 项目编译文件大小
  • 你对linux中grep命令知道多少?
  • Play Store发现SimBad恶意软件,1.5亿Android用户成受害者 ...
  • # MySQL server 层和存储引擎层是怎么交互数据的?
  • #define与typedef区别
  • #laravel 通过手动安装依赖PHPExcel#
  • #mysql 8.0 踩坑日记
  • #stm32驱动外设模块总结w5500模块
  • $redis-setphp_redis Set命令,php操作Redis Set函数介绍
  • (附源码)ssm基于微信小程序的疫苗管理系统 毕业设计 092354
  • (教学思路 C#之类三)方法参数类型(ref、out、parmas)
  • (力扣)1314.矩阵区域和
  • (六)软件测试分工
  • (论文阅读30/100)Convolutional Pose Machines
  • (免费领源码)python#django#mysql校园校园宿舍管理系统84831-计算机毕业设计项目选题推荐
  • (学习日记)2024.01.19
  • (一)eclipse Dynamic web project 工程目录以及文件路径问题
  • (转)Mysql的优化设置
  • .mysql secret在哪_MySQL如何使用索引
  • .NET 5种线程安全集合
  • .NET Compact Framework 3.5 支持 WCF 的子集
  • .net core 微服务_.NET Core 3.0中用 Code-First 方式创建 gRPC 服务与客户端
  • .NET Framework 和 .NET Core 在默认情况下垃圾回收(GC)机制的不同(局部变量部分)
  • .NET Micro Framework初体验