import osimport socketimport sysimport timeimport threadingfrom loguru import loggerfrom concurrent.futures import ThreadPoolExecutorfrom concurrent.futures._base import Futureimport multiprocessingdefault_encoding: str = 'utf-8'pool = ThreadPoolExecutor( max_workers=20, thread_name_prefix='simple-work-thread-pool')def init_serversocket() -> socket.socket: serversocket = socket.socket( family=socket.AF_INET, type=socket.SOCK_STREAM ) # 获取本地主机名 host = socket.gethostname() logger.debug(f'host {host}') port = 6001 # 绑定端口号 serversocket.bind(('0.0.0.0', port)) # 设置最大连接数,超过后排队 serversocket.listen(2048) return serversocketdef send_response(clientsocket: socket.socket, addr: tuple, response_body: bytes) -> int: send_len: int = clientsocket.send(response_body) clientsocket.close() return send_lendef start_request(clientsocket: socket.socket, addr: tuple) -> int: try: pid = os.getpid() # logger.debug(f'pid: {pid}, get message from {addr}') request_body: bytes = clientsocket.recv(2048) request_text: str = request_body.decode(encoding=default_encoding) response_text: str = f'server get message: {request_text}' response_body: bytes = response_text.encode(default_encoding) # time.sleep(1) send_len = send_response( clientsocket=clientsocket, addr=addr, response_body=response_body) # logger.debug(f'发送了响应') return send_len except Exception as error: logger.exception(error)def start_request_callback(future: Future) -> None: send_len: int = future.result() logger.debug( f'{threading.current_thread().name}, send payload len is {send_len}')if __name__ == "__main__": serversocket = init_serversocket() pool = multiprocessing.Pool(processes=16) try: while True: clientsocket, addr = serversocket.accept() clientsocket: socket.socket addr: tuple # future: Future = pool.submit(start_request, clientsocket, addr) # future.add_done_callback(start_request_callback) pool.apply_async(start_request, (clientsocket, addr)) finally: pool.close() pool.join()
写了下面的代码,用于实现一个master-slave 架构的 TCP 服务,然而遇到了一个,就是我 CTRL+C 关了和这个过程组之后,过程没有全副敞开,并且还始终占用这端口 6001
─➤ ps jfax | grep 002 42535 120264 120263 42535 pts/13 120263 S+ 1000 0:00 | | | \_ grep --color=auto --exclude-dir=.bzr --exclude-dir=CVS --exclude-dir=.git --exclude-dir=.hg --exclude-dir=.svn --exclude-dir=.idea --exclude-dir=.tox 002 23700 27002 27002 27002 pts/4 27002 Ss+ 1000 0:00 | \_ /usr/bin/zsh --login 1 116522 102068 41388 pts/12 41388 S 1000 0:00 python 002_socket_tcp_server_process.py 1 117004 116633 41388 pts/12 41388 S 1000 0:00 python 002_socket_tcp_client_process.py 1 117005 116633 41388 pts/12 41388 S 1000 0:00 python 002_socket_tcp_client_process.py 1 118556 117249 42535 pts/13 120263 S 1000 0:00 python 002_socket_tcp_client_process.py
(http_wsgi_asgi_tutorial) ╭─pon@amd5700g ~/Desktop/code/me/http_wsgi_asgi_tutorial ‹master*› ╰─➤ sudo lsof -i :6001 [sudo] pon 的明码: COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAMEpython 116522 pon 3u IPv4 8483065 0t0 TCP *:x11-1 (LISTEN)python 116522 pon 48u IPv4 12515823 0t0 TCP localhost:x11-1->localhost:60056 (CLOSE_WAIT)python 116522 pon 53u IPv4 12514260 0t0 TCP localhost:x11-1->localhost:60072 (CLOSE_WAIT)
这就很让人厌恶了
问了一下 chatGPT 解决方案:
Q:如何在TCP 服务端程序退出的时候,敞开全副TCP连贯,开释端口资源?
A:在 TCP 服务端程序退出时,须要将所有的连贯断开,开释占用的端口资源,免得产生端口占用问题。
具体的实现形式有多种,以下是一种罕用的实现形式:
通过 socket.setsockopt() 函数设置 socket 选项 SO_REUSEADDR 和 SO_REUSEPORT,以容许端口重用。
import socketserver_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)server_socket.bind(('localhost', 8080))
在 TCP 服务端程序退出时,先敞开监听套接字,而后循环遍历所有连贯套接字,敞开连贯并开释相干资源。
server_socket.listen()# TCP 服务端程序退出时执行的清理代码def cleanup(): # 敞开监听套接字 server_socket.close() # 敞开所有连贯 for client_socket in clients: client_socket.close()try: while True: # 承受客户端连贯 client_socket, client_address = server_socket.accept() clients.append(client_socket) # 解决客户端申请 handle_client(client_socket)except KeyboardInterrupt: # 当用户按下 Ctrl-C 时执行清理代码 cleanup()except: # 产生异样时执行清理代码 cleanup() raise
留神:在应用 SO_REUSEADDR 和 SO_REUSEPORT 选项时,应该在调用 bind() 函数之前设置这些选项。如果在 bind() 函数之后设置这些选项,可能会导致 bind() 函数失败。