我有一个简单的Python套接字服务器,我希望我的客户端能够在多个连接期间打开/关闭线程进程(即我不想在请求之间保持客户端-服务器连接打开)。服务器的简化版本如下所示:
_bottle_thread = None # I have tried this and a class variable
class CalibrationServer(socketserver.BaseRequestHandler):
allow_reuse_address = True
request_params = None
response = None
bottle_thread = None
def handle(self):
self.data = self.request.recv(1024).strip()
self.request_params = str(self.data.decode('utf-8')).split(" ")
method = self.request_params[0]
if method == "start": self.handle_start()
elif method == "stop": self.handle_stop()
else: self.response = "ERROR: Unknown request"
self.request.sendall(self.response.encode('utf-8'))
def handle_start(self):
try:
bottle = self.request_params[1]
_bottle_thread = threading.Thread(target=start, args=(bottle,))
_bottle_thread.start()
self.response = "Ran successfully"
print(_bottle_thread.is_alive(), _bottle_thread)
except Exception as e:
self.response = f"ERROR: Failed to unwrap: {e}"
def handle_stop(self):
print(_bottle_thread)
if _bottle_thread and _bottle_thread.is_alive():
_bottle_thread.join() # Wait for the thread to finish
self.response = "Thread stopped successfully"
else:
self.response = "No active thread to stop"
if __name__ == "__main__":
HOST, PORT = LOCAL_IP, CAL_PORT
with socketserver.TCPServer((HOST, PORT), CalibrationServer) as server:
server.serve_forever()
该过程开始正常,但是当我与服务器的连接关闭时,我无法再访问
_bottle_thread
。它只是重新初始化为 None
。我只有一台主计算机与服务器通信,并且只需要运行一个 start
实例。我尝试过使用类变量来保存它并使用全局变量。我还尝试过使用 Threading 和 Forking TCP 服务器。我如何访问该线程以将其关闭?我需要更改它以使连接始终打开吗?还有其他方法可以解决这个问题吗?我想使用服务器,这样我就可以更轻松地控制这个过程,因为它将同时在 8 台不同的计算机上运行,但我完全接受其他想法(我尝试过 Ansible,但它对我不起作用)。谢谢!
编辑:
这是我的客户代码:
HOST, PORT = LOCAL_IP, CAL_PORT
data = " ".join(sys.argv[1:])
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
sock.connect((HOST, PORT))
sock.sendall(bytes(data + "\n", "utf-8"))
received = str(sock.recv(1024), "utf-8")
print("Sent: {}".format(data))
print("Received: {}".format(received))
它通过一些简单的参数打开、发送和关闭与服务器的连接。
这是我的客户端控制台输出:
(venv) path$ python cal_client.py start argument
Sent: start argument
Received: Ran successfully
(venv) path$ python cal_client.py stop
Sent: stop
Received: No active thread to stop
和我的服务器:
Received from 127.0.0.1:
['start', 'argument']
Initializing
True <Thread(Thread-1, started 8236592650235098)>
Received from 127.0.0.1:
['stop']
None # the thread is showing None
啊。我一开始就错过了发生了什么......
问题是,当您分配给
_bottle_thread
时,您并没有分配给它的全局版本(甚至分配给它的类版本)。
运行这个简短的示例将会很有启发:
_bottle_thread = "Global"
class C:
_bottle_thread = "Class"
def meth(self):
_bottle_thread = "Method"
print(f"Method {_bottle_thread}")
c = C()
c.meth()
print(f"Global: {_bottle_thread}")
print(f"Class: {c._bottle_thread}")
输出为:
Method Method
Global: Global
Class: Class
发生的事情是,由于 python 作用域规则,您最终会得到
_bottle_thread
的三个不同版本:通过在所有其他作用域之外分配给它而获得的全局版本,通过在类中分配给它而获得的类版本定义,以及通过在方法内分配给它而获得的本地定义。 (请注意,不可能从方法外部打印在 _bottle_thread
内部分配的本地 meth
,也就是说,因为一旦方法结束,它的引用计数就会变为 0,然后它将被销毁。)
您需要在您希望全局可见的每个范围内使用
global
关键字:
def meth(self):
global _bottle_thread
_bottle_thread = "Method"
更好的解决方案是将
_bottle_thread
变量保留为实例变量:
class CalibrationServer(socketserver.BaseRequestHandler):
...
def __init__(self):
# (Not strictly necessary but considered best practice to
# initialize instance variables in the constructor)
self._bottle_thread = None
def handle_start(self):
...
self._bottle_thread = threading.Thread(target=start, args=(bottle,))
...
def handle_stop(self):
if self._bottle_thread and self._bottle_thread.is_alive():
...