HTML5 WebSocket

一、HTTP协议

  传统的HTTP协议是无状态的,每次请求(request)都要由客户端(如 浏览器)主动发起,服务端进行处理后返回response结果,而服务端很难主动向客户端发送数据;

这种客户端是主动方,服务端是被动方的传统Web模式。 对于信息变化不频繁的Web应用来说造成的麻烦较小,而对于涉及实时信息的Web应用却带来了很大的不便,

如带有即时通信、实时数据、订阅推送等功能的应 用。在WebSocket规范提出之前,开发人员若要实现这些实时性较强的功能,经常会使用折衷的解决方法:

  轮询(polling)Comet技术。其实后者本质上也是一种轮询,只不过有所改进。

  轮询是最原始的实现实时Web应用的解决方案。轮询技术要求客户端以设定的时间间隔周期性地向服务端发送请求,频繁地查询是否有新的数据改动。明显地,这种方法会导致过多不必要的请求,浪费流量和服务器资源。轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器。这种传统的模式带来很明显的缺点,即浏览器需要不断的向服务器发出请求,然而HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的带宽等资源。

  Comet技术又可以分为长轮询流技术长轮询改进了上述的轮询技术,减小了无用的请求。它会为某些数据设定过期时间,当数据过期后才会向服务端发送请求;这种机制适合数据的改动不是特别频繁的情况。流技术通常是指客户端使用一个隐藏的窗口与服务端建立一个HTTP长连接,服务端会不断更新连接状态以保持HTTP长连接存活;这样的话,服务端就可以通过这条长连接主动将数据发送给客户端;流技术在大并发环境下,可能会考验到服务端的性能。

  这两种技术都是基于请求-应答模式,都不算是真正意义上的实时技术;它们的每一次请求、应答,都浪费了一定流量在相同的头部信息上,并且开发复杂度也较大。

二、WebSocket

  WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。

  WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。

  在 WebSocket API 中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。

HTML5 定义的 WebSocket 协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。

浏览器通过 JavaScript 向服务器发出建立 WebSocket 连接的请求,连接建立以后,客户端和服务器端就可以通过 TCP 连接直接交换数据。

当你获取 Web Socket 连接后,你可以通过 send() 方法来向服务器发送数据,并通过 onmessage 事件来接收服务器返回的数据。

 

为客户端推送时间消息的 Tornado WebSocket程序:

import tornado.ioloop
import tornado.web
import tornado.websocket
import threading
import time
import datetime
import asyncio


from tornado.options import define, options, parse_command_line
define("port", default=8888, help="run on the given port", type=int)
clients = dict()                        # 客户端Session 字典


class IndexHandler(tornado.web.RequestHandler):

    async def get(self):
        self.render("index.html")


class MyWebSocketHandler(tornado.websocket.WebSocketHandler):

    def open(self, *args):                         # 有新链接时被调用
        self.id = self.get_argument("Id")
        # 保存Session 到 clients字典中
        clients[self.id] = {"id": self.id, "object": self}
        print("open function", str(clients))

    def on_message(self, message):              # 接受消息时被调用
        print("Client %s received a message : %s" % (self.id, message))

    def on_close(self):                            # 关闭链接时被调用
        if self.id in clients:
            del clients[self.id]
            print("Client %s is closed" % self.id)

    def check_origin(self, origin):
        return True


app = tornado.web.Application([(r'/', IndexHandler),(r'/websocket', MyWebSocketHandler)])

# 启动单独的线程运行此函数, 每隔一秒向所有的客户端推送当前时间
def sendTime():
    asyncio.set_event_loop(asyncio.new_event_loop())   # 启动异步 event loop
    while True:
        for key in clients.keys():
            msg = str(datetime.datetime.now())
            clients[key]["object"].write_message(msg)
            print("write to client %s: %s" % (key, msg))
        time.sleep(1)


if __name__ == "__main__":
    threading.Thread(target=sendTime).start()
    parse_command_line()
    app.listen(options.port)
    tornado.ioloop.IOLoop.instance().start()        # 挂起运行

##############################################################################################
  • 定义了全局变量字典clients, 用于保存所有与服务器建立起WebSocket链接的客户端信息。字典的键是客户端的id, 值是一个由id与相应的WebSocketHandler实例构成的元组;
  • IndexHandler 是一个普通的页面处理器,用于向客户端渲染主页, 该页面中包含了 Websocket 的客户端程序;
  • MyWebSockerHandler 是本例的核心处理器,继承 tornado.web.WebSocketHandler. 其中 open() 函数将所有客户端链接保存到 clients字典中;
  • on_message() 函数用于显示客户端发来的消息; on_close()函数用于将已经关闭的 WebSocket链接从 clients 字典中移除。
  • 函数 sendTime() 运行在单独的线程中,每隔一秒轮询 clients 中的所有客户端并通过 MyWebSocketHandler.write_message() 函数向客户端推送时间消息。
  • 所有 Tornado 线程中必须有一个 event_loop, 该项要求通过 sendTime() 函数中的第一行代码被满足。
  • 本例的 tornado.web.Application 实例中只配置了两个路由,分别指向 IndexHandler 和 MyWebSocketHandler, 仍然由 Tornado IOLoop 启动并运行。

index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Python后端WebSocket的实现</title>
</head>
<body>
    <a href="javascript:WebSocketTest()">Run Websocket</a>
    <div id="messages" style="height:200px; background:black; color: white;"></div>
    <script type="text/javascript">
        var messageContainer = document.getElementById("messages");
        function WebSocketTest(){

            // 判断当前浏览器是否支持WebSocket
            if("WebSocket" in window){
                messageContainer.innerHTML = "WebSocket is supported by your browser!";
                let ws = new WebSocket("ws://localhost:8888/websocket?Id=666888");

                //连接成功建立的回调方法
                ws.onopen = function(){
                    ws.send("Message to send");
                };

                //接收到消息的回调方法
                ws.onmessage = function(evt){
                    var received_msg = evt.data;
                    messageContainer.innerHTML += "<br/>Message is received: " + received_msg;
                };

                //连接关闭的回调方法
                ws.onclose = function(){
                    messageContainer.innerHTML += "<br/>Connection is closed ...";
                };
            }else{
                messageContainer.innerHTML = "WebSocket Not Supported by your Browser!";
            }
        }
    </script>
</body>
</html>

WebSocket程序运行效果:

posted @ 2019-06-12 21:35  Eagle_Fly  阅读(755)  评论(0编辑  收藏  举报