第11节,tornado与websocket

tornado 提供的WebSocketHandler 可以让你非常轻松的编写websocket应用程序,它提供了几个核心的接口,分别在连接被建立,收到消息,连接断开时被调用,你只需要实现这几个方法就能实现wobsocket服务端。

1. websocket 协议

websocket 是一种通信协议,直到它出现才实现了B/S之间真正的全双工通信。我们最熟悉的http协议,只能由客户端发起通信,客户端想要获得某个数据,必须先发起请求,服务端收到请求后将数据返回给客户端,在绝大多数场景下,http就足够了。

但某些情形下,http协议就无能为力了或者说实现起来成本比较高,比如我想在前端页面里实时的显示股票的价格,普通的解决办法时前端每隔一段很小的时间就向后端发送请求。这样做,每一次请求都要建立一次连接,进行3次握手,耗费网络带宽。

能不能有更好的方案呢,你可能想到了长连接,前后端建立起长连接,通过流的方式源源不断的向前端发送数据,没错,这是一种解决方案,但未来,websocket才是解决问题的最佳方式。

前后端建立起socket连接后,服务端源源不断的向客户端推送数据,下面的代码实现后端向前端推送当前时间。

2. 前端页面

项目整体结构

web_socket_demo/
├── __init__.py
├── app.py
└── templates
    └── index.html

先来看前端页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
</head>
<script type="text/javascript">

var wsUpdater = {
    socket: null,
    start: function(){
        if ("WebSocket" in window) {
            wsUpdater.socket = new WebSocket("ws://localhost:8000/websocket");
        }
        else {
            wsUpdater.socket = new MozWebSocket("ws://localhost:8000/websocket");
        }
        wsUpdater.socket.onmessage = function(event) {
            $("#time").text(event.data);
        };
    }
};
wsUpdater.start();

</script>
<body>
<p>当前时间: </p><p id="time"></p>
</body>
</html>

建立websocket连接后,等待服务端发送数据,onmessage 函数是用来处理服务端返回数据的,收到数据后修改p标签显示值。

3. tornado.websocket.WebSocketHandler

tornado 已经提供了可以直接做websocket server的Handler, 直接继承即可

import os
import time
import tornado.ioloop
import tornado.websocket
from datetime import datetime
from tornado.web import RequestHandler, Application
from tornado.httpserver import HTTPServer
from tornado.options import options, define

define('port', default=8000, help='监听端口')




class WebSocketHandler(tornado.websocket.WebSocketHandler):
    def open(self):
        """
        建立连接
        :return:
        """
        self.send_time()

    def send_time(self):
        self.write_message(datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
        # 1秒以后再次调用send_time, 不阻塞其他前端与websocket 建立连接,不要使用time.sleep
        tornado.ioloop.IOLoop.instance().add_timeout(
            time.time() + 1,
            self.send_time
        )

    def check_origin(self, origin):
        """
        允许同源访问
        :param origin:
        :return:
        """
        return True

    def on_message(self, message):
        """
        接收到前端发送的信息后调用on_message
        :param message:
        :return:
        """
        self.write_message("You say:" + message)

    def on_close(self) -> None:
        """
        连接断开时
        :return:
        """
        print("连接断开")


class IndexHandler(RequestHandler):
    def get(self):
        return self.render("index.html")


if __name__ == '__main__':
    options.parse_command_line()
    handlers_routes = [
        (r'/websocket', WebSocketHandler),
        (r'/', IndexHandler),
    ]
    app = Application(handlers=handlers_routes, template_path=os.path.join(os.path.dirname(__file__), "templates"))
    http_server = HTTPServer(app)
    http_server.listen(options.port)
    tornado.ioloop.IOLoop.current().start()
  1. open 建立连接时调用
  2. on_message 在收到前端数据后被调用
  3. on_close 在连接断开时调用,做收尾工作
  4. check_origin 返回True,表示允许同源访问,如果不设置,同源访问时会返回403

在本例中,open方法里调用send_time方法向前端发送当前时间,这里一定要注意,不能使用time.sleep,因为sleep方法是阻塞的,这会导致整个服务不可用,其他客户端无法建立websocket 连接。

tonado 是一个异步的web框架,但这不代表你随意编写代码它都能为你做到异步,如果你的代码本身不异步的,那么tornado 自然也就无法做到异步非阻塞处理请求。

扫描关注, 与我技术互动

QQ交流群: 211426309

加入知识星球, 每天收获更多精彩内容

分享日常研究的python技术和遇到的问题及解决方案