今天来讲讲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. 从零开始编写自己的C#框架(9)——数据库设计与创建

    对于千万级与百万级数据库设计是有所区别的,由于本项目是基于中小型软件开发框架来设计,记录量相对会比较少,所以数据库设计时考虑的角度是:与开发相结合:空间换性能:空间换开发效率:减少null异常.... ...

  2. EntityFramework 如何进行异步化(关键词:async·await·SaveChangesAsync·ToListAsync)

    应用程序为什么要异步化?关于这个原因就不多说了,至于现有项目中代码异步化改进,可以参考:实际案例:在现有代码中通过async/await实现并行 这篇博文内容针对的是,EntityFramework ...

  3. Android样式之selector

    日常开发当中,难免会出现这样一种情况,为一个按钮.TextView...设置一个点击状态的颜色改变,可能是background背景的改变,也可能是字体颜色的改变,简单点说:默认状态下,字体颜色或者背景 ...

  4. mysql悲观锁总结和实践--转

    原文地址:http://chenzhou123520.iteye.com/blog/1860954 最近学习了一下数据库的悲观锁和乐观锁,根据自己的理解和网上参考资料总结如下: 悲观锁介绍(百科): ...

  5. Microsoft.Office.Interop.Word 创建word

    Microsoft.Office.Interop.Word 创建word 转载:http://www.cnblogs.com/chenbg2001/archive/2010/03/14/1685746 ...

  6. 将Excel文件转换为Html

    将Excel文件转换为HTML 背景 我的工作有时会涉及到财务数据的处理.我们大家都知道,Excel文件在处理数据中很流行并且被广泛使用.Excel让我们可以将存储在里面的数据进行数学计算.我在工作中 ...

  7. 注册表(C#)

    Windowa注册表是包含Windows安装,用户喜好以及以安装软件和设备的所有配置信息的核心储存库.COM组件必须把它的信息出存在注册表中,才能被客户程序使用.注册表也包含了一些系统配置的信息,这些 ...

  8. Oracle 英文 非标准格式 日期 格式化

    最近在处理一张表的时候,需要按照日期排序,日期字段中日期的格式有两种. 格式一:07-Aug-2015 格式二:10/28/16 日期转化及格式化sql语句: select to_date('07-A ...

  9. Redis学习笔记——初级

    1. Redis是什么.特点.优势 Redis是一个开源的使用C语言编写.开源.支持网络.可基于内存亦可持久化的日志型.高性能的Key-Value数据库,并提供多种语言的API. 它通常被称为数据结构 ...

  10. cron表达式详解[转]

    Cron表达式是一个字符串,字符串以5或6个空格隔开,分为6或7个域,每一个域代表一个含义,Cron有如下两种语法格式: Seconds Minutes Hours DayofMonth Month ...