java CTRLC在Python中的行为不同
我最近开始学习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
。我可以停止服务器的唯一方法是按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发射“^C”时按Ctrl+C,在shell发射“^Z”时按Ctrl+Z
所以我的问题是,为什么会有这种奇怪的行为?我可能会有多个服务器进程在同一个进程中作为单独的线程运行,所以当进程接收SIGINT
时,有没有干净的方法杀死服务器?顺便说一句,在Ubuntu上使用Python 2.6.4
TIA,
佐助
# 1 楼答案
您创建的线程不是守护线程,因此当父线程退出时它不会退出
main在启动子线程后立即退出,python进程等待子线程终止
offtop:不要忘记关闭插座
应该为子线程实现一些停止机制。一个简单的解决方法是:检查while循环中接受客户机连接的一些
stopped
标志(使用模块级别,或者扩展threading.Thread
类来封装它),将该标志设置为True,并在Ctrl+C和/或Ctrl+Z或不使用它们的情况下(通过连接)启动服务器套接字;在主块中等待while t.isAlive(): time.sleep(1)
。下面是一个示例代码:我不知道为什么它会把stop、kick、run方法显示为模块级,并放置多个换行符,抱歉
# 2 楼答案
有几个问题:
不要捕捉所有异常并悄悄退出。这是你能做的最糟糕的事情
除非你真的知道自己在做什么,否则千万不要抓住SIGTSTP。这不是一个应用程序用来拦截的信号,如果你捕捉到了它,很可能是你做错了什么
键盘中断只发送到程序的初始线程,而不会发送到其他线程。这样做有两个原因。通常,您希望在单个位置处理^C,而不是在碰巧接收到异常的随机、任意线程中。此外,它有助于保证在正常操作中,大多数线程不会收到异步异常;这很有用,因为(在所有语言中,包括Java)很难可靠地处理它们
你永远不会在这里看到键盘中断,因为你的主线程在启动次线程后立即退出。没有接收异常的主线程,它只是被丢弃
解决方法有两个:保持主线程,这样键盘中断就有了出路,只捕获键盘中断,而不是所有异常