如何使用Ctrl+C停止Tornado Web服务器?

16

我是tornado web服务器的新手。当我使用python main_tornado.py启动tornado web服务器时,它可以正常工作。请参见下面的代码。

import tornado.ioloop
import tornado.web

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("Hello, world")

application = tornado.web.Application([
    (r"/", MainHandler),
])

if __name__ == "__main__":
    application.listen(8888)
    tornado.ioloop.IOLoop.instance().start()

当我使用CTRL+C停止服务器时,它会给出以下错误。

    ^CTraceback (most recent call last):
  File "main_tornado.py", line 19, in <module>
    tornado.ioloop.IOLoop.instance().start()
  File "/home/nyros/Desktop/NewWeb/venv/lib/python3.2/site-packages/tornado/ioloop.py", line 301, in start
    event_pairs = self._impl.poll(poll_timeout)
KeyboardInterrupt

请帮我解决问题。谢谢。


可能是重复的问题:如何停止Tornado Web服务器? - Blender
为什么这是个问题? - Cole Maclean
4个回答

29

你可以使用tornado.ioloop.IOLoop.instance().stop()来停止Tornado主循环。为了在通过Ctrl+C发送信号后调用此方法,您可以定期检查全局标志以测试主循环是否应该结束,并注册SIGINT信号的处理程序,该处理程序将更改此标志的值:

#!/usr/bin/python
# -*- coding: utf-8 -*-

import signal
import logging

import tornado.ioloop
import tornado.web
import tornado.options


class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("Hello, world")


class MyApplication(tornado.web.Application):
    is_closing = False

    def signal_handler(self, signum, frame):
        logging.info('exiting...')
        self.is_closing = True

    def try_exit(self):
        if self.is_closing:
            # clean up here
            tornado.ioloop.IOLoop.instance().stop()
            logging.info('exit success')


application = MyApplication([
    (r"/", MainHandler),
])

if __name__ == "__main__":
    tornado.options.parse_command_line()
    signal.signal(signal.SIGINT, application.signal_handler)
    application.listen(8888)
    tornado.ioloop.PeriodicCallback(application.try_exit, 100).start()
    tornado.ioloop.IOLoop.instance().start()

输出:

$ python test.py 
[I 181209 22:13:43 web:2162] 200 GET / (127.0.0.1) 0.92ms
^C[I 181209 22:13:45 test:21] exiting...
[I 181209 22:13:45 test:28] exit success

更新

我刚在问题Tornado long polling requests中看到了这个简单的解决方案:

try:
    tornado.ioloop.IOLoop.instance().start()
except KeyboardInterrupt:
    tornado.ioloop.IOLoop.instance().stop()

显然,这是一种不太安全的方式。


更新

已编辑代码以删除对 global 的使用。


谢谢回复。你的代码很完美。我正在使用tornado httpserver。当我按下ctrl+c时,关闭服务器需要很长时间。有什么想法吗? - dhana
在调用 try_exit() 回调函数之前,您可能有一些需要先结束的阻塞任务。您可以尝试在信号处理程序中直接使用 tornado.ioloop.IOLoop.instance().stop(),但对我来说似乎不太安全。如果您想要在处理过程中粗暴地中断无限循环,那么很难避免错误消息。 - Nykakin
原始解决方案(处理signal.SIGINT)在Windows上对我非常有效,但是您答案的“更新”(仅捕获KeyboardInterrupt)不起作用。在Windows上,Ctrl+C不会生成KeyboardInterrupt,并且有些人(比如我,使用紧凑型键盘)的键盘上没有CTRL+Pause Break。 - Myk Willis
我不是Python专家,但在脚本语言中使用全局变量可能不是一个好主意...为什么不直接使用def signal_handler(signum, frame): tornado.ioloop.IOLoop.instance().stop()呢?信号是事件,你不需要一个标志...无论如何,这些解决方案都对我不起作用(在Windows上的Boot2Docker)。 - DestyNova
1
尝试: tornado.ioloop.IOLoop.instance().start() 除非出现 KeyboardInterrupt 异常,否则不会停止。这对我不起作用。 - Josh Usre
在Windows上,如果我们在创建“Application”时传递了debug=True,我发现没有办法杀死Python实例。即使我之前已经在VSCode集成终端上使用Ctrl-C停止了它,保存文件后服务器仍会自动重启。唯一的方法是在任务管理器中结束它。 - shioko

8

您可以通过信号处理程序简单地停止Tornado ioloop。由于add_callback_from_signal()方法的存在,它应该是安全的。事件循环将会平稳退出,并完成任何正在并发运行的任务。

import tornado.ioloop
import tornado.web
import signal

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("Hello, world")

application = tornado.web.Application([
    (r"/", MainHandler),
])

def sig_exit(signum, frame):
    tornado.ioloop.IOLoop.instance().add_callback_from_signal(do_stop)

def do_stop(signum, frame):
    tornado.ioloop.IOLoop.instance().stop()

if __name__ == "__main__":
    application.listen(8888)
    signal.signal(signal.SIGINT, sig_exit)
    tornado.ioloop.IOLoop.instance().start()

3
这个答案真的应该被接受,而不是在全局范围内污染变量。 - cowbert
有没有任何理由不使用lambda而是显式定义do_stop - cowbert
do_stop() 也可以从其他地方调用。但实际上,它可以是 lambda。 - Ales Teska
无论如何,由于存在 http://bugs.python.org/issue18040,在Windows上没有什么东西能够正常工作 :( - cowbert
在Windows上没有像信号这样的东西,您需要以不同的方式模拟它。然后拥有一个独立的do_stop()方法会变得非常方便。 - Ales Teska

3

代码没有问题。按下 CTRL+C 会产生 KeyboardInterrupt(中断信号)。在停止服务器时,您可以使用 CTRL+Pause Break(在 Windows 上)而不是 CTRL+C。 在 Linux 上,CTRL+C 也会生成 KeyboardInterrupt。 如果您使用 CTRL+Z,则程序将停止但端口仍然忙碌。


当你说“CTRL+”而不是“CTRL+C”时,你实际上是指控制键和“加号”键吗?因为这对我没有任何作用。 - puk
1
CTRL+Z 无法停止它,只会将进程移至后台。 - WhyNotHugo
1
我正在使用Boot2Docker Windows,但是CTRL+CCTRL+PAUSE会终止进程,但端口仍在使用中... - DestyNova

0
我认为最干净、最安全、最便携的解决方案是将所有关闭和清理调用放在 finally 块中,而不是依赖于 KeyboardInterrupt 异常:
import tornado.ioloop
import tornado.web

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("Hello, world")

application = tornado.web.Application([
    (r"/", MainHandler),
])

# .instance() is deprecated in Tornado 5
loop = tornado.ioloop.IOLoop.current()

if __name__ == "__main__":
    try:
        print("Starting server")
        application.listen(8888)
        loop.start()
    except KeyboardInterrupt:
        pass
    finally:
        loop.stop()       # might be redundant, the loop has already stopped
        loop.close(True)  # needed to close all open sockets
    print("Server shut down, exiting...")

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