关于python:如何在TCP-服务端程序退出的时候关闭全部TCP连接释放端口资源

32次阅读

共计 3760 个字符,预计需要花费 10 分钟才能阅读完成。

import os
import socket
import sys
import time
import threading
from loguru import logger
from concurrent.futures import ThreadPoolExecutor
from concurrent.futures._base import Future
import multiprocessing

default_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 serversocket


def send_response(clientsocket: socket.socket, addr: tuple, response_body: bytes) -> int:
    send_len: int = clientsocket.send(response_body)
    clientsocket.close()
    return send_len


def 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 NAME
python  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 socket
    
    server_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() 函数失败。

正文完
 0