Flask 服务器发送事件套接字异常

6 投票
4 回答
2920 浏览
提问于 2025-04-17 19:31

我在考虑使用SSE(服务器发送事件)来把新数据推送给客户端,然后用Flot(一个JavaScript图表库)来显示“实时”更新。我的服务器是用Python的Flask框架搭建的,我已经搞定了如何把数据推送到客户端,但问题在于一旦我离开页面,就会出现错误:

Exception happened during processing of request from ('127.0.0.1', 38814)
Traceback (most recent call last):
  File "/usr/lib/python2.7/SocketServer.py", line 582, in process_request_thread
    self.finish_request(request, client_address)
  File "/usr/lib/python2.7/SocketServer.py", line 323, in finish_request
    self.RequestHandlerClass(request, client_address, self)
  File "/usr/lib/python2.7/SocketServer.py", line 640, in __init__
    self.finish()
  File "/usr/lib/python2.7/SocketServer.py", line 693, in finish
    self.wfile.flush()
  File "/usr/lib/python2.7/socket.py", line 303, in flush
    self._sock.sendall(view[write_offset:write_offset+buffer_size])
error: [Errno 32] Broken pipe

我明白为什么会出现这个错误——因为在不断循环中提供“实时”数据时,连接没有被关闭。我的问题是,如何检测到页面的变化并干净利落地关闭这个连接?我能在客户端关闭连接吗?那我该如何检测页面变化呢?

这是服务器代码的基本框架,当然我会把文本消息替换成包含要显示对象列表的JSON格式:

def event_stream():
    import time
    while True:
        time.sleep(1)
        yield "data: This is a message number X.\n\n"

@app.route('/stream')
def stream():
    return Response(event_stream(), mimetype="text/event-stream")

4 个回答

2

我找到了一种比较“脏”的解决办法(包括了一些临时修补),但它确实能工作。

因为在连接断开时,SocketServer.StreamRequestHandler.finish 会出现一个异常,所以我们可以对它进行修补,捕捉这个异常,然后按照我们的需求来处理:

import socket
import SocketServer

def patched_finish(self):
    try:
        if not self.wfile.closed:
            self.wfile.flush()
            self.wfile.close()
    except socket.error:
        # Remove this code, if you don't need access to the Request object
        if _request_ctx_stack.top is not None:
            request = _request_ctx_stack.top.request
            # More cleanup code...
    self.rfile.close()

SocketServer.StreamRequestHandler.finish = patched_finish

如果你需要访问对应的 Request 对象,你还需要用 flask.stream_with_context 来包装事件流,在我的例子中是这样:

@app.route(url)
def method(host):
    return Response(stream_with_context(event_stream()),
                    mimetype='text/event-stream')

再次强调,这个解决办法非常“脏”,如果你不使用内置的 WSGI 服务器,可能就不管用了。

2

我没有更好的答案,但我觉得上面的ajax请求服务器的方式不好。

在flask中,SSE(服务器发送事件)是通过Response对象来进行流式传输的。如果能在Response中检测到断开连接或管道破裂的事件,那就能更好地处理socket事件,并释放其他分配的资源。

2

你可以使用 onBeforeUnload 或者 jQuery 的 window.unload() 来发送一个 Ajax 请求,调用一个关闭连接的方法。大概是这样的:

$(window).unload(
    function() {
        $.ajax(type: 'POST',
               async: false,
               url: 'foo.com/client_teardown')
    }
}

不过,unload()onBeforeUnload() 的处理方式在不同浏览器中可能会有些不一致,所以在像 Chrome 这样的浏览器中,你可能还需要做一些额外的工作。

撰写回答