上周的作业写了单线程的服务端,这周晋级一下,写个多线程的客户端。
主要使用到的就是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(‘\033[32;0mfile [%s] has uploaded completely\033[1m‘%filename)
self.request.send(b‘file has uploaded completely‘)
else:
print(‘\033[31;0mAttention,file [%s] has uploaded partially\033[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(‘\033[32;0mfile [%s] has downloaded completely\033[1m‘%filename)
else:
print(‘\033[31;0mAttention,file [%s] has downloaded partially\033[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()
原文:http://www.cnblogs.com/little-hunter/p/6459492.html