XML-RPC là giao thức gọi hàm từ xa dựa trên XML và HTTP, cho phép các ứng dụng giao tiếp qua mạng một cách đơn giản. Trong bài này, ta sẽ xây dựng một hệ thống P2P chia sẻ tệp sử dụng XML-RPC — tương tự mô hình của các công cụ như BitTorrent hay eDonkey.
1. Thử nghiệm cơ bản
Tạo file server_basic.py:
from xmlrpc.server import SimpleXMLRPCServer
def square_value(val):
return val ** 2
server = SimpleXMLRPCServer(('localhost', 4242), allow_none=True)
server.register_function(square_value, 'square')
server.serve_forever()
Sau đó, mở terminal khác và chạy:
from xmlrpc.client import ServerProxy
proxy = ServerProxy('http://localhost:4242')
print(proxy.square(6)) # Kết quả: 36
2. Xây dựng server chia sẻ tệp
Tạo file file_server.py:
# -*- coding: utf-8 -*-
import sys
import os
from xmlrpc.server import SimpleXMLRPCServer
from xmlrpc.client import ServerProxy, Fault
from urllib.parse import urlparse
SimpleXMLRPCServer.allow_reuse_address = True
MAX_HOPS = 5
ERR_NOT_FOUND = 100
ERR_FORBIDDEN = 200
class FileNotFound(Fault):
def __init__(self, msg="File not found or unhandled"):
super().__init__(ERR_NOT_FOUND, msg)
class ForbiddenAccess(Fault):
def __init__(self, msg="Access denied to this resource"):
super().__init__(ERR_FORBIDDEN, msg)
def is_within_directory(base_dir, target_path):
base = os.path.abspath(base_dir)
target = os.path.abspath(target_path)
return os.path.commonpath([base, target]) == base
def extract_port(url):
parsed = urlparse(url)
if ':' in parsed.netloc:
return int(parsed.netloc.split(':')[-1])
return 80
class PeerNode:
def __init__(self, node_url, shared_dir, auth_key):
self.url = node_url
self.dir = shared_dir
self.key = auth_key
self.peers = set()
def search(self, filename, visited=None):
if visited is None:
visited = []
try:
return self._local_lookup(filename)
except FileNotFound:
visited.append(self.url)
if len(visited) > MAX_HOPS:
raise
return self._propagate_search(filename, visited)
def connect(self, peer_url):
self.peers.add(peer_url)
return True
def retrieve(self, filename, key):
if key != self.key:
raise ForbiddenAccess()
content = self.search(filename)
filepath = os.path.join(self.dir, filename)
with open(filepath, 'wb') as f:
f.write(content.data)
return True
def _run_server(self):
port = extract_port(self.url)
rpc_server = SimpleXMLRPCServer(('localhost', port), logRequests=False)
rpc_server.register_instance(self)
rpc_server.serve_forever()
def _local_lookup(self, filename):
full_path = os.path.join(self.dir, filename)
if not os.path.isfile(full_path):
raise FileNotFound()
if not is_within_directory(self.dir, full_path):
raise ForbiddenAccess()
with open(full_path, 'rb') as f:
import xmlrpc.client
return xmlrpc.client.Binary(f.read())
def _propagate_search(self, filename, trail):
for peer in list(self.peers):
if peer in trail:
continue
try:
remote = ServerProxy(peer)
result = remote.search(filename, trail)
return result
except Fault as e:
if e.faultCode != ERR_NOT_FOUND:
self.peers.discard(peer)
except Exception:
self.peers.discard(peer)
raise FileNotFound()
def launch_node():
if len(sys.argv) != 4:
print("Usage: python file_server.py <url> <directory> <secret>")
sys.exit(1)
url, folder, secret = sys.argv[1:]
node = PeerNode(url, folder, secret)
node._run_server()
if __name__ == '__main__':
launch_node()
3. Tạo client điều khiển bằng dòng lệnh
Tạo file file_client.py:
# -*- coding: utf-8 -*-
import sys
import threading
import time
import random
import string
from cmd import Cmd
from xmlrpc.client import ServerProxy, Fault
from file_server import PeerNode, ERR_NOT_FOUND
INIT_DELAY = 0.1
KEY_LEN = 32
def generate_token(length):
chars = string.ascii_lowercase
return ''.join(random.choice(chars) for _ in range(length))
class FileClient(Cmd):
prompt = 'p2p> '
def __init__(self, config_file, local_dir, self_url):
super().__init__()
self.auth_key = generate_token(KEY_LEN)
node = PeerNode(self_url, local_dir, self.auth_key)
thread = threading.Thread(target=node._run_server)
thread.daemon = True
thread.start()
time.sleep(INIT_DELAY)
self.proxy = ServerProxy(self_url)
with open(config_file, 'r') as f:
for line in f:
peer_url = line.strip()
if peer_url:
self.proxy.connect(peer_url)
def do_get(self, filename):
"""Tải tệp từ mạng P2P: get <filename>"""
try:
self.proxy.retrieve(filename, self.auth_key)
print(f"Đã tải '{filename}' thành công.")
except Fault as e:
if e.faultCode == ERR_NOT_FOUND:
print(f"Không tìm thấy tệp: {filename}")
else:
raise
def do_quit(self, arg):
"""Thoát chương trình"""
print("Đang thoát...")
return True
do_exit = do_quit
def main():
if len(sys.argv) != 4:
print("Usage: python file_client.py <peer_list.txt> <local_dir> <self_url>")
sys.exit(1)
peer_list, local_folder, my_url = sys.argv[1:]
client = FileClient(peer_list, local_folder, my_url)
client.cmdloop()
if __name__ == '__main__':
main()
4. Hướng dẫn chạy thử
- Tạo thư mục
nodeAvànodeC. - Trong
nodeC, tạo filesample.txtvới nội dung bất kỳ. - Tạo file
peersA.txttrong thư mục gốc, ghi vào:http://localhost:4243 - Tạo file
peersC.txt(có thể để trống hoặc ghi ngược lại URL của node A). - Mở terminal thứ nhất, chạy:
python file_client.py peersA.txt nodeA http://localhost:4242 - Mở terminal thứ hai, chạy:
python file_client.py peersC.txt nodeC http://localhost:4243 - Ở terminal thứ nhất, gõ:
get sample.txt→ ban đầu sẽ báo lỗi vì chưa biết node C. - Khởi động lại client A (hoặc cập nhật danh sách peer trong code), sau đó thử lại → file sẽ được tải về thư mục
nodeA.