老男孩Day11作业:selectors版socket

一、作业需求:

使用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() == '100':
                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() != '204':
            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'100')
        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'204')
            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

 

 

posted @ 2018-04-30 12:32  空s蝉灬  阅读(1054)  评论(0编辑  收藏  举报