这是一段实验/教育代码,专门为了“玩”socketserver 功能而编写。
代码运行没有错误。它所做的只是打开并读取文件,通过 TCP 连接将文件内容发送到 TCP 服务器。服务器在重写 BaseRequestHandler 的 handle() 函数期间打印接收到的数据。
from socket import socket, AF_INET, SOCK_STREAM
from socketserver import BaseRequestHandler, TCPServer
from threading import Thread
HOST = "localhost"
PORT = 10101
ADDR = HOST, PORT
RECVBUF = 4096
SENDBUF = RECVBUF // 2
INPFILE = "inputfile.txt"
class MyHandler(BaseRequestHandler):
def handle(self):
while data := self.request.recv(RECVBUF):
print(data.decode(), end="")
def server(tcpserver: TCPServer):
tcpserver.serve_forever()
if __name__ == "__main__":
with open(INPFILE, "rb") as indata:
with TCPServer(ADDR, MyHandler) as tcpserver:
(t := Thread(target=server, args=[tcpserver])).start()
with socket(AF_INET, SOCK_STREAM) as s:
s.connect(ADDR)
while chunk := indata.read(SENDBUF):
s.sendall(chunk)
tcpserver.shutdown()
t.join()
所以这很好,但是如果我想要 handle() 函数将接收到的数据写入文件怎么办?当然,我可以将文件名硬编码到 handle() 函数中,甚至可以使其全局可用。对我来说,这两个选项似乎都不是特别Pythonic。
TCPServer 是基于 RequestHandler 类型构造的 - 即,不是类实例。我真正想要做的是(以某种方式)将文件名传递到 MyHandler 类中。 TCPServer 实例(大概)将有一个用于构造的 MyHandler 的内部实例变量,但没有记录,所以我不知道它在哪里。
也许我错过了一些基本的东西,但我只是想不出实现这一目标的“正确”方法
解决此 API 限制的一种方法是使用类工厂函数:
def get_handler(fname):
class MyHandler(BaseRequestHandler):
def handle(self):
while data := self.request.recv(RECVBUF):
print(data.decode(), end="")
# copy it to file, I dunno =)
with open(fname, 'wb') as f:
f.write(data)
return MyHandler
然后像这样使用它:
with TCPServer(ADDR, get_handler('/tmp/file.bin')) as tcpserver:
另一个可能的选择是查看内部 socket 模块 API 和子类
TCPServer
来覆盖 finish_request()
方法:
class MyTCPServer(TCPServer):
finish_request(self, request, client_address):
""" That's the only method where handler is used:
could be easily overridden
"""
self.RequestHandlerClass(request, client_address, self)
从这里出发还有很多路要走:
MyTCPServer.filename
属性的内容并将其作为另一个构造函数参数传递给 RequestHandlerClass
__init__()
,本质上将 self.RequestHandlerClass
重命名为更明确,如果它不再被视为类的话总体而言,
BaseRequestHandler
API 非常简单且易于扩展。