Python - 无法使用KeyboardInterrupt结束主线程

12
我正在制作一个简单的多线程端口扫描器。它会扫描主机上的所有端口并返回打开的端口。问题在于如何中断扫描。扫描需要很长时间才能完成,有时我希望在扫描中途使用C-c杀死程序。但是问题在于扫描不会停止。主线程被锁定在queue.join()上,忽略KeyboardInterrupt,直到队列中所有数据被处理完毕,因此解除了主线程的阻塞并优雅地退出程序。我的所有线程都是守护线程,因此当主线程死亡时,它们也应该随之死亡。
我尝试使用signal lib,但没有成功。覆盖threading.Thread类并添加用于优雅终止的方法也没有起作用...当执行queue.join()时,主线程仍然无法接收KeyboardInterrupt。
import threading, sys, Queue, socket

queue = Queue.Queue()

def scan(host):
    while True:
        port = queue.get()

        if port > 999 and port % 1000 == 0:
            print port
        try:
            #sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
            #sock.settimeout(2) #you need timeout or else it will try to connect forever! 
            #sock.connect((host, port))
            #----OR----
            sock = socket.create_connection((host, port), timeout = 2)

            sock.send('aaa')
            data = sock.recv(100)
            print "Port {} open, message: {}".format(port, data)
            sock.shutdown()
            sock.close()
            queue.task_done()
        except:
            queue.task_done()


def main(host):
    #populate queue
    for i in range(1, 65536):
        queue.put(i)
    #spawn worker threads
    for port in range(100):
        t = threading.Thread(target = scan, args = (host,))
        t.daemon = True
        t.start()

if __name__ == '__main__':
    host = ""

    #does input exist?
    try:
        host = sys.argv[1]
    except:
        print "No argument was recivied!"
        exit(1)

    #is input sane?
    try:
        host = socket.gethostbyname(host)
    except:
        print "Adress does not exist"
        exit(2)

    #execute main program and wait for scan to complete
    main(host)
    print "Post main() call!"
    try:
        queue.join()
    except KeyboardInterrupt:
        print "C-C"
        exit(3)

编辑:

我通过使用time模块找到了解决方案。

#execute main program and wait for scan to complete
main(host)

#a little trick. queue.join() makes main thread immune to keyboardinterrupt. So use queue.empty() with time.sleep()
#queue.empty() is "unreliable" so it may return True a bit earlier then intented.
#when queue is true, queue.join() is executed, to confirm that all data was processed.
#not a true solution, you can't interrupt main thread near the end of scan (when queue.empty() returns True)
try:
    while True:
        if queue.empty() == False:
            time.sleep(1)
        else:
            break
except KeyboardInterrupt:
    print "Alas poor port scanner..."
    exit(1)
queue.join()

2
有没有人知道有没有不用while True的方法做到这一点? 这会浪费大量CPU资源,我正在使用threading中的Thread来避免while True。如果可以让thread.join更完美,只是我能歇菜(而不需要浪费CPU时间的无限循环)。有什么想法吗? 我将要运行的测试并不需要我杀掉线程,因为线程会无限期地继续下去(从理论上讲,它会永远继续下去),但出于测试目的和将来的参考(因为每次都在另一个终端窗口执行pkill python很繁琐),我真的想知道。 谢谢(别说ctrl + z) - dylnmc
1
如果您在Windows上运行此程序,则这很“正常”。 Python解释器会捕获CTRL-C并设置一个内部标志,然后等待控制返回到Python解释器,以便它可以引发KeyboardInterrupt。 它依赖于阻塞系统调用返回EINTR,以便返回并检查该内部标志。 但是,在Windows上,当发生这种情况时,阻止系统操作不会返回EINTR错误代码,因此KeyboardInterrupt异常将延迟直到阻止操作完成。 - André Caron
2个回答

4

您已经将您的线程设置为守护进程,但是在守护线程仍然存在时,您需要保持主线程活动,以下是如何实现: 无法使用Ctrl-C结束Python脚本


4
我的主线程正在运行。queue.join()会阻塞线程,直到队列中的所有数据被处理完毕,然后queue.join()会解除主线程的阻塞状态并使程序正常退出。问题在于,当主线程到达queue.join()时,它会使线程对KeyboardInterrupt免疫。如果我不能杀死主线程,我也无法杀死守护进程线程。我会澄清我的问题... - RedSparrow
3
能否分享解决方法? - Rishi

0

当您创建线程时,请将它们添加到正在运行的线程列表中,处理 ctrl-C 时向列表中的每个线程发送 kill 信号。这样,您就可以积极地进行清理,而不是依赖于自动完成。


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