websocket介绍

websocket应用

手动实现的websocket

你所见过的websocket

你一定见过在网站中,有一个游客聊天的聊天框,比如人人影视。这个聊天框是如何实现即时通讯的呢,就是用到了websocket

你可以打开浏览器的network,会看到有个ws://xxxxx,这就代表了是websocket做的

那么什么是websocket?

websocket就是一套协议。

看名字,虽然有个websocket,但他和http协议一样,也要走socket。

不同的是:http是短连接,处理完一个请求就断开;

? websocket是连上就不断开,一直不断开,属于双工通道,服务端可以主动给客户端推送消息,客户端也可以主动给服务端推送消息

当某一个客户端发送一条消息,服务端接收以后,再推送给所有的客户端,所以才会呈现出所有人都在即时通讯的效果

服务端当然就是我们写的程序了,那客户端是浏览器,所以还需要浏览器支持才行。不要以为浏览器是都支持的,如果所有人都用chrome,前端开发工程师估计就没什么工作了。还有,如果所有的浏览器都支持,腾讯的webQQ,web微信,也不会使用长轮询来做这个事了。

来看一下具体的代码实现

import socket
import base64
import hashlib

def get_headers(data):
    """
    将请求头格式化成字典
    :param data:
    :return:
    """
    header_dict = {}
    data = str(data, encoding=‘utf-8‘)

    for i in data.split(‘\r\n‘):
        print(i)
    header, body = data.split(‘\r\n\r\n‘, 1)
    header_list = header.split(‘\r\n‘)
    for i in range(0, len(header_list)):
        if i == 0:
            if len(header_list[i].split(‘ ‘)) == 3:
                header_dict[‘method‘], header_dict[‘url‘], header_dict[‘protocol‘] = header_list[i].split(‘ ‘)
        else:
            k, v = header_list[i].split(‘:‘, 1)
            header_dict[k] = v.strip()
    return header_dict

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind((‘127.0.0.1‘, 8002))
sock.listen(5)

conn, address = sock.accept()
data = conn.recv(1024)

headers = get_headers(data)  # 提取请求头信息

# 对请求头中的sec-websocket-key进行加密
response_tpl = "HTTP/1.1 101 Switching Protocols\r\n" \
               "Upgrade:websocket\r\n" \
               "Connection: Upgrade\r\n" \
               "Sec-WebSocket-Accept: %s\r\n" \
               "WebSocket-Location: ws://%s%s\r\n\r\n"

magic_string = ‘258EAFA5-E914-47DA-95CA-C5AB0DC85B11‘       #固定的,魔法字符串就是这个字符串
value = headers[‘Sec-WebSocket-Key‘] + magic_string
ac = base64.b64encode(hashlib.sha1(value.encode(‘utf-8‘)).digest()) #把返回消息加密

response_str = response_tpl % (ac.decode(‘utf-8‘), headers[‘Host‘], headers[‘url‘])
# 响应【握手】信息
conn.send(bytes(response_str, encoding=‘utf-8‘))

info = conn.recv(8096)

#下面是对浏览器发来的消息解密的过程
payload_len = info[1] & 127
if payload_len == 126:
    extend_payload_len = info[2:4]
    mask = info[4:8]
    decoded = info[8:] # 数据
elif payload_len == 127:
    extend_payload_len = info[2:10]
    mask = info[10:14]
    decoded = info[14:]
else:
    extend_payload_len = None
    mask = info[2:6]
    decoded = info[6:]

bytes_list = bytearray()
for i in range(len(decoded)):       #上面解密的最终结果,就是拿到这个decode,就是浏览器发来的真实的数据(加密的)
    chunk = decoded[i] ^ mask[i % 4]    #按位异或
    bytes_list.append(chunk)

body = str(bytes_list, encoding=‘utf-8‘)
print(body)

客户端向服务端发送的请求里,有Sec-WebSocket-Key这样一个key,服务端回消息的时候,就要拿到这个key,加密后再发给浏览器,浏览器会判断自己加密后的值,与浏览器处理的是否一致,一致才能连接。加密的方式,用到一个magic_string,其实就是一段固定的字符串258EAFA5-E914-47DA-95CA-C5AB0DC85B11,加密后打包发给浏览器,浏览器验证通过后就可以通讯了,再来看看客户端:

客户端就直接用浏览器运行这个html文件就行

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="dist/css/bootstrap.css">
</head>
<body>

    <div>
        <input type="text" id="txt"/>
        <input type="button" id="btn" value="提交" onclick="sendMsg();"/>
        <input type="button" id="close" value="关闭连接" onclick="closeConn();"/>
    </div>
    <div id="content"></div>

    <script type="text/javascript">
          var socket = new WebSocket("ws://127.0.0.1:8002");
            socket.onopen = function () {
                /* 与服务器端连接成功后,自动执行 */
                var newTag = document.createElement(‘div‘);
                newTag.innerHTML = "【连接成功】";
                document.getElementById(‘content‘).appendChild(newTag);
            };
            socket.onmessage = function (event) {
                /* 服务器端向客户端发送数据时,自动执行 */
                var response = event.data;
                var newTag = document.createElement(‘div‘);
                newTag.innerHTML = response;
                document.getElementById(‘content‘).appendChild(newTag);
            };
            socket.onclose = function (event) {
                /* 服务器端主动断开连接时,自动执行 */
                var newTag = document.createElement(‘div‘);
                newTag.innerHTML = "【关闭连接】";
                document.getElementById(‘content‘).appendChild(newTag);
            };
            function sendMsg() {
                var txt = document.getElementById(‘txt‘);
                socket.send(txt.value);
                txt.value = "";
            }
            function closeConn() {
                socket.close();
                var newTag = document.createElement(‘div‘);
                newTag.innerHTML = "【关闭连接】";
                document.getElementById(‘content‘).appendChild(newTag);
            }
    </script>

<script></script>
</body>
</html>

这里面有三个方法:

  1. 连接上后,onopen会自动执行
  2. 发消息时,onmessage自动执行
  3. 断开连接,onclose自动执行

客户端发送给服务端的数据,还有一层加密,必须通过解密才能拿到正确的消息

payload_len = info[1] & 127
if payload_len == 126:
    extend_payload_len = info[2:4]
    mask = info[4:8]
    decoded = info[8:] # 数据
elif payload_len == 127:
    extend_payload_len = info[2:10]
    mask = info[10:14]
    decoded = info[14:]
else:
    extend_payload_len = None
    mask = info[2:6]
    decoded = info[6:]

bytes_list = bytearray()
for i in range(len(decoded)):       #上面解密的最终结果,就是拿到这个decode,就是浏览器发来的真实的数据(加密的)
    chunk = decoded[i] ^ mask[i % 4]    #按位异或
    bytes_list.append(chunk)

body = str(bytes_list, encoding=‘utf-8‘)

这段就是解密的过程,用到位运算

Django默认是不支持websocket的,虽然有个第三方的channels插件

但是tornado默认就支持

tornado实现websocket

如果用tornado,客户端不能直接用浏览器运行了,而应该是运行tornado的一个模板文件

服务端代码:

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import uuid
import json
import tornado.ioloop
import tornado.web
import tornado.websocket

class IndexHandler(tornado.web.RequestHandler):
    def get(self):
        self.render(‘index.html‘)

class ChatHandler(tornado.websocket.WebSocketHandler):
    # 用户存储当前聊天室用户
    waiters = set()
    # 用于存储历时消息
    messages = []

    def open(self):
        """
        客户端连接成功时,自动执行
        :return:
        """
        ChatHandler.waiters.add(self)
        uid = str(uuid.uuid4())
        self.write_message(uid)

        # 下面这段代码是给新加入的用户,显示历史信息的
        for msg in ChatHandler.messages:
            # {‘uid‘:‘xxx‘,‘message‘:asdfasd}
            content = self.render_string(‘message.html‘, **msg)
            self.write_message(content)

    def on_message(self, message):
        """
        客户端连发送消息时,自动执行
        :param message:
        :return:
        """
        msg = json.loads(message)
        ChatHandler.messages.append(msg)

        for client in ChatHandler.waiters:
            content = client.render_string(‘message.html‘, **msg)
            client.write_message(content)

    def on_close(self):
        """
        客户端关闭连接时,,自动执行
        :return:
        """
        ChatHandler.waiters.remove(self)

def run():
    settings = {
        ‘template_path‘: ‘templates‘,       # 配置模板文件
        ‘static_path‘: ‘static‘,            # 配置静态文件路径
    }
    application = tornado.web.Application([         # 配置路由
        (r"/", IndexHandler),
        (r"/chat", ChatHandler),
    ], **settings)
    application.listen(8009)
    tornado.ioloop.IOLoop.instance().start()

if __name__ == "__main__":
    run()

模板文件(客户端代码):

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Python聊天室</title>
</head>
<body>
    <div>
        <input type="text" id="txt"/>
        <input type="button" id="btn" value="提交" onclick="sendMsg();"/>
        <input type="button" id="close" value="关闭连接" onclick="closeConn();"/>
    </div>
    <div id="container" style="border: 1px solid #dddddd;margin: 20px;min-height: 500px;">

    </div>

    <script src="/static/jquery-3.2.1.js"></script>
    <script type="text/javascript">
        $(function () {
            wsUpdater.start();
        });
        var wsUpdater = {
            socket: null,
            uid: null,
            start: function() {
                var url = "ws://192.168.16.200:8009/chat";
                wsUpdater.socket = new WebSocket(url);
                wsUpdater.socket.onmessage = function(event) {
                    if(wsUpdater.uid){
                        wsUpdater.showMessage(event.data);
                    }else{
                        wsUpdater.uid = event.data;
                    }
                }
            },
            showMessage: function(content) {
                $(‘#container‘).append(content);
            }
        };
        function sendMsg() {
            var msg = {
                uid: wsUpdater.uid,
                message: $("#txt").val()
            };
            wsUpdater.socket.send(JSON.stringify(msg));
        }
</script>

</body>
</html>

原理都一样,但是用tornado实现起来,就清爽多了。

ps:再说一下腾讯的长轮询,如果你登录webQQ,或者web微信,你可以在network里面找到 pending的字样,这就是表示是使用的长轮询。

长轮询与轮询的区别就是:

? 轮询是过来以后看到没消息就立马去走了,但是长轮询不会立马走,而是在这等30秒(约定的时间)之后,如果一直没有消息,才返回,下一次来在等30秒,直到有消息了,这样有个缺点就是,拿到的消息并不是即时的。那腾讯这么大的公司,为什么不用性能更好的websocket呢?原因就是他是个大公司,必须要考虑兼容性,必须要保证所有的浏览器都能使用才行。

你可以从这里拿到完整 的示例代码

https://github.com/zEllis/websocket_demo

时间: 2024-09-28 17:18:16

websocket介绍的相关文章

WebSocket介绍,与Socket的区别

WebSocket介绍与原理 WebSocket protocol 是HTML5一种新的协议.它实现了浏览器与服务器全双工通信(full-duplex).一开始的握手需要借助HTTP请求完成. --百度百科 目的:即时通讯,替代轮询 网站上的即时通讯是很常见的,比如网页的QQ,聊天系统等.按照以往的技术能力通常是采用轮询.Comet技术解决. HTTP协议是非持久化的,单向的网络协议,在建立连接后只允许浏览器向服务器发出请求后,服务器才能返回相应的数据.当需要即时通讯时,通过轮询在特定的时间间隔

WebSocket 介绍(二)-WebSocket API

这一章介绍如何用WebSocket API来控制协议和创建应用,运用http://websocket.org 提供的现有WebSocket服务器,我们可以收发消息.创建一些简单的WebSocket应用.一步一步的学习使用WebSocket API,最后我们会讨论浏览器的支持度和连通性.这一章的重点是WebSocket 协议在Web客户端的应用,在稍后的章节会介绍WebSocket协议以及其使用环境. 综述: 正如第一章提到的,WebSocket包含网络协议和API,让你能够在客户端和服务端创建W

WebSocket 介绍(一)

WebSocket 发起单个请求,服务端不需要等待客服端,客户端在任何时候也能发消息到服务端,减少了轮询时候的延迟.轮询与WebSocket的区别.简历一次连接后,服务器能给客户端发多次. 基于http的实时消息是相当的复杂的,在无状态的请求中维持回话的状态增加了复杂度,跨域是曲折的,使用ajax处理请求有序请求需要考虑更多.通过ajax进行交流也不简单.每一个延伸http功能的目的不是增加他的复杂度.websocket 可以大大简化实时通信应用中的链接. Websocket是关于标准 Webs

Jetty开发指导:WebSocket介绍

WebSocket是一个新的基于HTTP的双向通讯的协议. 它是基于低级别的框架协议.使用UTF-8 TEXT或者BINARY格式传递信息. 在WebSocket中的单个信息能够是不论什么长度(然而底层框架有单帧63bits的限制). 发送的信息数量不受限制. 信息被持续的发送,基础协议不支持交叉的信息. 一个WebSocket连接经历一些主要的状态改变: 1)CONNECTING:HTTP升级到WebSocket正在进行中 2)OPEN:HTTP升级成功,而且socket如今打开并准备读/写

WebSocket介绍和一个简单的聊天室

WebSocket是什么呢? WebSocket一种在单个 TCP 连接上进行全双工通讯的协议.WebSocket通信协议于2011年被IETF定为标准RFC 6455,并被RFC7936所补充规范,WebSocketAPI被W3C定为标准. WebSocket 是独立的.创建在 TCP 上的协议,和 HTTP 的唯一关联是使用 HTTP 协议的101状态码进行协议切换,使用的 TCP 端口是80,可以用于绕过大多数防火墙的限制. WebSocket 使得客户端和服务器之间的数据交换变得更加简单

WebSocket介绍与原理

WebSocket 是一种双向通信协议,在建立连接后,WebSocket 服务器和 Browser/Client Agent 都能主动的向对方发送或接收数据,就像 Socket 一样: WebSocket 需要类似 TCP 的客户端和服务器端通过握手连接,连接成功后才能相互通信. 目的:即时通讯,替代轮询 连接过程 -- 握手过程 浏览器.服务器建立TCP连接,三次握手.这是通信的基础,传输控制层,若失败后续都不执行. TCP连接成功后,浏览器通过HTTP协议向服务器传送WebSocket支持的

Html5 WebSocket 技术介绍

WebSocket是html5规范新引入的功能,用于解决浏览器与后台服务器双向通讯的问题,使用WebSocket技术,后台可以随时向前端推送消息,以保证前后台状态统一,在传统的无状态HTTP协议中,这是“无法做到”的. 传统服务端推(server push)技术 WebSocket提出之前,为了解决后台推送消息到前台的需求,提出了一些解决方案,这些方案使用已有的技术(如ajax,iframe,flashplayer,java applet …),通过一些变通的处理来实现. 简单轮询 最简单的是前

websocket实战(1) 入门

1.WebSocket介绍 1.1 概念 WebSocket是HTML5中一系列新的API,或者说新规范,新技术.支持页面上使用Web Socket协议与远程主机进行全双工的通信.它引入了WebSocket接口并且定义了一个全双工的通信通道,通过一个单一的套接字在Web上进行操作. 1.2 websocket vs HTTP 首先,web技术发展经历了以下阶段. 静态页面(html) 动态页面(cgi,j2ee,php...) Ajax技术 comet技术(轮询) 1.2.1 实现方案对比 举个

[Python]通过websocket与jsclient通信

站点大多使用HTTP协议通信.而HTTP是无连接的协议.仅仅有client请求时,server端才干发出对应的应答.HTTP请求的包也比較大,假设仅仅是非常小的数据通信.开销过大.于是,我们能够使用websocket这个协议,用最小的开销实现面向连接的通信. 详细的websocket介绍可见http://zh.wikipedia.org/wiki/WebSocket 这里,介绍怎样使用Python与前端js进行通信. websocket使用HTTP协议完毕握手之后,不通过HTTP直接进行webs