本帖最后由 hackerbob 于 2022-4-16 21:29 编辑
从网上找的
服务端:
[Python] 纯文本查看 复制代码 #-- coding:UTF-8 --
from xmlrpc.server import SimpleXMLRPCServer # 用于创建服务器
from xmlrpc.client import ServerProxy ,Fault # 用于向其它节点发出请求
from urllib.parse import urlparse # 用于URL解析
from os.path import join, isfile ,exists , abspath # 用于路径处理和文件查询
from os import mkdir
import sys
MAX_HISTORY_LENGTH = 6 # 访问链最大长度
SimpleXMLRPCServer.allow_reuse_address = 1 # 保证节点服务器重启时能够立即访问
UNHANDLED = 100 # 文件不存在的异常代码
ACCESS_DENIED = 200 # 文件访问受限的异常代码
class UnhandledQuery(Fault): # 创建自定义异常类
def __init__(self, message='无法处理请求!'): # 定义构造方法
Fault.__init__(self, UNHANDLED, message) # 重载超类构造方法
class AccessDenied(Fault): # 创建自定义异常类
def __init__(self, message='访问资源受限!'): # 定义构造方法
Fault.__init__(self, ACCESS_DENIED, message) # 重载超类构造方法
def inside(dir_path, file_path): # 定义文件路径检查的方法
directory = abspath(dir_path) # 获取共享目录的绝对路径
file = abspath(file_path) # 获取请求资源的绝对路径
return file.startswith(join(directory, '')) # 返回请求资源的路径是否以共享目录路径开始
def get_port(url): # 定义获取端口号的函数
result = urlparse(url)[1] # 解析并获取URL中的[域名:端口号]
port = result.split(':')[-1] # 获取以":"进行分割后的最后一组
return int(port) # 转换为整数后返回
class Node:
def __init__(self, url, dir_name, secret):
self.url = url
self.dirname = dir_name
self.secret = secret
self.known = set()
def _start(self): # 定义启动服务器的方法
server = SimpleXMLRPCServer(('', get_port(self.url)), logRequests=False)
server.register_instance(self) # 注册类的实例到服务器对象
server.serve_forever()
def _handle(self, filename): # 定义处理请求的内部方法
file_path = join(self.dirname, filename) # 获取请求路径
if not isfile(file_path): # 如果路径不是一个文件
# return FAIL, EMPTY # 返回无效状态和空数据
raise UnhandledQuery # 抛出文件不存在的异常
if not inside(self.dirname, file_path): # 如果请求的资源不是共享目录中的资源
raise AccessDenied # 抛出访问资源受限异常
return open(file_path).read() # 未发生异常时返回读取的文件数据
# return OK, open(file_path).read() # 返回正常状态和读取的文件数据
def _broadcast(self, filename, history): # 定义广播的内部方法
for other in self.known.copy():
if other in history:
continue
try:
server = ServerProxy(other)
return server.query(filename, history)
except Fault as f: # 如果捕获访问故障异常获取异常代码
if f.faultCode == UNHANDLED: # 如果是文件不存在异常
pass # 不做任何处理
else: # 如果是其它故障异常
self.known.remove(other) # 从已知节点列表中移除节点
except: # 如果捕获其它异常(非故障异常)
self.known.remove(other) # 从已知节点列表中移除节点
raise UnhandledQuery # >>如果已知节点都未能请求到资源<<,抛出文件不存在异常。
def query(self, filename, history=[]): # 定义接受请求的方法
try:
return self._handle(filename)
except UnhandledQuery: # 如果捕获文件不存在的异常
history.append(self.url)
if len(history) >= MAX_HISTORY_LENGTH:
raise
return self._broadcast(filename, history)
def hello(self, other): # 定义向添加其它节点到已知节点的方法
self.known.add(other) # 添加其它节点到已知节点
return OK # 返回值是必须的
def fetch(self, filename, secret): # 定义下载的方法
if secret != self.secret: # 如果密钥不匹配
raise AccessDenied # 抛出访问资源受限异常
result = self.query(filename)
with open(join(self.dirname, filename), 'w') as file:
file.write(result)
return 0 # 必须返回非None的值
def main():
url, directory, secret = sys.argv[1:]
node = Node(url, directory, secret)
node._start()
if __name__ == '__main__':
main()
客户端:
[Python] 纯文本查看 复制代码 from xmlrpc.client import ServerProxy, Fault # 导入服务器代{过}{滤}理类和故障类
from random import choice # 导入随机选取的方法
from string import ascii_lowercase # 导入小写字母列表对象
from time import sleep # 导入延迟方法
from server import Node, UNHANDLED # 导入服务器中的节类和变量
from threading import Thread # 导入线程类
from cmd import Cmd # 导入命令类
import sys # 导入系统模块
import os
HEAD_START = 0.1 # 等待服务器启动时长
SECRET_LENGTH = 10 # 密钥长度
def random_string(length): # 定义随机密钥的函数
secret = ''
while length > 0:
length -= 1
secret += choice(ascii_lowercase) # 随机获取小写字母叠加到变量
return secret
# 因为要使用CMD界面,所以这个类要继承CMD类。
class Client(Cmd):
prompt = '>>>' # 重写超类中的命令提示符
def __init__(self, url_file, dir_name, url): # 定义构造方法
Cmd.__init__(self) # 重载超类的构造方法
self.secret = random_string(SECRET_LENGTH) # 创建密钥变量
node = Node(url, dir_name, self.secret) # 创建节点对象
thread = Thread(target=node._start) # 在独立的线程中启动服务器
thread.setDaemon(True) # 将线程设置为守护线程
thread.start() # 启动线程
sleep(HEAD_START) # 等待服务器启动
self.server = ServerProxy(url) # 创建服务器代{过}{滤}理对象
for line in open(url_file): # 读取URL文件
self.server.hello(line.strip()) # 添加URL文件中的URL到已知节点集合
# 类中对应命令的方法命名,都要以“do_”开头。
def do_fetch(self, filename): # 定义下载命令的方法
try:
self.server.fetch(filename, self.secret) # 调用服务器代{过}{滤}理对象的下载方法
print('调用服务器代{过}{滤}理对象的下载方法')
except Fault as f: # 捕获故障异常
if f.faultCode != UNHANDLED: # 如果异常代码不是未找到文件
pass # 不做处理
print('找不到文件:', filename)
def do_exit(self, arg): # 定义退出命令的方法
print('------------------退出程序------------------')
sys.exit() # 系统退出
def main(): # 定义主程序函数
urlfile, dir_name, url = sys.argv[1:] # 获取通过命令行输入的参数
client = Client(urlfile, dir_name, url) # 创建客户端对象
client.cmdloop() # 启动命令行循环执行
if __name__ == '__main__':
print('Download: >>>fetch [filename]')
print('Exit: >>>exit')
main()
|