CTRL-C在Python中的表现不同

时间:2010-11-03 19:31:07

标签: java python sockets

我最近开始学习Python(这里是长期的Java程序员),目前正在编写一些简单的服务器程序。问题是,对于看似相似的代码段,Java对应方正确响应SIGINT信号( Ctrl + C ),而Python对不起吨。当使用单独的线程生成服务器时,可以看到这种情况。代码如下:

// Java code

package pkg;

import java.io.*;
import java.net.*;

public class ServerTest {

    public static void main(final String[] args) throws Exception {
        final Thread t = new Server();
        t.start();
    }

}

class Server extends Thread {

    @Override
    public void run() {
        try {
            final ServerSocket sock = new ServerSocket(12345);
            while(true) {
                final Socket clientSock = sock.accept();
                clientSock.close();
            }
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

}

和Python代码:

# Python code
import threading, sys, socket, signal

def startserver():
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.bind(('', 12345))
    s.listen(1)
    while True:
        csock, caddr = s.accept()
        csock.sendall('Get off my lawn kids...\n')
        csock.close()

if __name__ == '__main__':
    try:
        t = threading.Thread(target=startserver)
        t.start()
    except:
        sys.exit(1)

在上面的两段代码片段中,我创建了一个简单的服务器,它监听给定端口上的TCP请求。如果在Java代码的情况下按 Ctrl + C ,则JVM退出,而在Python代码的情况下,我得到的只是^C at贝壳。我可以停止服务器的唯一方法是按 Ctrl + Z 然后手动终止进程。

所以我提出了一个计划;为什么没有一个sighandler监听 Ctrl + Z 并退出应用程序?很酷,所以我提出来:

import threading, sys, socket, signal

def startserver():
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.bind(('', 12345))
    s.listen(1)
    while True:
        csock, caddr = s.accept()
        csock.sendall('Get off my lawn kids...\n')
        csock.close()

def ctrlz_handler(*args, **kwargs):
    sys.exit(1)

if __name__ == '__main__':
    try:
        signal.signal(signal.SIGTSTP, ctrlz_handler)
        t = threading.Thread(target=startserver)
        t.start()
    except:
        sys.exit(1)

但现在,似乎我的情况更糟!现在在shell处按 Ctrl + C 发出'^ C'并在shell处按下 Ctrl + Z '^ Z'。

所以我的问题是,为什么这种奇怪的行为?我可能有多个服务器进程在同一进程中作为单独的线程运行,所以在进程收到SIGINT时杀死服务器的任何干净方法? BTW在Ubuntu上使用Python 2.6.4。

TIA,
佐助

2 个答案:

答案 0 :(得分:5)

几个问题:

  • 不要捕获所有异常并以静默方式退出。这是你可以做的最糟糕的事情。

  • 除非你真的知道自己在做什么,否则永远不要抓住SIGTSTP。这不是一个意图让应用程序拦截的信号,如果你抓住它,你可能会做错事。

  • KeyboardInterrupt只发送到程序的初始线程,而不会发送到其他线程。这样做有几个原因。通常,您希望在单个位置处理^ C,而不是在接收异常的随机任意线程中处理。此外,它有助于保证在正常操作中,大多数线程永远不会收到异步异常;这很有用,因为(在所有语言中,包括Java),它们很难可靠地处理。

  • 你永远不会在这里看到KeyboardInterrupt,因为你的主线程在启动辅助线程后立即退出。没有主线程来接收异常,它只是被丢弃了。

修复是双重的:保持主线程,所以KeyboardInterrupt有一个地方可去,只捕捉KeyboardInterrupt,而不是所有例外。


import threading, sys, socket, signal, time

def startserver():
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.bind(('', 12345))
    s.listen(1)
    while True:
        csock, caddr = s.accept()
        csock.sendall('Get off my lawn kids...\n')
        csock.close()

if __name__ == '__main__':
    try:
        t = threading.Thread(target=startserver)
        t.start()

        # Wait forever, so we can receive KeyboardInterrupt.
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        print "^C received"
        sys.exit(1)

    # We never get here.
    raise RuntimeError, "not reached"

答案 1 :(得分:1)

  1. 您的create不是守护进程的线程,所以当父线程退出时它不会退出;

  2. 主要出口启动子线程后,python进程等待子线程终止;

  3. offtop:不要忘记关闭套接字;

  4. 你应该为子线程实现一些停止机制。一个简单的workround:检查接受客户端连接的while循环中的一些stopped标志(使用模块级,或扩展threading.Thread类来封装它),将该标志设置为True并踢出服务器socket +(通过连接)在Ctrl + C和/或Ctrl + Z上或没有它们;等待while t.isAlive(): time.sleep(1)的主要区块。以下是示例代码:

  5. import threading, sys, socket, signal, time

    class Server(threading.Thread): def init(self): threading.Thread.init(self) self._stop = False self._port =12345

    def stop(self): self.__stop = True self.kick()

    def kick(self): try: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('', self.__port)) s.close() except IOError, e: print "Failed to kick the server:", e

    def run(self): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind(('', self._port)) s.listen(1) try: while True: csock, caddr = s.accept() if self._stop: print "Stopped, breaking" break print "client connected from", caddr try: csock.sendall('Get off my lawn kids...\n') finally: csock.close() finally: s.close()

    if name == 'main': t = Server() # if it is failed to stop the thread properly, it will exit t.setDaemon(True) t.start() try: while t.isAlive(): time.sleep(1) except: # Ctrl+C brings us here print "Maybe, you have interrupted this thing?" t.stop() # t.join() may be called here, or another synchronization should appear to # make sure the server stopped correctly

    我不知道为什么它显示停止,踢,运行方法作为模块级别并放置多个换行符,抱歉