FTP文件服务器

"""
FTP服务器(File Transfer Protocol Server)是在互联网上提供文件存储和访问服务的计算机,它们依照FTP协议提供服务。 FTP是File Transfer Protocol(文件传输协议)。顾名思义,就是专门用来传输文件的协议。简单地说,支持FTP协议的服务器就是FTP服务器。
"""

从上述定义中,我们知道了文件服务器的两个核心功能:上传和下载

在现实生活中,常用的文件服务器有哪些了?

Xftp,WinScp,FileZilla

如何实现FTP文件服务器

"""
我们需要一个客户端,和一个服务端(基本需求):
1、客户端用来发送服务请求
2、服务端用来处理服务请求,并返回相应的数据给客户端
3、使用socket来实现客户端和服务端之间的通信
"""

实验准备

为了模拟并发的效果,我们准备两个客户端,一个服务端

"""
服务端:阿里云服务器(172.21.44.11)
客户端1:本地虚拟机1(192.168.80.129)
客户端2:本地虚拟机1(192.168.80.130)
"""

主要实现功能

"""
1.文件上传
2.文件下载
3.文件MD5校验
4.文件进度条显示
"""

使用模块

import os
import sys
import struct
import json
import hashlib
import argparse
from prettytable import PrettyTable
from socket import *

客户端代码

"""
​
FTP客户端
time:2020/4/21
version:1.0
coder:surpass
​
"""
​
import os
import sys
import struct
import json
import hashlib
import argparse
from prettytable import PrettyTable
from socket import *
​
func_dic = {'upload': ['上传文件', 'upload'],'download': ['下载文件', 'download']
}
​
base_path = os.path.dirname(os.path.abspath(__file__))
​
​
def get_file_md5(file_path):if os.path.isfile(file_path):size = os.path.getsize(file_path) // 5m = hashlib.md5()with open(file_path, mode='rb') as f:for i in range(4):m.update(f.read(5))f.seek(size, 1)return m.hexdigest()
​
​
def check_file_md5(filename, file_md5):size = os.path.getsize(filename) // 5m = hashlib.md5()with open(filename, mode='rb') as f:for i in range(4):m.update(f.read(5))f.seek(size, 1)if m.hexdigest() == file_md5:return True
​
​
def get_pretty_tb(table_list, *args):tb = PrettyTable(field_names=table_list)for item in args:tb.add_row(item)return tb
​
​
class MYFTPClient:def __init__(self, ip_address, buf_size=1024):self.ip_address = ip_addressself.buf_size = buf_sizeself.client = socket(AF_INET, SOCK_STREAM)self.client_connect()
​def client_connect(self):try:self.client.connect_ex(self.ip_address)except Exception as e:sys.stderr.write(f'connect error:{e}')
​def client_close(self):self.client.close()
​def send_head_pack(self, cmd=None, file=None, remote_path=None, file_path=None,filename=None, file_size=None, file_md5=None):"""cmd:操作命令,file:文件或文件夹集合,remote_path:远程路径,file_path:文件路径filename:文件名,file_size:文件大小,file_md5:文件的md5值"""head_dic = {'cmd': cmd, 'file': file, 'remote_path': remote_path, 'file_path': file_path,'filename': filename, 'file_size': file_size, 'file_md5': file_md5}head_dic = {k: v for k, v in head_dic.items() if v is not None}# print(head_dic)head_json = json.dumps(head_dic)head_bytes = bytes(head_json, encoding='utf-8')head_struct = struct.pack('i', len(head_bytes))# 将报头传给服务端self.client.send(head_struct)self.client.send(head_bytes)
​def show_remote_path(self, remote_path=None):"""获取远端的文件夹,包括文件和文件夹"""self.send_head_pack(cmd='show_remote_path', file={}, remote_path=remote_path)
​recv_size = struct.unpack('i', self.client.recv(4))[0]file_dic = json.loads(self.client.recv(recv_size).decode('utf-8'))
​print('获取远程路径'.center(60, '='))print(f"{file_dic.get('base_path')}")table_list = ['名字', '属性', '大小', '创建日期']result = []file_list = []dir_list = []for item in file_dic['file_list']:item.insert(1, 'file')result.append(item)file_list.append(os.path.join(file_dic.get('base_path'), item[0]))for obj in file_dic['dir_list']:obj.insert(1, 'dir')result.append(obj)dir_list.append(os.path.join(file_dic.get('base_path'), obj[0]))tb_show = get_pretty_tb(table_list, *result)print(tb_show)dir_list.append(os.path.dirname(file_dic.get('base_path')))return file_list, dir_list
​def show_progress(self, func=None, filename=None, percent=None):res = int(50 * percent) * '#'sys.stdout.write('r%s %s[%-50s] %d%%' % (func, filename, res, int(100 * percent)))sys.stdout.flush()
​def upload(self, msg):cmd = msgwhile 1:filepath = input('请输入上传文件的路径>>:').strip()if not filepath: continuebreakfilename = os.path.basename(filepath)file_size = os.path.getsize(filepath)file_md5 = get_file_md5(filepath)if not file_md5:raise FileNotFoundError(f'file does not exits:{filepath}')self.send_head_pack(cmd=cmd, filename=filename, file_size=file_size, file_md5=file_md5)current_size = 0with open(filepath, mode='rb') as f:for line in f:current_size += len(line)self.show_progress('upload', filename, current_size // file_size)self.client.send(line)res = self.client.recv(4)msg_len = struct.unpack('i', res)[0]msg = self.client.recv(msg_len).decode('utf-8')print(f'n{msg}')
​def download(self, msg):cmd = msgfile_all = self.show_remote_path()while 1:choice = input("请选择相应的文件下载,或者通过选相应的路径切换路径:")if choice in file_all[1]:file_all = self.show_remote_path(choice)continueelif choice in file_all[0]:self.send_head_pack(cmd=cmd, file_path=choice, filename=os.path.basename(choice))
​file_struct = struct.unpack('i', self.client.recv(4))[0]file_json = self.client.recv(file_struct).decode('utf-8')file_dic = json.loads(file_json)# print(file_dic)filename = file_dic.get('filename')file_size = file_dic.get('file_size')download_size = 0while download_size < file_size:content = self.client.recv(1024)download_size += len(content)self.show_progress('download', filename, download_size // file_size)with open(os.path.join(base_path, filename), mode='ab+') as fw:fw.write(content)else:file_path = os.path.join(base_path, filename)if check_file_md5(file_path, file_dic.get('file_md5')):sys.stdout.write(f'ndownload {filename} success!n')else:os.remove(file_path)sys.stderr.write(f'nfiled to download {filename}!n')breakelse:print('请输入正确的文件或路径!')
​def run(self):while 1:func_table_list = ['功能选项', '功能名称', '操作示例']result = [(lambda x, y, z: [x, y, z])(k, v[0], v[1]) for k, v in func_dic.items()]tb_func = get_pretty_tb(func_table_list, *result)msg = input(f'{tb_func}nplease enter the operate signal>>:').strip()if not msg: continueif hasattr(self, msg):func = getattr(self, msg)func(msg)_continue = input('是否继续进行操作(y,n):').strip().lower()if _continue == 'n': breakself.client_close()
​
​
def main():parser = argparse.ArgumentParser(description='MY FTP Client v1.0')parser.add_argument('--ip', action='store', dest='ip', required=True, help='The remote ip to connect')parser.add_argument('--port', action='store', dest='port', required=True, type=int, help='The port to connect')parser.add_argument('--buf_size', action='store', dest='buf_size', type=int,help='the max size(one time) to upload/download')args = parser.parse_args()if args.buf_size:client = MYFTPClient((args.ip, args.port), args.buf_size)else:client = MYFTPClient((args.ip, args.port))client.run()
​
​
if __name__ == '__main__':main()

服务端代码

基于socketserver实现并发

"""
FTP服务端
time:2020/4/21
version:v1.0
coder:surpass
"""
​
import os
import sys
import json
import struct
import time
import hashlib
import socketserver
​
IP_ADDRESS = ('0.0.0.0', 9090)
BUF_SIZE = 1024
​
​
def get_file_md5(file_path):if os.path.isfile(file_path):size = os.path.getsize(file_path) // 5m = hashlib.md5()with open(file_path, mode='rb') as f:for i in range(4):m.update(f.read(5))f.seek(size, 1)return m.hexdigest()
​
​
def check_file_md5(filename, file_md5):size = os.path.getsize(filename) // 5m = hashlib.md5()with open(filename, mode='rb') as f:for i in range(4):m.update(f.read(5))f.seek(size, 1)if m.hexdigest() == file_md5:return True
​
​
class MYFTPServer(socketserver.BaseRequestHandler):def handle(self):while 1:try:head_struct = self.request.recv(4)if not head_struct: breakhead_len = struct.unpack('i', head_struct)[0]head_dic_json = self.request.recv(head_len).decode('utf-8')head_dic = json.loads(head_dic_json)if hasattr(self, head_dic.get('cmd')):func = getattr(self, head_dic.get('cmd'))func(head_dic)except Exception as e:sys.stderr.write(f'error message:{e}')self.request.close()
​def show_remote_path(self, head_dic):if not head_dic.get('remote_path'):base_path = os.path.dirname(os.path.abspath(__file__))else:base_path = head_dic.get('remote_path')head_dic['file']['base_path'] = base_pathhead_dic['file']['file_list'] = []head_dic['file']['dir_list'] = []for obj in os.listdir(base_path):obj_abs_path = os.path.join(base_path, obj)obj_name = os.path.basename(obj_abs_path)obj_size = os.path.getsize(obj_abs_path)timestamp = os.path.getmtime(obj_abs_path)obj_mtime = time.strftime('%Y/%m/%d %X', time.localtime(timestamp))if os.path.isfile(obj_abs_path):head_dic['file']['file_list'].append([obj_name, obj_size, obj_mtime])elif os.path.isdir(obj_abs_path):obj_size = ''head_dic['file']['dir_list'].append([obj_name, obj_size, obj_mtime])head_json = json.dumps(head_dic.get('file'))head_bytes = bytes(head_json, encoding='utf-8')head_struct = struct.pack('i', len(head_bytes))
​self.request.send(head_struct)self.request.send(head_json_bytes)
​def upload(self, head_dic):file_size = head_dic.get('file_size')recv_size = 0while recv_size < file_size:content = self.request.recv(BUF_SIZE)recv_size += len(content)with open(head_dic.get('filename'), mode='ab+') as f:f.write(content)else:if check_file_md5(head_dic.get('filename'), head_dic.get('file_md5')):msg = f"upload file {head_dic.get('filename')} success!"else:os.remove(head_dic.get('filename'))msg = f"failed to upload file,file {head_dic.get('filename')} is damaged!"msg_len = struct.pack('i', len(msg.encode('utf-8')))self.request.send(msg_len)self.request.send(msg.encode('utf-8'))
​def download(self, head_dic):file_path = head_dic.get('file_path')filename = os.path.basename(file_path)file_md5 = get_file_md5(file_path)file_size = os.path.getsize(file_path)file_dic = {'filename': filename, 'file_size': file_size, 'file_md5': file_md5}file_json = json.dumps(file_dic)file_bytes = bytes(file_json, encoding='utf-8')file_struct = struct.pack('i', len(file_bytes))
​# 发送报头消息self.request.send(file_struct)self.request.send(file_bytes)
​with open(file_path, mode='rb') as fr:for line in fr:self.request.send(line)
​
​
def main():server = socketserver.ThreadingTCPServer(IP_ADDRESS, MYFTPServer)server.serve_forever()
​
​
if __name__ == '__main__':main()

但是采用socketserver实现并发,容易遇到地址占用问题:

解决方案:

"""
1.使用Thread实现并发
2.使用 setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)解决地址占用问题
"""

基于Thread实现并发

"""
FTP服务端
time:2020/4/24
version:v2.0
coder:surpass
"""
​
import os
import sys
import json
import struct
import time
import hashlib
from socket import *
from threading import Thread
​
IP_ADDRESS = ('0.0.0.0', 9090)
BUF_SIZE = 1024
BACK_LOG = 5
​
​
class MYFTPServer:def __init__(self, ip_address):self.ip_address = ip_addressself.server = socket(AF_INET, SOCK_STREAM)
​def bind(self):self.server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)self.server.bind(self.ip_address)
​def listen(self):self.server.listen(BACK_LOG)
​def accept(self):return self.server.accept()
​@staticmethoddef show_remote_path(head_dic, conn):if not head_dic.get('remote_path'):remote_path = os.path.dirname(os.path.abspath(__file__))else:remote_path = head_dic.get('remote_path')head_dic['file']['base_path'] = remote_pathhead_dic['file']['file_list'] = []head_dic['file']['dir_list'] = []for item in os.listdir(remote_path):obj_path = os.path.join(remote_path, item)obj_mtime = os.path.getmtime(obj_path)if os.path.isfile(obj_path):head_dic['file']['file_list'].append(os.path.basename(obj_path),os.path.getsize(obj_path),time.strftime('%Y-%m-%d %X', time.localtime(obj_mtime)))elif os.path.isdir(obj_path):head_dic['file']['dir_list'].append(os.path.basename(obj_path), '',time.strftime('%Y-%m-%d %X', time.localtime(obj_mtime)))head_json = json.dumps(head_dic['file'])head_bytes = bytes(head_json, encoding='utf-8')head_struct = struct.pack('i', len(head_bytes))
​conn.send(head_struct)conn.send(head_bytes)
​@staticmethoddef check_file_md5(filename, file_size, file_md5):m = hashlib.md5()move_size = file_size // 5with open(filename, mode='rb') as fr:for i in range(4):m.update(fr.read(5))fr.seek(move_size, 1)if m.hexdigest() == file_md5:return True
​@staticmethoddef get_file_md5(file_path, file_size):m = hashlib.md5()move_size = file_size // 5with open(file_path, mode='rb') as fr:for i in range(4):m.update(fr.read(5))fr.seek(move_size, 1)return m.hexdigest()
​def upload(self, head_dic, conn):recv_size = 0file_size = head_dic.get('file_size')while recv_size < file_size:content = conn.recv(BUF_SIZE)recv_size += len(content)with open(head_dic.get('filename'), mode='ab+') as fw:fw.write(content)else:filename = head_dic.get('filename')file_size = head_dic.get('file_size')file_md5 = head_dic.get('file_md5')if self.check_file_md5(filename, file_size, file_md5):msg = f'upload file {filename} success!'else:os.remove(head_dic.get('file_path'))msg = f'failed to upload file {filename}!'msg_struct = struct.pack('i', len(msg.encode('utf-8')))conn.send(msg_struct)conn.send(msg.encode('utf-8'))
​def download(self, head_dic, conn):file_path = head_dic.get('file_path')file_size = os.path.getsize(file_path)filename = head_dic.get('filename')file_md5 = self.get_file_md5(file_path, file_size)head_dic = {'filename': filename, 'file_size': file_size, 'file_md5': file_md5}head_json = json.dumps(head_dic)head_bytes = bytes(head_json, encoding='utf-8')head_struct = struct.pack('i', len(head_bytes))conn.send(head_struct)conn.send(head_bytes)
​with open(file_path, mode='rb') as fr:for line in fr:conn.send(line)
​def communicate(self, conn, addr):print(f'收到客户端[{addr[0]}:{addr[1]}]的信息...')while 1:try:head_struct = conn.recv(4)if not head_struct: breakhead_len = struct.unpack('i', head_struct)[0]head_bytes = conn.recv(head_len)head_dic = json.loads(head_bytes.decode('utf-8'))if hasattr(self, head_dic.get('cmd')):func = getattr(self, head_dic.get('cmd'))func(head_dic, conn)except ConnectionResetError as e:sys.stderr.write(f'connect error:{e}')conn.close()
​def run(self):self.bind()self.listen()while 1:conn, addr = self.accept()t = Thread(target=self.communicate, args=(conn, addr))t.start()
​
​
if __name__ == '__main__':server = MYFTPServer(IP_ADDRESS)server.run()

上传功能

客户端1:

客户端2:

服务端:

下载功能

客户端1:

客户端2:

服务端:

实现ftp_FTP文件服务器的实现相关推荐

  1. 文件服务器共享目录设置(二)

    三.             设置磁盘配额及文件屏蔽 为了防止用户无限制的上传文件,或上传病毒木马等文件,还需要进一步加强安全设置.用磁盘配额来管理用户的文件夹空间,用文件屏蔽来阻止用户上传有风险的文 ...

  2. c 找文件服务器文件,Linux C/C++项目:虚拟文件服务器(功能匹配百度网盘)

    github地址:https://github.com/Worthy-Wang/NetDisk FTP文件服务器设计思路: 1.整体架构采用线程池结合epoll监听 客户端通过sockfd与服务器通信 ...

  3. Windows 文件服务器升级跨林迁移(二)

    迁移文件服务器 登陆到ADMT迁移服务器上,打开ADMT管理控制台,邮件点击AD迁移工具,选择"计算机迁移向导" 点击下一步 选择源域为旧的控,目标域为新域控,点击下一步 从域中选 ...

  4. Windows Server 2012 文件服务器群集

    概述:之前已经测试了Windows Server 2012系统群集.Hyper-V群集,接下来将测试Windows Server 2012 文件服务器群集功能. 实验环境: 4台服务器都为Window ...

  5. 文件服务器的内存要多少,文件服务器内存要多大

    文件服务器内存要多大 内容精选 换一换 Windows场景中,当把源端服务器迁移到华为云后,目的端服务器C盘的已用空间比对应源端服务器C盘的已用空间大至少1GB,而不是与源端服务器C盘的已用空间一致, ...

  6. 文件服务器搭建 xp,xp文件服务器搭建

    xp文件服务器搭建 内容精选 换一换 登录Windows操作系统的弹性云服务器时,需使用密码方式登录.因此,用户需先根据创建弹性云服务器时使用的密钥文件,获取该弹性云服务器初始安装时系统生成的管理员密 ...

  7. 使用Nginx搭建前端静态服务器+文件服务器

    大家可能被虚拟主机不太了解,那我们的解释一下,虚拟主机指在一台物理主机服务器上划分出多个磁盘空间,每个磁盘空间都是一个虚拟主机,每台虚拟主机都可以对外提供Web服务,并且互不干扰. 一.Nginx的虚 ...

  8. 文件服务器raid1设置,文件服务器raid1设置

    文件服务器raid1设置 内容精选 换一换 外部镜像文件在从原平台导出前,没有按照"Windows操作系统的镜像文件限制"的要求完成初始化操作,推荐您使用弹性云服务器完成相关配置. ...

  9. java独立承担,Java使用独立文件服务器

    应用程序中免不了要上传下载文件,实现方式由简单到复杂有很多种: 最简单的,文件上传后服务端程序将文件写到服务器上一个指定的目录 稍微复杂一点,我们需要和应用服务器分开的单独文件服务器 再复杂一点,需要 ...

最新文章

  1. 2019年值得关注的九个AI创业风口
  2. 一大波数据来袭 网络桃色陷阱暗藏致命危机
  3. python中文读音ndarray-numpy中的ndarray方法和属性详解
  4. html5 java交互_html5+jquery与ssm进行json交互集成项目
  5. Ubuntu下千千静听Audacious的安装步骤详解
  6. idea编辑springboot,如何打成war包
  7. 爱了!爱了!Markdown 必备组合神器!
  8. JSP过滤器Filter配置过滤类型汇总
  9. WebM VP8 SDK Usage/关于WebM VP8 SDK的用法
  10. CDB和PDB的创建、连接、启动、关闭
  11. LAMP、LNMP实战之九搭建cms、blog、bbs(持续更新)
  12. python画条形图-用Matplotlib如何绘制条形图、直方图和散点图
  13. Android开发/源码资源汇总
  14. 343.整数拆分(力扣leetcode) 博主可答疑该问题
  15. 全球DEM下载 90米、30米、12.5米等各种精度DEM数据
  16. RJ45与网络变压器脚位及网线线序的关系?
  17. 微信小程序 讲座预约签到系统java python php
  18. 抖音:技术优化打造最佳创作体验
  19. 史上最详细SharePoint 2007安装步骤图解新手教程
  20. 《算法第一步》出版啦!

热门文章

  1. 轻量高效!清华智能计算实验室开源基于PyTorch的视频 (图片) 去模糊框架SimDeblur
  2. 实现一个bind函数
  3. mongodb(2)
  4. Tomcat虚拟主机
  5. Nginx 教程- 获取真实IP模块 - http_realip_module
  6. 研发手Q推广遇到的一系列问题
  7. 全闪存阵列的“五十度黑”
  8. 1.2 Name That Number
  9. C语言程序设计 数组,结构体和指针练习题
  10. 【opencv系列03】OpenCV4.X视频捕获与显示