关于python socket accept方法阻塞导致程序屏蔽ctrl+c中断信号(程序无法捕捉KeyboardInterrupt)的一种

1、背景

socket 服务端一直不停的接收客户端连接,当服务端停止时做一些处理操作,比如从数据表中删除某些数据。

如下代码:

import socket


class SocketServer:
    def __init__(self, host, port):
        self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.server_address = (host, port)
        self.server_socket.bind(self.server_address)
        self.server_socket.listen(5)

    def handle_client(self, client_socket):
        pass

    def delete_handler(self):
        print("Deleting data")

    def start_server(self):
        try:
            while True:
                client_socket, client_address = self.server_socket.accept()
                self.handle_client(client_socket)
        except KeyboardInterrupt:
            print('Server shutting down.')
        finally:
            self.server_socket.close()
            self.delete_handler()  # 服务端关闭后的处理操作


if __name__ == '__main__':
    server = SocketServer('0.0.0.0', 9999)
    server.start_server()

情况:

  1. cmd窗口运行时,按ctrl + c捕捉不到KeyboardInterrupt错误。
  2. pycharm运行关闭时也捕捉不到KeyboardInterrupt错误。

2、解决办法1:signal信号机制+threading.Event()(不行)

  1. 设置个全局的事件机制
  2. 设置信号捕捉ctrl+c
  3. 捕捉到后evnet.set()
    • 事件默认false,set()后为True
  4. while True 改成while not self.stop_event.is_set()

代码示例:

import socket
import threading
import signal


class SocketServer:
    def __init__(self, host, port):
        self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.server_address = (host, port)
        self.server_socket.bind(self.server_address)
        self.server_socket.listen(5)
        self.stop_event = threading.Event()

        # 设置信号处理程序,捕获 Ctrl+C
        signal.signal(signal.SIGINT, self.handle_interrupt)

    def handle_client(self, client_socket):
        pass

    def delete_handler(self):
        print("Deleting data")

    def handle_interrupt(self):
        # 捕获 Ctrl+C 信号,设置停止事件
        self.stop_event.set()

    def start_server(self):
        try:
            while not self.stop_event.is_set():
                client_socket, client_address = self.server_socket.accept()
                self.handle_client(client_socket)
        except KeyboardInterrupt:
            print('Server shutting down.')
        finally:
            self.server_socket.close()
            self.delete_handler()  # 服务端关闭后的处理操作


if __name__ == '__main__':
    server = SocketServer('0.0.0.0', 9999)
    server.start_server()

结果:不行,因为accept已经阻塞住了,一直卡在当前循环,无法进行下一次循环判断。除非接受一条客户段连接,到下次循环之间,恰好按了ctrl+c,这样可以判断到。当然这种实际也是行不通的。

3、解决办法2:单独的线程等待连接(成功)

使用一个单独的线程来等待连接,并在主线程中捕获信号以触发关闭操作。

import socket
import threading
import time


class SocketServer:
    def __init__(self, host, port):
        self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.server_address = (host, port)
        self.server_socket.bind(self.server_address)
        self.server_socket.listen(5)
        self.stop_event = threading.Event()

    def handle_client(self, client_socket):
        pass

    def delete_handler(self):
        print("Deleting data")

    def accept_connections(self):
        try:
            while True:
                client_socket, client_address = self.server_socket.accept()
                client_thread = threading.Thread(target=self.handle_client,
                                                 args=(client_socket,))
                client_thread.start()
        except Exception as e:
            print(f'Server error: {e}')

    def start_server(self):
        accept_thread = threading.Thread(target=self.accept_connections, args=())
        accept_thread.start()
        try:
            # 主程序逻辑
            while True:
                time.sleep(1)  # 主线程的其他操作
        except KeyboardInterrupt:
            # 捕获 Ctrl+C
            print('Server shutting down.')
        finally:
            self.server_socket.close()
            self.delete_handler()  # 服务端关闭后的处理操作


if __name__ == '__main__':
    server = SocketServer('0.0.0.0', 9999)
    server.start_server()

实际情况:

  1. cmd运行,按ctrl+c后会捕捉到KeyboardInterrupt异常,因此finally里的服务端关闭后的处理操作也会执行。但是命令行还会有输出,应是多线程的问题,还需要点击X号关闭程序。
  2. pycharm中运行,关闭时会捕捉到KeyboardInterrupt异常,因此finally里的服务端关闭后的处理操作也会执行。

发表评论

评论列表,共 0 条评论

    暂无评论