一、作业需求:

使用SELECT或SELECTORS模块实现并发简单版FTP

允许多用户并发上传下载文件

二、readme

一、作业需求:

使用SELECT或SELECTORS模块实现并发简单版FTP

允许多用户并发上传下载文件

二、博客地址:http://www.cnblogs.com/catepython/p/8973372.html

三、运行环境

操作系统:Win10

Python:3.6.4rcl

Pycharm:2017.3.4

四、功能实现

1)实现所有基本需求

2)充分利用了面向对象式编程

3)实现了单线程多并发上传/下载文件(多路复用IO)模式

五、测试

1)文件名为空判断

2)指令格式化判断

3)文件名/用户目录有效判断

六、备注

1、服务端put()函数中一遇到 data = conn.recv(size) 逻辑
就会出现“BlockingIOError:无法立即完成一个非阻止性套接字操作”报错 注:尝试过异常处理但效果不明显
完美解决办法:客户端直接把所需上传文件路径与操作字典{'action':'put','file':'e:\xx\xx\'}
一并发送至服务端。然后服务端直接读取路径并写入server目录中,这样就避免了“BlockingIOError”异常报错

readme

三、流程图

四、目录结构图

五、核心代码

bin目录程序运行文件

# -*- coding:utf-8 -*-
# Author:D.Gray
from core import socket_client
start = socket_client.MyClient()

client_start.py

# -*- coding:utf-8 -*-
# Author:D.Gray
from core import socket_server
start = socket_server.MyServer()
start.start()

server_start.py

conf配置文件目录

# -*- coding:utf-8 -*-
# Author:D.Gray
import os
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) LOCAL_HOST = ('localhost',6969) HOME_PATH = os.path.join(BASE_DIR,'File','home_file') SERVER_PATH = os.path.join(BASE_DIR,'File','server_file')

setting.py

core主逻辑程序目录

# -*- coding:utf-8 -*-
# Author:D.Gray
import selectors
import socket,json,os,hashlib,sys,time
from conf import setting class MyClient(object):
def __init__(self):
self.client = socket.socket()
self.client.connect(setting.LOCAL_HOST)
self.run = self.run() def run(self):
'''
启动函数
:return:
'''
while True:
help = '''\033[33;1m
"get":"用于下载文件,例如:get readme.txt 即 get 文件名"
"put":"用于上传文件,例如:put readme.txt 即 put 文件名"
\033[0m'''
print(help)
meg = input('root@selectors_client>>>:').strip().split()
if len(meg) == 0:continue
if len(meg) >= 2:
dic = {
'action':meg[0],
'filename':meg[1],
'filesize':0
}
if hasattr(self,str(dic['action'])):
action = getattr(self,str(dic['action']))
action(dic)
else:
print('\033[31;1m请输入有效操作指令\033[0m')
else:
print('\033[31;1m请输入有效操作指令\033[0m') def put(self,*args):
'''
上传文件至服务端函数
:param args:
:return:
'''
args = args[0] # args = {'action':put,'filename':xxx}
#print('in the put:',args)
file_path = os.path.join(setting.HOME_PATH,args['filename'])
if os.path.isfile(file_path):
file_totle_size = os.stat(file_path).st_size #获取文件大小
args['filesize'] = file_totle_size #在字典中增加 文件大小键值对
self.client.send(json.dumps(args).encode()) #字典序列化上传
print('\033[34;1m发送文件相关信息至服务端:\n%s\033[0m'%args)
recv_server = self.client.recv(1024) #接收服务端回调:允许客户端上传文件参数
if recv_server.decode() == '':
print('\033[35;1m收到服务回调信息:%s\033[0m'%recv_server.decode())
print('开始上传文件...')
i = 0
file_totle_size = int(file_totle_size)
send_size = 0
self.m = hashlib.md5()
with open(file_path,'rb') as f:
while send_size < file_totle_size:
if file_totle_size - send_size < 1024:
size = file_totle_size - send_size
data = f.read(size)
send_size += len(data)
else:
size = 1024
data = f.read(size)
send_size += len(data)
self.client.send(data)
'''
进度条
'''
str1 = '已上传 %s Bytes'%send_size
str2 = '%s%s'%(round((send_size/file_totle_size)*100,2),'%')
str3 = '[%s%s]'%('*'*i,str2)
sys.stdout.write('\033[32;1m\r%s%s\033[0m'%(str1,str3))
sys.stdout.flush()
while i<=50:
i += 2
break
time.sleep(1)
'''
加密认证
'''
self.m.update(data)
self.encryption()
else:
print('\033[31;1m收到服务端异常回调信息\033[0m',recv_server.decode())
else:
print('\033[31;1m未找到该文件\033[0m') def get(self,*args):
dic = args[0]
self.client.send(json.dumps(dic).encode())
server_recv = self.client.recv(1024) #获取服务端回调参数或文件大小
if server_recv.decode() != '':
print('收到服务端发送过来的文件大小[%s bytes]'%server_recv.decode())
file_path = os.path.join(setting.HOME_PATH,dic['filename'])
print('开始接受文件')
file_totle_size = int(server_recv.decode())
self.m = hashlib.md5()
recv_size = 0
i = 0
with open(file_path,'wb') as f:
while recv_size < file_totle_size:
if file_totle_size - recv_size < 1024:
size = file_totle_size - recv_size
else:
size = 1024
data = self.client.recv(size)
self.m.update(data)
f.write(data)
recv_size += len(data)
'''
进度条
'''
str1 = '已下载 %s Bytes' % recv_size
str2 = '%s%s' % (round((recv_size / file_totle_size) * 100, 2), '%')
str3 = '[%s%s]' % ('*' * i, str2)
sys.stdout.write('\033[32;1m\r%s%s\033[0m' % (str1, str3))
sys.stdout.flush()
i += 2
time.sleep(1)
'''
加密认证
'''
self.encryption()
else:
print('服务端未找到该文件') def encryption(self):
'''
文件加密函数
:return:
'''
enc = input('\n文件已上传是否需要加密(n取消加密)>>>:')
if enc == 'n':
self.client.recv(1024) #因服务端无论客户端是否选择加密都会发送加密消息过来,所以这里也必须接受下防止粘包
print('已取消加密文件上传成功')
else:
file_md5 = self.m.hexdigest()
server_md5 = self.client.recv(1024) #接受服务端文件加密信息
print("\033[32;1m本地文件加密:%s\n服务端文件加密:%s\033[0m" % (file_md5 ,server_md5.decode()))
if file_md5 == server_md5.decode():
print("\033[32;1m加密认证成功\033[0m")
else:
print("加密认证失败")

socket_client

# -*- coding:utf-8 -*-
# Author:D.Gray
import selectors
import socket,os,json,errno,hashlib,time
from conf import setting
sel = selectors.DefaultSelector() class MyServer(object):
def __init__(self):
self.server = socket.socket() def start(self):
'''
启动函数
:return:
'''
print('等待链接...')
self.server.bind(setting.LOCAL_HOST)
self.server.listen(100)
self.server.setblocking(False)
self.register() def register(self):
'''
注册函数
:return:
'''
sel.register(self.server,selectors.EVENT_READ,self.accept)
while True:
events = sel.select()
for k,mask in events:
callback = k.data
callback(k.fileobj,mask) def accept(self,server,mask):
'''
服务器监听函数
:param server:
:param mask:
:return:
'''
conn,self.addr = server.accept()
print('\033[32;1m已和客户端[%s]建立了链接\033[0m'%(conn))
conn.setblocking(False)
sel.register(conn,selectors.EVENT_READ,self.read) def read(self,conn,mask):
'''
接收客户端信息函数
:param conn:
:param mask:
:return:
'''
data = conn.recv(1024)
#print('in the read conn:',conn)
if data:
action_dic = json.loads(data) #序列化data={'action':xxx}
print('\033[35;1m收到客户端操作指令:%s\033[0m'%action_dic['action'])
if hasattr(self,str(action_dic['action'])):
action = getattr(self,str(action_dic['action']))
action(action_dic,conn) #此时传参一定要用conn,千万不能传self.conn 多并发时会出现异常
else:
print('\033[31;1m客户端已断开\033[0m')
conn.unregister(conn) #关闭客户端链接
conn.close() def put(self,*args):
'''
服务端接收客户端文件函数
:param args:
:return:
'''
conn = args[1] #客户端链接地址
dic = args[0] #操作字典
file_path = os.path.join(setting.SERVER_PATH,dic['filename'])
print('\033[34;1m已收到客户端传来的文件相关信息:\n%s\033[0m'%dic)
conn.send(b'')
print('\033[35;1m发送回调给客户端:100\033[0m')
print('开始接收客户端文件')
self.m = hashlib.md5()
with open(file_path,'wb') as f:
with open(dic['file'],'rb')as fr:
line = fr.read()
f.write(line)
self.m.update(line)
self.encryption(conn) def get(self,*args):
'''
服务端上传文件至客户端函数
:param args:
:return:
'''
conn = args[1]
dic = args[0]
print('in the get:',dic)
file_path = os.path.join(setting.SERVER_PATH,dic['filename'])
if os.path.isfile(file_path):
file_totle_size = os.stat(file_path).st_size
conn.send(str(file_totle_size).encode())
print('开始发送文件给客户端')
self.m = hashlib.md5()
with open(file_path,'rb') as f:
for line in f:
self.m.update(line)
conn.send(line)
self.encryption(conn)
else:
conn.send(b'')
print('未找到该文件') def encryption(self,*args):
'''
加密函数
:param args:
:return:
'''
conn = args[0]
server_md5 = self.m.hexdigest()
conn.send(str(server_md5).encode())
print('文件操作完成并发送加密信息【%s】至客户端' % server_md5)

socket_server

老男孩Day11作业:selectors版socket的更多相关文章

  1. selectors版socket

    一.作业需求: 使用SELECT或SELECTORS模块实现并发简单版FTP 允许多用户并发上传下载文件 二.readme 一.作业需求: 使用SELECT或SELECTORS模块实现并发简单版FTP ...

  2. Python selectors实现socket并发

    selectors模块 此模块允许基于选择模块原语构建高级别和高效的I / O多路复用. 鼓励用户使用此模块,除非他们想要精确控制使用的os级别的原语. 注:selectors也是包装了select高 ...

  3. 老男孩Day12作业:RabbitMQ-RPC版主机管理程序

    一.作业需求 1.可以对指定机器异步的执行多个命令 例子: 请输入操作指令>>>:run ipconfig --host 127.0.0.0 in the call     tack ...

  4. 老男孩Day16作业:登录、注册、后台管理页面(动态)

    一.作业需求: 1.后台管理主界面(左边菜单框.(全选.反选)框.返回顶部按钮) 2.老男孩登录.注册页面 二.博客地址:https://www.cnblogs.com/catepython/p/93 ...

  5. 老男孩Day14作业:堡垒机

    一.作业需求: 1.业务需求 兼顾业务安全目标与用户体验,堡垒机部署后,不应使用户访问业务系统的访问变的复杂,否则工作将很难推进,因为没人喜欢改变现状,尤其是改变后生活变得更艰难     保证堡垒机稳 ...

  6. 老男孩Day9作业:高级FTP

    一.作业需求 1. 用户加密认证(已完成) 2. 多用户同时登陆(已完成) 3. 每个用户有自己的家目录且只能访问自己的家目录(已完成) 4. 对用户进行磁盘配额.不同用户配额可不同(已完成) 5.  ...

  7. 老男孩Day8作业:FTP

    1.作业需求 开发简单的FTP: 1. 用户登陆 2. 上传/下载文件 3. 不同用户家目录不同 4. 查看当前目录下文件 5. 充分使用面向对象知识 2.流程图 3.目录结构 4.代码区 bin目录 ...

  8. 老男孩Day7作业:选课系统

    1.作业需求:角色:学校.学员.课程.讲师 1. 创建北京.上海 2 所学校 2. 创建linux , python , go 3个课程 , linux\py 在北京开, go 在上海开 3. 课程包 ...

  9. C#版 Socket编程(最简单的Socket通信功能)

    示例程序是同步套接字程序,功能很简单,只是客户端发给服务器一条信息,服务器向客户端返回一条信息:这里只是一个简单的示例,是一个最基本的socket编程流程,在接下来的文章中,会依次记录套接字的同步和异 ...

随机推荐

  1. Windows + Ubuntu 双系统安装

    前言:本篇文章是对之前文章的更新,更新的主内容是把原来用手机拍摄的图片换成了虚拟机的截图,以及对磁盘划分的新的见解和一些使用感受,原本是打算删除之前的那篇Win + Ubuntu双系统的文章的,后来想 ...

  2. openGL 纹理05

    纹理(Texture) 为了能够把纹理映射(Map)到三角形上,我们需要指定三角形的每个顶点各自对应纹理的哪个部分. 这样每个顶点就会关联着一个纹理坐标(Texture Coordinate) 用来标 ...

  3. 时区时差换算(GMT,UTC,PST,PDT)

    2014年美国冬令时标准时间Stardand Time于11月2号开始实施,直到2015年3月8号为止. 冬令时,是指在冬天使用的标准时间.在使用日光节约时制(夏令时)的地区,夏天时钟拨快一小时,冬天 ...

  4. 【NOIP2008】传纸条

    [描述] Description 小渊和小轩是好朋友也是同班同学,他们在一起总有谈不完的话题.一次素质拓展活动中,班上同学安排做成一个m行n列的矩阵,而小渊和小轩被安排在矩阵对角线的两端,因此,他们就 ...

  5. 一个完整的用java客户端使用httpClient请求网页并返回的方法

    import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.InputStream;import ja ...

  6. IWebBrowser和IE浏览器的行为不一样

    原本一直以为IWebBrowser2的行为和IE浏览器的行为应该是一样的,但是最近发现事实不是如此. IE8以后的浏览器都带有兼容模式,而IWebBrowser2默认情况下是在兼容模式下运行的,可以参 ...

  7. SQL Server 索引维护:系统常见的索引问题

    在很多系统中,比如本人目前管理的数据库,索引经常被滥用,甚至使用DTA(数据库引擎优化顾问)来成批创建索引(DTA目前个人认为它的真正用处应该是在发现缺失的统计信息,在以前的项目中,用过一次DTA,里 ...

  8. struts2 与 spring 整合

    1. 首先把所有jar包导入工程 2.在struts2的核心配置文件(在src文件目录下)中添加如下配置: <!-- 将Struts的对象交给Spring管理 所以需要导入Spring和Stru ...

  9. 一个可用模板的Xml描述

    <?xml version="1.0" encoding="utf-8"?> <VMTEMPLATE> <ID>48< ...

  10. 12.Alias(别名)

    通过使用 SQL,可以为列名称和表名称指定别名(Alias). SQL Alias 表的 SQL Alias 语法 SELECT column_name(s) FROM table_name AS a ...