Socketserver

上周的作业写了单线程的服务端,这周晋级一下,写个多线程的客户端。

主要使用到的就是socketserver,这个模块存在的主要功能就是简化网络服务器的书写。

使用它的主要步骤有:

1.根据BaseRequestHandler创建一个类,并且需要重新定义父类的handler方法

class MyTCPHandler(socketserver.BaseRequetHandler)

def handler(self)主要用于获取用户名字,验证登录,然后根据用户的需求,执行相应的方法

2.实例化TCPServer, 将server ip, port 还有上面上面创建的请求处理类传递给TCPServer

server = socketserver.ThreadingTCPServer(('localhost',9999),MyTCPHandler)

3.调用server.serve_forever()#处理多个请求,或者server.server_request()#处理一个请求

4.关闭server, server.close()

虽然不是很难,但是要写一个传递,一个收取然后在发送,这个还是有些绕的。不多说,还是先上流程图看起来比较清晰。

有了流程图,思路就清楚多了。然后就是小心细心的把server和client写好了。感觉这个程序写起来,也不是很难,传来传去,别弄晕了就好。

下面是server端的代码:

#__author__ = 'little hunter'
#!usr/bin/env/Python
# -*- coding: utf-8 -*-
import json
import os
import socketserver
import sys
import hashlib
sys.path.append('..')
from conf import setting
server_response = setting.Server_Response
client_size = setting.ClientSize
basepath = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) #ftp_server
db_path = os.path.join(basepath,'db')
client_catalogue_path = os.path.join(basepath,'client_catalogue')
#继承父类,然后重写父类中的handle方法。注意handle方法 和客户端所有的交互都是在handle中写的
#每一个客户端的请求过来,都会实例化

class MyTCPHandler(socketserver.BaseRequestHandler):

   #需要询问客户到那个目录下
    def get_dir(self,clientname):
        client_path = os.path.join(client_catalogue_path,clientname)
        dir_list = os.listdir(client_path)
        dir_dict = {0:'home'}
        for item in dir_list:
            if os.path.isdir(os.path.join(client_path,item)):
                dir_dict[dir_list.index(item)+1]=item
        print(dir_dict)
        self.request.send(json.dumps(dir_dict).encode()) #将服务器端有的目录发给客户端,询问是要上传到那个目录下
        dir = self.request.recv(1024).decode()
        if dir == 'home':
            client_dir_path = client_path
        else:
            client_dir_path = os.path.join(client_path,dir)
        return client_dir_path


    """接受客户端上传文件"""
    def upload(self,*args):
        """接受客户端的文件"""
        upload_flag = True
        while upload_flag:
            cmd_dic = args[0]
            clientname = cmd_dic['username']
            filename = cmd_dic['filename']
            filesize = int(cmd_dic['filesize'])
            client_dir_path = self.get_dir(clientname)
            client_file_path = os.path.join(client_dir_path,filename)
            client_db_path = os.path.join(db_path,clientname)
            with open(client_db_path,'r') as f:
                for line in f:
                    content_list = line.split(',')
                    client_usedsize = int(content_list[3])
            client_totalsize = int(self.find_client_totalsize(clientname))
            if client_totalsize - client_usedsize-filesize >= 0 :
                self.request.send(server_response[200].encode()) #send b'ok' to client, and update the space that the client can use in the server
                client_usedsize += filesize
                content_list[3] = str(client_usedsize)
                print('updated client used size',client_usedsize)
                res = ",".join(content_list)
                with open(client_db_path,'w') as f:
                    f.write(res)
            else:
                self.request.send(server_response[100].encode()) #send b'not enough space' to client
                upload_flag = False
                break
            if os.path.isfile(client_file_path):
                f = open(client_file_path + '.new','wb')
            else:
                f = open(client_file_path,'wb')
            received_size = 0
            m = hashlib.md5()
            while received_size < filesize:
                if filesize - received_size >=1024:
                    size = 1024
                else:
                    size = filesize - received_size
                data = self.request.recv(size)
                f.write(data)
                m.update(data)
                received_size += len(data)
                self.request.send(str(received_size).encode('utf-8'))
            else:
                sent_file_md5 = self.request.recv(1024)
                print("The md5 of the uploaded file is:",sent_file_md5)
                print('The md5 of the received file is:',m.hexdigest())
                if sent_file_md5.decode() == m.hexdigest():
                    print('33[32;0mfile [%s] has uploaded completely33[1m'%filename)
                    self.request.send(b'file has uploaded completely')
                else:
                    print('33[31;0mAttention,file [%s] has uploaded partially33[1m'%filename)
                    self.request.send(b'Attention, file has uploaded partially')
                f.close()
            break
        return upload_flag,filename

    """接受客户端要求下载文件"""
    def download(self,*args):
        download_flag = True
        while download_flag:
            cmd_dic = args[0]
            clientname = cmd_dic['username']
            print(clientname)
            client_dir_path = self.get_dir(clientname) #询问客户是在要去哪个目录下,并且打印该目录下的文件
            client_dir_file_list = []
            for item in os.listdir(client_dir_path):
                if os.path.isdir(os.path.join(client_dir_path,item)):
                    pass
                else:
                    client_dir_file_list.append(item)
            print(client_dir_file_list)
            client_dir_file_dict = {}
            for item in client_dir_file_list:
                client_dir_file_dict[client_dir_file_list.index(item)] = item
            print(client_dir_file_dict)
            self.request.send(json.dumps(client_dir_file_dict).encode('utf-8')) #将服务器端的客户的目录下的文件发送给客户
            filename = self.request.recv(1024).decode()
            client_file_path = os.path.join(client_dir_path,filename)
            if os.path.isfile(client_file_path):
                filesize = os.stat(client_file_path).st_size
                print('The file size is',filesize)
                self.request.send(str(filesize).encode()) #发送文件大小给客户端
                client_response= self.request.recv(1024).decode() #收到客户端的响应
            else:
                print('The file requested to download is not exist')
                download_flag = False
                break
            m = hashlib.md5()
            with open (client_file_path,'rb') as f:
                for line in f:
                    self.request.send(line)
            print('File is sent to client')
            self.request.send(m.hexdigest().encode())
            break
        return download_flag,filename


    """查找配置文件中客户的磁盘大小,最大数据级别为GB"""
    def find_client_totalsize(self,clientname):
        temp = client_size[clientname].split() #temp[0] is number, temp[1] is the type
        if temp[1] == 'B':
            cof = 1
        elif temp[1]  == 'KB':
            cof = 1024
        elif temp[1] == 'MB':
            cof = 1024*1024
        elif temp[1] == 'GB':
            cof = 1024*1024*1024
        else:
            cof = 0
        return int(temp[0])*cof


    """根据db文件中的客户存储的md5值验证是否登录成功"""
    def verify_user(self,data,clientname):
        authenticated_flag = False
        client_db_path = os.path.join(db_path,clientname)
        with open(client_db_path,'r') as f:
            for line in f:
                user_info_list = line.strip().split(',')
                if user_info_list[2] == data.decode():
                    authenticated_flag = True
        return authenticated_flag


    def handle(self):
        while True:
            try:
                clientname = self.request.recv(1024).decode() #这里的self.data是用户名字
                print("{} wrote:".format(self.client_address[0]))
                print('Received request from the client:',clientname)
                #接受的加密后的用户名,密码
                self.data = self.request.recv(1024)
                authenticated_flag = self.verify_user(self.data,clientname)
                self.request.send(str(authenticated_flag).encode()) #True登录成功,反之,失败
                finish_flag = False
                while not finish_flag:
                    self.data = self.request.recv(1024) #上传客户端的指定字典
                    cmd_dic = json.loads(self.data.decode())
                    action = cmd_dic['action']
                    if hasattr(self,action):
                        func = getattr(self,action)
                        res,filename = func(cmd_dic)
                    if res == 'True':
                        finish_flag = True
                    print(cmd_dic['action'],filename,res)
            except Exception as e:
                print('err,',e)
                break


# server = socketserver.TCPServer((HOST,PORT),MyTCPHandler) #不能支持并发,只能一个线程占线
def run():
    HOST, PORT = 'localhost',9999
    server = socketserver.ThreadingTCPServer((HOST,PORT),MyTCPHandler) #每来一个请求都会来一个线程
    server.serve_forever()
    server.close()

 之后是Client端的代码:

#__author__ = 'little hunter'
#!usr/bin/env/Python
# -*- coding: utf-8 -*-
import socket
import hashlib
import os
import json
import sys
homepath = os.path.dirname(os.path.abspath(__file__))

class Myclient(object):

    def __init__(self):
        self.client = socket.socket()
    def connect(self,ip,port):
        self.client.connect((ip,port))

    def  interactive(self):
        authenticated_state, username =self.authenticated()
        while authenticated_state == 'True':
            cmd = input(">>:").strip() #用户输入 upload test
            if len(cmd) == 0:continue
            cmd_str = cmd.split()[0] #将指令和文件名分隔开来
            if hasattr(self,"cmd_%s"%cmd_str):
                func = getattr(self,"cmd_%s"%cmd_str)
                func(cmd,username)
            else:
                self.help()
        else:
            print('User is not authenticated')


    def cmd_upload(self,*args):
        upload_flag = True
        while upload_flag:
            cmd_split = args[0].split() #是一个列表,动作 + 文件名
            username = args[1]
            if len(cmd_split) >1:
                userpath = os.path.join(homepath,username)
                filename = cmd_split[1]
                filepath = os.path.join(userpath,filename)
                if os.path.isfile(filepath):
                    filesize = os.stat(filepath).st_size
                    msg_dic = {
                        "username":username,
                        "action":cmd_split[0],
                        "filename":filename,
                        "filesize":filesize
                    }
                    self.client.send(json.dumps(msg_dic).encode('utf-8'))
                    #接受服务端的询问,是上传到哪个目录下
                    dir_dict = json.loads(self.client.recv(1024).decode())
                    print(dir_dict)
                    target_dir_index = input('Which catalogue do you want to upload? Enter the index').strip()
                    target_dir = dir_dict[target_dir_index]
                    self.client.send(target_dir.encode())

                    server_response = self.client.recv(1024).decode() #接受客户的端的响应,看是不是能上传了(空间是不是够),可能接受到的是字典
                    m = hashlib.md5()
                    if server_response == 'OK':
                        f = open(filepath,'rb')
                        for line in f:
                            self.client.send(line)
                            m.update(line)
                            sent_size = int(self.client.recv(1024).decode())
                            process = sent_size/filesize*100
                            print("%d%%"%int(process))
                        else:
                            print('file in sent')
                            self.client.send(m.hexdigest().encode()) #上传是,客户端向服务器端发送md5
                            f.close()
                            print(self.client.recv(1024).decode())
                            break
                    else:
                        print(server_response)
                        upload_flag = False
                        break
                else:
                    print(filename,'is not exit')

    def cmd_download(self,*args):
        #先给客户打印服务器端的目录
        download_flag = True
        while download_flag:
            cmd = args[0]
            username = args[1]
            userpath = os.path.join(homepath,username)
            msg_dic = {
                "username":username,
                "action":cmd,
            }
            self.client.send(json.dumps(msg_dic).encode('utf-8')) #向服务器端发送下载请求
            dir_dict = json.loads(self.client.recv(1024).decode())
            print(dir_dict)
            target_dir_index = input('Please enter the index of the catalogue you would like enter into:').strip()
            target_dir = dir_dict[target_dir_index]
            self.client.send(target_dir.encode()) #将需要进入的目录发送给服务器端
            dir_file_dict = json.loads(self.client.recv(1024).decode())
            print('Files presented in this catalogues are:')
            print(dir_file_dict)
            filename_index = input('Please enter filename to download:').strip()
            filename = dir_file_dict[filename_index]
            self.client.send(filename.encode())
            filesize = int(self.client.recv(1024).decode())
            print('filesize',filesize)
            self.client.send(b'Ready to receive file')
            client_file_path = os.path.join(userpath,filename)
            m = hashlib.md5()
            if os.path.isfile(client_file_path):
                f = open(client_file_path + '.new','wb')
            else:
                f= open(client_file_path,'wb')
            received_size = 0
            while received_size < filesize:
                if filesize - received_size >=1024:
                    size = 1024
                else:
                    size = filesize - received_size
                data = self.client.recv(size)
                f.write(data)
                received_size += len(data)
                process = received_size/filesize*100
                print("%d%%"%int(process))
            else:
                sent_file_md5 = self.client.recv(1024)
                print("The md5 of the download file is:",sent_file_md5)
                print('The md5 of the received file is:',m.hexdigest())
                if sent_file_md5.decode() == m.hexdigest():
                    print('33[32;0mfile [%s] has downloaded completely33[1m'%filename)
                else:
                    print('33[31;0mAttention,file [%s] has downloaded partially33[1m'%filename)
                f.close()
            break

    def help(self):
        msg = """
        dir home:进入主目录
        upload filename:上传文件
        download filename:下载文件
        """
    def authenticated(self): #登录成功之后返回用户名
        inp = input("Pleae enter your username,password:").strip() #用户名,密码:liqing,1234
        username,pwd = inp.split(',')
        self.client.send(username.encode())
        m = hashlib.md5()
        m.update(inp.encode()) #hashlib 支持二进制
        self.client.send(m.hexdigest().encode()) #给服务器段发送用户名和密码
        res = self.client.recv(1024).decode()#接受服务器端发回来的相应,看是否能登陆成功
        print('Authentication is ',res)
        return res,username

ftp = Myclient()
ftp.connect('localhost',9999)
ftp.interactive()
原文地址:https://www.cnblogs.com/little-hunter/p/6459492.html