SELECTORS模块实现并发简单版FTP
环境:windows, python 3.5
功能:
使用SELECTORS模块实现并发简单版FTP
允许多用户并发上传下载文件 结构:
ftp_client ---|
bin ---|
start_client.py ......启动客户端
conf---|
config.py ......客户端参数配置
system.ini ......客户端参数配置文件
core---|
clients.py ......客户端主程序
home ......默认下载路径
ftp_server ---|
bin ---|
start_server.py ......启动服务端
conf---|
config.py ......服务端参数配置
system.ini ......服务端参数配置文件
core---|
servers.py ......服务端主程序
db ---|
data.py ......存取用户数据
home ......默认上传路径 功能实现:
客户端输入命令,根据命令通过映射进入相应方法,上传或者下载;
服务端用SELECTORS模块实现并发,接收数据,通过action用映射进入相应方法; 如何使用:
启动start_server.py,启动start_client.py;
在客户端输入get pathname/put pathname进行上传下载文件,开启多个客户端可并发上传下载。 core:
#!/usr/bin/env python
# -*-coding:utf-8-*-
# Author:zh
import socket
import os
import json
import random
import conf
PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))+os.sep+"home"+os.sep # 默认下载存放路径为当前路径的home目录下 class FtpClient(object):
def __init__(self):
self.client = socket.socket() def connect(self, ip, port):
self.client.connect((ip, port)) def interactive(self):
# 入口
while True:
cmd = input(">>").strip()
if len(cmd) == 0:
continue
cmd_list = cmd.split()
if len(cmd_list) == 2:
cmd_str = cmd_list[0]
if hasattr(self, cmd_str):
func = getattr(self, cmd_str)
func(cmd)
else:
print("输入格式错误")
self.help()
else:
print("输入格式错误")
self.help() @staticmethod
def help():
msg = '''
get filepath(下载文件)
put filepath(上传文件)
'''
print(msg) def get(self, *args):
# 下载
data = args[0].split()
action = data[0]
path = data[1]
send_msg = {
"action": action, # 第一次请求,需要服务端返回文件信息
"file_path": path # F:\oracle课程\(必读)11gOCP考试流程与须知.rar
}
self.client.send(json.dumps(send_msg).encode())
data = self.client.recv(1024)
data = json.loads(data.decode())
if data["sign"] == "":
file_length = data["length"]
file_path = PATH+data["filename"]
if os.path.isfile(file_path):
file_path = file_path + str(random.randint(1,1000))
file = open(file_path, "wb")
send_msg_2 = {
"action": "get_file_data", # 第二次请求,请求客户端给发送文件数据
"file_path": path # 第二次请求依然需要传入文件路径,也可以在服务端利用全局变量保存
}
self.client.send(json.dumps(send_msg_2).encode())
get_length = 0
while get_length < int(file_length):
data = self.client.recv(1024)
file.write(data)
get_length += len(data)
else:
file.close()
if get_length == int(file_length):
print("下载成功")
else:
print("文件传输失败")
os.remove(file_path) # 如果传输过程出现问题,删除文件
else:
print("文件不存在") def put(self, *args):
# 上传
data = args[0].split()
action = data[0]
file_path = data[1]
if os.path.isfile(file_path):
msg = {
"action": action, # 上传第一次发送,发送文件大小和姓名
"length": os.stat(file_path).st_size,
"filename": file_path[file_path.rfind("\\") + 1:]
}
self.client.send(json.dumps(msg).encode())
self.client.recv(1024) # 避免黏包
file = open(file_path, "rb")
for line in file:
self.client.send(line) # 上传第二次发送,发送文件数据,没有action,在服务端通过try,json报错的进入接收数据的方法里
else:
file.close()
sign = self.client.recv(1024)
if sign == b'':
print("上传成功")
else:
print("上传失败")
else:
print("文件不存在") def run():
data = conf.config.Configuration()
conf_data = data.get_config()
obj = FtpClient()
obj.connect(conf_data[0][1], int(conf_data[1][1]))
obj.interactive()
clients
core:
#!/usr/bin/env python
# -*-coding:utf-8-*-
# Author:zh
import selectors
import socket
import json
import os
import random
import conf
PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))+os.sep+"home"+os.sep # 默认上传存放路径为当前路径的home目录下 class SelectorFtp(object): def __init__(self):
self.sel = selectors.DefaultSelector() def connect(self, ip, port):
sock = socket.socket()
sock.bind((ip, port))
sock.listen(100)
sock.setblocking(False)
self.sel.register(sock, selectors.EVENT_READ, self.accept)
while True:
events = self.sel.select()
for key, mask in events:
callback = key.data
callback(key.fileobj, mask) def accept(self, sock, mask):
conn, addr = sock.accept() # Should be ready
conn.setblocking(False)
self.sel.register(conn, selectors.EVENT_READ, self.interactive) def interactive(self, conn, mask):
# 回调函数,每次接收消息都判断action,根据action分别调用不同函数信息交互
try:
data = conn.recv(1024)
if data:
try:
cmd_dict = json.loads(data.decode())
action = cmd_dict['action']
if hasattr(self, action):
func = getattr(self, action)
func(cmd_dict, conn)
except (UnicodeDecodeError, json.decoder.JSONDecodeError, TypeError) as e:
# put发送文件数据,无法通过json打包,采取json报错后进入方法循环接收
self.put_file_data(data, conn)
else:
self.sel.unregister(conn)
conn.close()
except ConnectionResetError as e:
print(e)
# 客户端意外断开时注销链接
self.sel.unregister(conn)
conn.close() def get(self, *args):
# 下载第一步:判断文件是否存在并返回文件大小和文件名
conn = args[1]
file_path = args[0]["file_path"]
if os.path.isfile(file_path):
sign = ""
length = os.stat(file_path).st_size
else:
sign = ""
length = None
msg = {
"sign": sign,
"length": length,
"filename": file_path[file_path.rfind("\\")+1:]
}
conn.send(json.dumps(msg).encode()) def get_file_data(self, *args):
# 下载第二步,传送文件数据
conn = args[1]
file_path = args[0]["file_path"]
file = open(file_path, "rb")
for line in file:
conn.send(line)
else:
file.close() def put(self, *args):
# 上传第一步,接收文件大小和文件名
conn = args[1]
self.length = args[0]["length"]
self.filename = PATH + args[0]["filename"]
if os.path.isfile(self.filename):
self.filename = self.filename + str(random.randint(1, 1000))
self.file = open(self.filename, "wb")
self.recv_size = 0
conn.send(b"") # 避免客户端黏包 def put_file_data(self, *args):
# json报错后进入此循环接收数据
conn = args[1]
self.recv_size += len(args[0])
self.file.write(args[0])
if int(self.length) == self.recv_size:
self.file.close()
conn.send(b"") def run():
data = conf.config.Configuration()
conf_data = data.get_config()
obj = SelectorFtp()
obj.connect(conf_data[0][1], int(conf_data[1][1]))
servers
config:
#!/usr/bin/env python
# -*-coding:utf-8-*-
# _author_=zh
import os
import configparser
PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) class Configuration(object):
def __init__(self):
self.config = configparser.ConfigParser()
self.name = PATH+os.sep+"conf"+os.sep+"system.ini" def init_config(self):
# 初始化配置文件,ip :客户端IP,port:客户端端口
if not os.path.exists(self.name):
self.config["config"] = {"ip": "localhost", "port": 1234}
self.config.write(open(self.name, "w", encoding="utf-8", )) def get_config(self, head="config"):
'''
获取配置文件数据
:param head: 配置文件的section,默认取初始化文件config的数据
:return:返回head中的所有数据(列表)
'''
self.init_config() # 取文件数据之前生成配置文件
self.config.read(self.name, encoding="utf-8")
if self.config.has_section(head):
section = self.config.sections()
return self.config.items(section[0])
config
SELECTORS模块实现并发简单版FTP的更多相关文章
- 基于selectors模块实现并发的FTP
import socketimport os,sysBASE_DIR = os.path.dirname(os.path.abspath(__file__)) class selectFtpClien ...
- SELECT版FTP
功能: 1.使用SELECT或SELECTORS模块实现并发简单版FTP 2.允许多用户并发上传下载文件环境: python 3.5特性: select 实现并发效果运行: get 文件名 #从服务器 ...
- day33 网络编程之线程,并发以及selectors模块io多路复用
io多路复用 selectors模块 概要: 并发编程需要掌握的知识点: 开启进程/线程 生产者消费者模型!!! GIL全局解释器锁(进程与线程的区别和应用场景) 进程池线程池 IO模型(理论) 1 ...
- IO模型《七》selectors模块
一 了解select,poll,epoll IO复用:为了解释这个名词,首先来理解下复用这个概念,复用也就是共用的意思,这样理解还是有些抽象, 为此,咱们来理解下复用在通信领域的使用,在通信领域中为了 ...
- Go语言之进阶篇简单版并发服务器
1.简单版并发服务器 示例1: package main import ( "fmt" "net" "strings" ) //处理用户请求 ...
- 并发编程之IO模型比较和Selectors模块
主要内容: 一.IO模型比较分析 二.selectors模块 1️⃣ IO模型比较分析 1.前情回顾: 上一小节中,我们已经分别介绍过了IO模型的四个模块,那么我想大多数都会和我一样好奇, 阻塞IO和 ...
- python 全栈开发,Day44(IO模型介绍,阻塞IO,非阻塞IO,多路复用IO,异步IO,IO模型比较分析,selectors模块,垃圾回收机制)
昨日内容回顾 协程实际上是一个线程,执行了多个任务,遇到IO就切换 切换,可以使用yield,greenlet 遇到IO gevent: 检测到IO,能够使用greenlet实现自动切换,规避了IO阻 ...
- {python之IO多路复用} IO模型介绍 阻塞IO(blocking IO) 非阻塞IO(non-blocking IO) 多路复用IO(IO multiplexing) 异步IO(Asynchronous I/O) IO模型比较分析 selectors模块
python之IO多路复用 阅读目录 一 IO模型介绍 二 阻塞IO(blocking IO) 三 非阻塞IO(non-blocking IO) 四 多路复用IO(IO multiplexing) 五 ...
- 验证客户端的链接合法性和socketserver模块实现并发
本节内容: 1.验证客户端的链接合法性 2.socketserver模块实现并发 一.验证客户端的链接合法性 首先,我们来探讨一下,什么叫验证合法性, 举个例子:有一天,我开了一个socket服务端, ...
随机推荐
- cocos2d-x推断sprite点击
我们经常须要推断用户的点击操作是否落于某个sprite之上,进而让这个sprite做出响应. 可是假设我们通过继承CCSprite类来实现自己的Sprite类的时候,产生的视图尺寸会充满屏幕.多个Sp ...
- Gym 100169A 最短路
题意:不久后滑铁卢将会变得非常冷,但是幸运的是,很多建筑都被桥梁和隧道连接着,所以你不需要总是走在外面.但是现在建筑 物之间的连接是错综复杂的,很难知道某两个建筑物之间的最优路线,所以需要你写程序判断 ...
- 数长方形有多少个?POJ(1693)
题目链接:http://poj.org/problem?id=1693 解题报告: 随机选两根横的,再找一下与这两根横线相交的竖线有多少根,m,那么就有(m-1)*m/2个长方形. #include ...
- Linux 初学者:移动文件
你学习了有关目录和访问目录的权限是如何工作的.你在这些文章中学习的大多数内容都可应用于文件 -- Paul Brown 在之前的该系列的部分中, 你学习了有关目录 和 访问目录 的权限 是如何工作的. ...
- 复杂链表的复制 -python编写
题目描述 输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head.(注意,输出结果中请不要返回参数中的节点引用,否 ...
- Python中的__name__和__main__含义详解
1背景 在写Python代码和看Python代码时,我们常常可以看到这样的代码: ? 1 2 3 4 5 def main(): ...... if __name == "__m ...
- mysql常用命令添加外键主键约束存储过程索引
数据库连接 mysql -u root -p123456 查看表 show databases 创建数据库设置编码 create table books character set utf8; 创建用 ...
- java循环删除List元素的方法总结
1.for循环 2.迭代器 3.过渡法 import java.util.*; /** * Created by HP on 2018/8/2. */ public class Test { publ ...
- 用python画小猪佩奇(非原创)
略作改动: # coding:utf-8 import turtle as t t.screensize(400, 300, "blue") t.pensize(4) # 设置画笔 ...
- 在React Native中集成热更新
最近,在项目DYTT集成了热更新,简单来说,就是不用重新下载安装包即可达到更新应用的目的,也不算教程吧,这里记录一下. 1.热更新方案 目前网上大概有两个比较广泛的方式,分别是 react-nativ ...