为了在一个 Python TCP 服务器中管理客户端连接,需要确定何时断开客户端连接。为了实现这个目标,可以为每个客户端创建一个任务,每隔一段时间(如 1-2 秒)向服务器发送一个数据包,以报告其活动状态。服务器上创建一个任务定期更新所有连接的超时时间,如果某一连接的超时时间超过 6 秒,则认为该连接已断开,并将其从服务器中移除。需要确定这种方式是否合适,是否存在更好的方法,以及 6 秒是否是一个合理的超时时间。
2、解决方案
方案一:使用队列和线程进行管理
-
每个客户端连接由以下几个部分组成:
- 套接字句柄(由 accept 调用获取)
- 队列(称为“逻辑队列”)
- 线程(称为“套接字线程”)
- 线程(称为“逻辑线程”)
-
这些组件之间的关系如下:
- 套接字线程只从套接字句柄中读取数据,并将消息放入队列中。
- 逻辑线程只向套接字句柄中写入数据,只从队列中读取消息。
- 套接字线程将在队列中放置以下类型的消息:
- 从客户端收到的输入
- 客户端超时或客户端关闭连接
-
逻辑线程从队列中读取消息并进行处理,它可以接收以下类型的消息:
- 客户端关闭连接/客户端超时 - 从客户端接收到的输入 - 另一个客户端连接/断开
4. 另外,需要创建其他一些线程和队列:
- 一个用于执行 accept() 并创建新连接的线程(称为“接收线程”)
- 一个用于向其他逻辑线程广播消息的线程(称为“广播线程”)
- 一个供广播线程读取、其他线程写入的队列(称为“广播队列”)
- 所有线程都需要访问广播队列。
- 只有广播线程从广播队列中读取数据,accept 线程和所有逻辑线程都可以向广播队列中写入数据。
- 广播线程需要知道所有逻辑线程的队列。
5. 以下是客户端超时时发生的情况:
- 客户端超时
- 该客户端的套接字线程将“客户端超时”消息放入其队列中
- 逻辑线程获取消息,关闭套接字,终止套接字线程,并将消息放入主服务器线程的输入队列中
- 主服务器线程获取消息并将其广播到所有其他客户端的队列中
- 每个客户端的逻辑线程获取客户端断开连接的消息,并通过其套接字发出通知
6. 以下是创建新客户端连接时发生的情况:
- accept 线程检测到一个新连接
- accept 线程创建一个新的套接字线程、逻辑线程和队列
- accept 线程将“新连接”消息排入广播队列
- 广播线程看到新连接消息,并将其广播到其他逻辑队列
- 每个逻辑线程获取消息并通过套接字发送一些通知
方案二:使用 select 函数
-
可以使用
select函数来监控客户端连接的活动状态。select函数可以同时监视多个文件描述符,并返回已准备好进行读/写操作的文件描述符列表。 -
对于每个客户端连接,可以创建一个套接字并将其添加到
select函数的监视列表中。 -
服务器可以定期调用
select函数来检查是否有任何客户端连接已经超时。如果某个客户端连接已经超时,则可以将其从服务器中移除。
以下是 select 函数的使用示例:
import select
# 创建一个套接字并将其添加到 `select` 函数的监视列表中
# 假设套接字对象为 `client_socket`
select_list = [client_socket]
while True:
# 调用 `select` 函数来检查是否有任何客户端连接已经准备好进行读/写操作
ready_list, _, _ = select.select(select_list, [], [], 1)
# 如果 `client_socket` 在 `ready_list` 中,则表示该客户端连接已经准备好进行读/写操作
if client_socket in ready_list:
# 从客户端连接中读取数据
data = client_socket.recv(1024)
# 处理从客户端连接中读取到的数据
# 如果 `client_socket` 不在 `ready_list` 中,则表示该客户端连接已经超时
else:
# 从服务器中移除该客户端连接
select_list.remove(client_socket)
client_socket.close()
方案三:使用 Python socketserver 模块
-
Python
socketserver模块可以帮助你更轻松地创建 TCP 服务器。 -
socketserver模块提供了几个预定义的类,可以用来创建不同的类型的服务器。如果要创建一个简单的 TCP 服务器,则可以使用TCPServer类。 -
TCPServer类提供了一个handle_timeout()方法,可以用来处理客户端连接的超时。如果某个客户端连接已经超时,则可以调用handle_timeout()方法来将其从服务器中移除。
以下是用 socketserver 模块创建 TCP 服务器的示例:
import socketserver
class MyTCPHandler(socketserver.BaseRequestHandler):
def handle(self):
# 处理客户端连接
pass
def handle_timeout(self):
# 从服务器中移除该客户端连接
self.request.close()
class MyTCPServer(socketserver.TCPServer):
def handle_timeout(self):
# 从服务器中移除该客户端连接
self.close_request(self.request)
if __name__ == "__main__":
HOST, PORT = "localhost", 9999
server = MyTCPServer((HOST, PORT), MyTCPHandler)
server.serve_forever()