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 服务端程序退出时,须要将所有的连贯断开,开释占用的端口资源,免得产生端口占用问题。

具体的实现形式有多种,以下是一种罕用的实现形式:

  1. 通过 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))
  2. 在 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() 函数失败。