无法在 KeyboardInterrupt 上关闭套接字

11
from socket import socket, AF_INET, SOCK_STREAM

sock = socket(AF_INET, SOCK_STREAM)
sock.bind(("localhost", 7777))
sock.listen(1)
while True:
    try:
        connection, address = sock.accept()
        print("connected from " + address)
        received_message = sock.recv(300)
        if not received_message:
            break
        connection.sendall(b"hello")

    except KeyBoardInterrupt:
        connection.close()

我正在努力理解套接字并编写了这个相当简单的脚本,但是由于某种原因,我无法使用 KeyboardInterrupt 命令终止此脚本。

请问如何使用 KeyboardInterrupt 命令强制终止此脚本?为什么我无法使用 KeyboardInterrupt 命令终止它呢?

7个回答

5
  1. 使用 break 来跳出 while 循环。没有 break,循环将不会结束。
  2. 为了保险起见,检查 connection 是否已设置。
from socket import socket, AF_INET, SOCK_STREAM

sock = socket(AF_INET, SOCK_STREAM)
sock.bind(("localhost", 7777))
sock.listen(1)
while True:
    connection = None # <---
    try:
        connection, address = sock.accept()
        print("connected from ", address)
        received_message = connection.recv(300)
        if not received_message:
            break
        connection.sendall(b"hello")
    except KeyboardInterrupt:
        if connection:  # <---
            connection.close()
        break  # <---

更新

  • 有一个错字:应该是KeyboardInterrupt而不是KeyBoardInterrupt
  • sock.recv 应该是 connection.recv

10
这很奇怪,因为即使运行这个命令,也无法通过 CTRL-C 关闭脚本。 - Zion
1
由于我在Windows上使用它,这是键盘中断未被触发的原因吗? - Zion
1
@Zion,我在Linux上尝试了一下,它可以工作。http://asciinema.org/a/dsatnomhvpvvl3skdunbpinov - falsetru
1
@Zion,Ctrl + Break怎么样? - falsetru
谢谢,我猜这是一个Windows的问题。我在OS X上尝试了一下,它可以工作。 - Zion
这段代码似乎在任何Nix系统上都能使用CTRL-C(包括Mac或Linux系统,至少在我的Mac上可以正常工作),但是在Windows系统上无法使用。对于Windows系统,我必须使用虚拟键盘中的奇怪CTRL + ScrLk组合键并在单独的powershell中打开它。这个想法来自于这个线程:https://dev59.com/GFgQ5IYBdhLWcg3w6IIH - Jeremy Bailey

4

尝试使用timeout使程序定期地从accept等待过程中“跳出”以接收KeyboardInterrupt命令。

这是一个Socket服务器的示例:

import socket

host = "127.0.0.1"
port = 23333

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

sock.bind((host,port))
sock.listen()

sock.settimeout(0.5)

print("> Listening {}:{} ...".format(host,port))

try:
    while True:
        try:
            conn, addr = sock.accept()
            data = conn.recv(1024)
            if not data:
                print("x Client disconnected!")
                # break
            else:
                print("> Message from client: {}".format(data.decode()))
                msg = "> Message from server".format(data.decode()).encode()
                conn.sendall(msg)
        except socket.timeout:
            # print("Timeout")
            pass
        except KeyboardInterrupt:
            pass
except KeyboardInterrupt:
    print("Server closed with KeyboardInterrupt!")
    sock.close()

3

尝试给套接字添加超时时间,方法如下:

from socket import socket, AF_INET, SOCK_STREAM
sock = socket(AF_INET, SOCK_STREAM)
sock.bind(("localhost", 7777))
sock.settimeout(1.0)
sock.listen(1)
while True:
    try:
        connection, address = sock.accept()
        print("connected from " + address)
        received_message = sock.recv(300)
        if not received_message:
            break
        connection.sendall(b"hello")
    except IOError as msg:
        print(msg)
        continue    
    except KeyboardInterrupt:
        try:
            if connection:
                connection.close()
        except: pass
        break
sock.shutdown
sock.close()

我是否在Windows上执行此操作会有所不同,因为在Mac上执行时会捕获键盘中断? - Zion
我无法告诉你,因为我在Linux上,你可以试一下看看 :) - Rolf of Saxony
你也可以将其用于仅发送设置(即不执行任何 sock.recv(),因为您正在使用 TCP 实现基本上是 UDP 的功能),只需在 sock.accept() 行周围使用匹配到 except socket.timeout: continuetry,然后根据需要使用 except KeyboardInterrupt: 外部块。 - Dan

3

我在Windows上遇到了这个问题。以下是我处理停止进程的方法:

    try:
        while self.running:
            try:
                c, addr = self.socket.accept()
                print("Connection accepted from " + repr(addr[1]))
                # do special stuff here...
                print("sending...")
                continue
            except (SystemExit, KeyboardInterrupt):
                print("Exiting....")
                service.stop_service()
                break
            except Exception as ex:
                print("======> Fatal Error....\n" + str(ex))
                print(traceback.format_exc())
                self.running = False
                service.stop_service()
                raise
    except (SystemExit, KeyboardInterrupt):
        print("Force Exiting....")
        service.stop_service()
        raise

def stop_service(self):
    """
    properly kills the process: https://dev59.com/M2Qn5IYBdhLWcg3wkH3U#16736227
    """
    self.running = False
    socket.socket(socket.AF_INET,
                  socket.SOCK_STREAM).connect((self.hostname, self.port))
    self.socket.close()

请注意,为了触发KeyboardInterrupt异常,请使用:

Ctrl+Fn+PageUp(Pause/Break)


3

CTRL+C事件可以在另一个进程中捕获并发送回运行在主进程中的另一个线程,以终止套接字。下面是一个示例,在Windows 10上使用Python 3.5.4测试成功。添加了一些注释和打印语句,以便您了解正在发生的情况。

from multiprocessing import Pipe, Process
from socket import socket, AF_INET, SOCK_STREAM
from threading import Thread
import time

def detect_interrupt(conn):
    try:
        print("Listening for KeyboardInterrupt...")
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        print("Detected KeyboardInterrupt!")
        print("Sending IPC...")
        conn.send(True)
        conn.close()

def listen_for_interrupt(conn, sock):
    print("Listening for IPC...")
    conn.recv()
    print("Detected IPC!")
    print("Closing sock...")
    sock.close()

if __name__ == "__main__":

    sock = socket(AF_INET, SOCK_STREAM)
    sock.bind(("localhost", 7777))
    sock.listen(1)

    # Crate a Pipe for interprocess communication
    main_conn, detect_conn = Pipe()
    # Create a thread in main process to listen on connection
    listen_for_interrupt_thread = Thread(
        target=listen_for_interrupt, args=(main_conn, sock), daemon=True)
    listen_for_interrupt_thread.start()
    # Create a separate process to detect the KeyboardInterrupt
    detect_interrupt_process = Process(
        target=detect_interrupt, args=(detect_conn,))
    detect_interrupt_process.start()

    connection = None
    try:
        while True:
            print("Running socket accept()")
            connection, address = sock.accept()
            print("connected from " + address)
            received_message = sock.recv(300)
            if not received_message:
                break
            connection.sendall(b"hello")
    except KeyboardInterrupt:
        print("Handling KeyboardInterrupt")
        sock.close()
        if connection:
            connection.close()

1
如果远端很少发送数据,你应该为连接设置超时时间。在这种情况下,当KeyboardInterrupt可以被检查时,连接将引发超时异常。
from socket import socket, AF_INET, SOCK_STREAM
sock = socket(AF_INET, SOCK_STREAM)
sock.bind(("localhost", 7777))
sock.settimeout(1.0)
sock.listen(1)
while True:
try:
    connection, address = sock.accept()
    connection.settimeout(1.0)
    print("connected from " + address)
    received_message = sock.recv(300)
    if not received_message:
        break
    connection.sendall(b"hello")
except socket.timeout:
    continue
except IOError as msg:
    print(msg)
    continue
except KeyboardInterrupt:
    try:
        if connection:
            connection.close()
    except: pass
    break
sock.shutdown
sock.close()

1

对于Windows用户,

试图捕获键盘中断的上述解决方案似乎不起作用。最终,我在套接字上设置了一个超时。

类似这样:server_socket.settimeout(10)

在此,如果没有活动(例如连续10秒未收到任何内容),则会引发异常。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接