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服务端, ...
随机推荐
- nbu8.1配置群集SQL Server实例的备份
1.About SQL Server high availability (HA) environments SQL Server Intelligent policies support the f ...
- 【转】DataURL在Web浏览器中的兼容性总结
IE8+,Firefox,Chrome,Opera,Safari 等现代浏览器普遍支持data URL IE8 data URL 最大长度限制为32k字节,超出无效.IE9+没有这个限制 IE只识别b ...
- 使用筛法在 O(logN) 的时间内查询多组数的素数因子
Prime Factorization using Sieve O(log n) for multiple queries 使用筛法在 O(logN) 的时间内查询多组数的素数因子 前言 通常, 我们 ...
- c/c++基础
如果有你认为重要的知识点,而我这却没有记录下来的,那么期待你分享给我(^U^)ノ~YO. 1.在结构体中,符号->的前面是指针变量,符号.的前面是普通变量. 程序中a->b等价于(*a ...
- XML DTD约束 对xml文件的crud的查询Read Retrieve操作 xml递归遍历
本地的dtd文档 xml中引入dtd文档 <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE 书 ...
- STL 之 set的应用
关于set Set是STL中的一个容器,特点是其中包含的元素值是唯一的,set根据其底层实现机制分为hash存储和红黑树存储两种方式,这两种结构最本质的区别就是有序和无序,红黑树的存储是有序的而has ...
- 【学时总结】◆学时·VI◆ SPLAY伸展树
◆学时·VI◆ SPLAY伸展树 平衡树之多,学之不尽也…… ◇算法概述 二叉排序树的一种,自动平衡,由 Tarjan 提出并实现.得名于特有的 Splay 操作. Splay操作:将节点u通过单旋. ...
- JZOJ 5914. 盟主的忧虑
Description 江湖由 N 个门派(2≤N≤100,000,编号从 1 到 N)组成,这些门派之间有 N-1 条小道将他们连接起来,每条道路都以“尺”为单位去计量,武林盟主发现任何两个 ...
- ADSL_自动拨号源码(Delphi),已经测试通过
下载地址: http://files.cnblogs.com/lwm8246/ADSL_%E8%87%AA%E5%8A%A8%E6%8B%A8%E5%8F%B7.rar
- Skyscrapers Covered in Solar Pancels【太阳能电池板覆盖的摩天大楼】
Skyscrapers Covered in Solar Panels An office tower on Miller Stree in Manchester is completely cove ...