WebSocket知识、轮询、长轮询、长链接

1、WebSocket理论知识

1.什么是websocket

WebSocket是HTML5新增的协议,它的目的是在浏览器和服务器之间创建一个不受限的双向通讯的通道,好比说,服务器能够在任意时刻发送消息给浏览器。html

为何传统的HTTP协议不能作到WebSocket实现的功能?这是由于HTTP协议是一个请求-响应协议,请求必须先由浏览器发给服务器,服务器才能响应这个请求,再把数据发送给浏览器。换句话说,浏览器不主动请求,服务器是无法主动发数据给浏览器的。前端

WebSocket 使得客户端和服务器之间的数据交换变得更加简单,容许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只须要完成一次握手,二者之间就直接能够建立持久性的链接,并进行双向数据传输。web

2.为何使用Websocket

也有人说,HTTP协议其实也能实现啊,好比用轮询或者Comet。json

轮询

轮询是指浏览器经过JavaScript启动一个定时器,而后以固定的间隔给服务器发请求,询问服务器有没有新消息。flask

缺点:浪费客户端资源后端

websocket

浏览器和服务器之间能够创建无限制的全双工通讯,任何一方均可以主动发消息给对方。浏览器

 

浏览器经过 JavaScript 向服务器发出创建 WebSocket 链接的请求,链接创建之后,客户端和服务器端就能够经过 TCP 链接直接交换数据。服务器

当你获取 Web Socket 链接后,你能够经过 send() 方法来向服务器发送数据,并经过 onmessage 事件来接收服务器返回的数据。websocket

3.websocket协议

WebSocket并非全新的协议,而是利用了HTTP协议来创建链接。咱们来看看WebSocket链接是如何建立的。网络

首先,WebSocket链接必须由浏览器发起,由于请求协议是一个标准的HTTP请求,格式以下:

GET ws://localhost:3000/ws/chat HTTP/1.1
Host: localhost
Upgrade: websocket
Connection: Upgrade
Origin: http://localhost:3000
Sec-WebSocket-Key: client-random-string
Sec-WebSocket-Version: 13

该请求和普通的HTTP请求有几点不一样:

  1. GET请求的地址不是相似/path/,而是以ws://开头的地址;

  2. 请求头Upgrade: websocket和Connection: Upgrade表示这个链接将要被转换为WebSocket链接;

  3. Sec-WebSocket-Key是用于标识这个链接,并不是用于加密数据;

  4. Sec-WebSocket-Version指定了WebSocket的协议版本。

随后,服务器若是接受该请求,就会返回以下响应:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: server-random-string

该响应代码101表示本次链接的HTTP协议即将被更改,更改后的协议就是Upgrade: websocket指定的WebSocket协议。

版本号和子协议规定了双方能理解的数据格式,以及是否支持压缩等等。若是仅使用WebSocket的API,就不须要关心这些。

如今,一个WebSocket链接就创建成功,浏览器和服务器就能够随时主动发送消息给对方。消息有两种,一种是文本,一种是二进制数据。一般,咱们能够发送JSON格式的文本,这样,在浏览器处理起来就十分容易。

浏览器

很显然,要支持WebSocket通讯,浏览器得支持这个协议,这样才能发出ws://xxx的请求。目前,支持WebSocket的主流浏览器以下:

  • Chrome

  • Firefox

  • IE >= 10

  • Sarafi >= 6

  • Android >= 4.4

  • iOS >= 8

4.轮询、长轮询、长链接概念

轮询:客户端定时向服务器发送Ajax请求,服务器接到请求后立刻返回响应信息并关闭链接。

  • 优势:后端程序编写比较容易。
  • 缺点:请求中有大半是无用,浪费带宽和服务器资源。(而每一次的 HTTP 请求和应答都带有完整的 HTTP 头信息,这就增长了每次传输的数据量)
  • 实例:适于小型应用。

长轮询:客户端向服务器发送Ajax请求,服务器接到请求后hold住链接,直到有新消息才返回响应信息并关闭链接(或到了设定的超时时间关闭链接),客户端处理完响应信息后再向服务器发送新的请求。

  • 优势:在无消息的状况下不会频繁的请求,节省了网络流量,解决了服务端一直疲于接受请求的窘境
  • 缺点:服务器hold链接会消耗资源,须要同时维护多个线程,服务器所能承载的TCP链接数是有上限的,这种轮询很容易把链接数顶满。
  • 实例:WebQQ、Hi网页版、Facebook IM。

长链接:在页面里嵌入一个隐蔵iframe,将这个隐蔵iframe的src属性设为对一个长链接的请求,服务器端就能源源不断地往客户端输入数据。
链接保持 - Http 发起请求 在请求中写一个协议 - WebSocket - 服务器收到Websocket请求 ,自动保持此链接 - 永久不断开,除非主动断开 - 能够经过此链接主动找到客户端

  • 优势:消息即时到达,不发无用请求。
  • 缺点:服务器维护一个长链接会增长开销。
  • 实例:Gmail聊天

2、WebSocket的相关方法

1.WebSocket 属性

如下是 WebSocket 对象的属性。假定咱们使用了以上代码建立了 Socket 对象:

类型 解释
Socket.readyState 只读属性 readyState 表示链接状态,能够是如下值:0 - 表示链接还没有创建。1 - 表示链接已创建,能够进行通讯。2 - 表示链接正在进行关闭。3 - 表示链接已经关闭或者链接不能打开。
Socket.bufferedAmount 只读属性 bufferedAmount 已被 send() 放入正在队列中等待传输,可是尚未发出的 UTF-8 文本字节数。

2.WebSocket事件

如下是 WebSocket 对象的相关事件。假定咱们使用了以上代码建立了 Socket 对象:

事件 事件处理程序 描述
open Socket.onopen 链接创建时触发
message Socket.onmessage 客户端接收服务端数据时触发
error Socket.onerror 通讯发生错误时触发
close Socket.onclose 链接关闭时触发

3.WebSocket 方法

如下是 WebSocket 对象的相关方法。假定咱们使用了以上代码建立了 Socket 对象:

方法 描述
Socket.send() 使用链接发送数据
Socket.close() 关闭链接

3、Websocket实战练习

既然学习了websocket的使用,如今咱们就要基于flask来实现即时通讯的简易版,也就是群聊和私聊的网页版,对于页面效果,你们就别吐槽了,仅用做练习学习。

环境包准备

因为是基于flask来实现的websocket,咱们须要装flask和gevent-websocket

pip3 install flask
pip3 install gevent-websocket

1.websocket实现群聊代码

群聊后端代码groupChat.py

from flask import Flask,render_template,request
from geventwebsocket.handler import WebSocketHandler  # 提供ws协议处理
from geventwebsocket.server import WSGIServer  # 承载服务
from geventwebsocket.websocket import WebSocket  # 提供语法提示
​
app = Flask(__name__)
​
user_socket_dict = {}
​
# 群发消息视图
@app.route("/groupchat/<username>")
def groupChat(username):
    # 获取当前客户端与服务器的socket链接
    user_socket = request.environ.get("wsgi.websocket")  # type: WebSocket
    if user_socket:
        # 保存连接到字典中,用户名做为键
        user_socket_dict[username] = user_socket
        print(len(user_socket_dict),user_socket_dict)
    while 1:
        msg = user_socket.receive()  # 接受每一个客户端的消息
        # 遍历字典,给每个客户端发送消息
        for name,socket in user_socket_dict.items():
            try:
                socket.send(msg)
            except:
                pass# 获取聊天页面视图
@app.route("/chat")
def chat():
    return render_template("groupChat.html")
​
​
if __name__ == '__main__':
    # 经过WSGIServer来启动web服务,并指定用WebSocketHandler来处理websocket的请求
    server = WSGIServer(("0.0.0.0",9527),app,handler_class=WebSocketHandler)
    server.serve_forever()

前端页面groupChat.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div style="text-align: center">
    <p id="login-tag">
        <input type="text" id="username">
        <button id="login-btn" onclick="login()">登陆</button>
    </p>
    <p>
        <input type="text" id="content">
        <button id="send-btn" onclick="sendMsg()">发送</button>
    </p>
</div>
<div id="chat_list"></div></body>
<script>
    var ws = null;
​
    // 登陆创建websocket连接函数
    function login() {
        var username = document.getElementById("username").value;
        console.log(username);
        var tag = document.getElementById("login-tag");
​
        tag.style.display = "none";  // 登陆后隐藏登陆框
        ws = new WebSocket("ws://192.168.16.13:9527/groupchat/"+username);
​
        // 监听电话
        ws.onmessage = function (messageEvent) {
            // 获取服务器发送过来的数据
            var msg = messageEvent.data;
            // 对json字符串进行反序列化
            msg_dic = JSON.parse(msg);
            var p = document.createElement("p");
            p.innerText = msg_dic.from_user + ":" + msg_dic.info;
​
            // 添加聊天记录到页面中
            document.getElementById("chat_list").appendChild(p)
        };
    }
​
    // 发送消息的函数
    function sendMsg() {
        // 原生js获取数据
        var username = document.getElementById("username").value;
        var content = document.getElementById("content").value;
​
        // 把数据封装在自定义对象中
        var msg = {
            from_user:username,
            info:content
        };
        // 经过websocket连接发送数据
        ws.send(JSON.stringify(msg));
    }
​
</script>
</html>

启动flask项目,访问页面192.168.16.13:9527/chat进行群聊,能够开多个服务器模拟多用户群聊,查看效果。

2.websocket实现私聊实战

私聊后端代码privateChat.py

from flask import Flask,render_template,request
from geventwebsocket.handler import WebSocketHandler
from geventwebsocket.server import WSGIServer
from geventwebsocket.websocket import WebSocket
import json
​
app = Flask(__name__)
​
user_socket_dict = {}
​
# 用户私聊视图
@app.route("/privateChat/<username>")
def privateChat(username):
    # 获取客户端和服务器之间的连接
    user_socket = request.environ.get("wsgi.websocket")  # type: WebSocket
if user_socket:
        # 保存连接到字典中,用户名做为键
        user_socket_dict[username] = user_socket
        print(len(user_socket_dict), user_socket_dict)
​
    while True:
        # 获取客户端发送的信息
        msg = user_socket.receive()
        msg_dic = json.loads(msg)
        to_user = msg_dic.get("to_user")  # 获取目标用户名
        to_user_socket = user_socket_dict.get(to_user)  # 根据用户名获取用户的连接
        to_user_socket.send(msg)  # 给目标用户发送信息
# 获取聊天页面视图
@app.route("/chat")
def chat():
    return render_template("privateChat.html")
​
​
if __name__ == '__main__':
    # 经过WSGIServer来启动web服务,并指定用WebSocketHandler来处理websocket的请求
    server = WSGIServer(("0.0.0.0",8520),app,handler_class=WebSocketHandler)
    server.serve_forever()

私聊前端代码privateChat.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div style="text-align: center">
    <p id="login-tag">
        <input type="text" id="username">
        <button id="login-btn" onclick="login()">登陆</button>
    </p>
    <p>
        <input type="text" id="content">
        <button id="send-btn" onclick="sendMsg()">发送</button>
    </p>
</div>
<div id="chat_list"></div></body>
<script>
    var ws = null;
​
    // 登陆创建websocket连接函数
    function login() {
        var username = document.getElementById("username").value;
        console.log(username);
        var tag = document.getElementById("login-tag");
​
        tag.style.display = "none";  // 登陆后隐藏登陆框
        ws = new WebSocket("ws://192.168.16.13:9527/groupchat/"+username);
​
        // 监听电话
        ws.onmessage = function (messageEvent) {
            // 获取服务器发送过来的数据
            var msg = messageEvent.data;
            // 对json字符串进行反序列化
            msg_dic = JSON.parse(msg);
            var p = document.createElement("p");
            p.innerText = msg_dic.from_user + ":" + msg_dic.info;
​
            // 添加聊天记录到页面中
            document.getElementById("chat_list").appendChild(p)
        };
    }
​
    // 发送消息的函数
    function sendMsg() {
        // 原生js获取数据
        var username = document.getElementById("username").value;
        var content = document.getElementById("content").value;
​
        // 把数据封装在自定义对象中
        var msg = {
            from_user:username,
            info:content
        };
        // 经过websocket连接发送数据
        ws.send(JSON.stringify(msg));
    }
​
</script>
</html>
相关文章
相关标签/搜索