今天来讲讲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,那肯定是一行一行传输的,一行一行加密的。

三、开始打码

服务端:

 import socket
import os
import hashlib server = socket.socket()
server.bind(("localhost", 9998)) 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(cmd, filename)
print("file md5", m.hexdigest())
f.close()
conn.send(m.hexdigest().encode()) #发送md5 server.close()

客户端:

 import socket
import hashlib client = socket.socket() client.connect(("localhost", 9998)) 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:
data = client.recv(1024)
receive_size += len(data)
m.update(data)
f.write(data) #写到文件
else:
new_file_md5 = m.hexdigest() #根据收到文件生成的md5
print("file recv done")
print("receive_size:", receive_size)
print("total_file_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()

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

OK, 这样就可以了,可以了吗?吗?废话不多说,测试一下:

客户端

 C:\Python34\python3.exe C:/Users/Administrator/PycharmProjects/laonanhai/day9/ftp_client.py
>>>:get test.py
server file size 477
file recv done
receive_size: 477
total_file_size: 477
server file md5: b'18e84dd5d7b345db59526b1a35d07ef2'
client file md5: 18e84dd5d7b345db59526b1a35d07ef2
>>>:get tes
server file size 39
file recv done
receive_size: 71
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
等待新指令

四、问题分析

  看了上面的例子,觉得你们应该给我一个赞,我可是搞了很久,才终于搞出一个有粘包的测试。

OK,分析一下上面的问题。

客户端执行第一个命令是正常的,但是执行get tes就卡住了。为什么呢??再分析一下,服务端发给客户端的文件大小是39个字节,但是客户端收到的却是71个字节,My God,这意味着什么?很有可能粘包不是么!啥,你们不信,好。爸爸证明给你们看:

首先,打开原tes文件:

 rgioknjt
bjfoikvj
bkitjmbvki
gvbtj

再打开运行后生成的新文件tes.new:

 rgioknjt
bjfoikvj
bkitjmbvki
gvbtj
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!

优化代码:

         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)

测试:

>>>: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()

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()
 

[原创]python之socket-ftp的更多相关文章

  1. Python:socket实现ftp程序

    刚开始学习socket编程,还不是特熟练,码了好长时间,中间遇到许多问题,记录一下用socketserver写ftp server端: #!/usr/bin/env python import soc ...

  2. Python 之socket的应用

    本节主要讲解socket编程的有关知识点,顺便也会讲解一些其它的关联性知识: 一.概述(socket.socketserver): python对于socket编程,提供了两个模块,分别是socket ...

  3. 操作系统底层原理与Python中socket解读

    目录 操作系统底层原理 网络通信原理 网络基础架构 局域网与交换机/网络常见术语 OSI七层协议 TCP/IP五层模型讲解 Python中Socket模块解读 TCP协议和UDP协议 操作系统底层原理 ...

  4. python之实现ftp上传下载代码(含错误处理)

    # -*- coding: utf-8 -*- #python 27 #xiaodeng #python之实现ftp上传下载代码(含错误处理) #http://www.cnblogs.com/kait ...

  5. 【Python学习 】Python实现的FTP上传和下载功能

    一.背景 最近公司的一些自动化操作需要使用Python来实现FTP的上传和下载功能.因此参考网上的例子,撸了一段代码来实现了该功能,下面做个记录. 二.ftplib介绍 Python中默认安装的ftp ...

  6. python通过socket实现多个连接并实现ssh功能详解

    python通过socket实现多个连接并实现ssh功能详解 一.前言 上一篇中我们已经知道了客户端通过socket来连接服务端,进行了一次数据传输,那如何实现客户端多次发生数据?而服务端接受多个客户 ...

  7. python之socket编程(一)

    socket之前我们先来熟悉回忆几个知识点. OSI七层模型 OSI(Open System Interconnection)参考模型是国际标准化组织(ISO)制定的一个用于计算机或通信系统间互联的标 ...

  8. 进击的Python【第十章】:Python的socket高级应用(多进程,协程与异步)

    Python的socket高级应用(多进程,协程与异步)

  9. Python底层socket库

    Python底层socket库将Unix关于网络通信的系统调用对象化处理,是底层函数的高级封装,socket()函数返回一个套接字,它的方法实现了各种套接字系统调用.read与write与Python ...

随机推荐

  1. 如何用Perl对Excel的数据进行提取并分析

    巡检类工作经常会出具日报,最近在原有日报的基础上又新增了一个表的数据量统计日报,主要是针对数据库中使用较频繁,数据量又较大的31张表.该日报有两个sheet组成,第一个sheet是数据填写,第二个sh ...

  2. objective-c 语法快速过(3)

    oc 里的匿名对象 oc 这里,很少用到,因为并不适用于oc的内存管理,只是面试笔试也许出现,要求能看懂,不要在项目里这样写,因为写匿名对象,会造成内存泄露 #import <Foundatio ...

  3. Android APK如何签名

    Android项目以它的包名作为唯一标识,如果在同一设备上安装两个相同的应用,后面安装的应用就会覆盖前面安装的应用.为了避免这种情况的发生,我们需要对作为产品发布的应用进行签名. 签名其实有两个作用: ...

  4. 附录C 编译安装Hive

    如果需要直接安装Hive,可以跳过编译步骤,从Hive的官网下载编译好的安装包,下载地址为http://hive.apache.org/downloads.html . C.1  编译Hive C.1 ...

  5. 1Z0-053 争议题目解析703

    1Z0-053 争议题目解析703 考试科目:1Z0-053 题库版本:V13.02 题库中原题为: 703.Given below are RMAN commands to enable backu ...

  6. 【十大经典数据挖掘算法】CART

    [十大经典数据挖掘算法]系列 C4.5 K-Means SVM Apriori EM PageRank AdaBoost kNN Naïve Bayes CART 1. 前言 分类与回归树(Class ...

  7. “全能”选手—Django 1.10文档中文版Part3

    Django 1.10官方文档的入门教程已经翻译完毕,后续的部分将不会按照顺序进行翻译,而是挑重点的先翻译. 有兴趣的可以关注我的博客. 第一部分传送门 第二部分传送门 第四部分传送门 3.2 模型和 ...

  8. 修复DapperExtension做Insert对象主键为Guid时不能赋值的问题

    最新的dapperExtension有个bug,就是当做Insert操作的时候,实体的主键类型为GUID的时候,会自动生产一个新的GUID替换原来的GUID,使得使用者在Insert的时候不能在外部指 ...

  9. identity与ASP.NET 模拟

    默认情况下,ASP.NET应用程序以本机的ASPNET帐号运行,该帐号属于普通用户组,权限受到一定的限制,以保障ASP.NET应用程序运行的安全.但是有时需要某个ASP.NET应用程序或者程序中的某段 ...

  10. 设计模式(四)抽象工厂模式(Abstract Factory Pattern)

    一.引言 在上一专题中介绍了工厂方法模式,工厂方法模式是为了克服简单工厂模式的缺点而设计出来的,简单工厂模式的工厂类随着产品类的增加需要增加额外的代码,而工厂方法模式每个具体工厂类只完成单个实例的创建 ...