WebSocket
WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。
WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
在 WebSocket API 中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。
在 WebSocket 没出现前 有以下几种解决办法:
轮询
对客户端 以及服务器要求相对要高。
小资源访问时优势很明显 - 因为客户端较少
程序在每次请求时都会新建一个HTTP请求,然而并不是每次都能返回所需的新数据。当同时发起的请求达到一定数目时,会对服务器造成较大负担
当前Web应用中较常见的一种持续通信方式,通常采取 setInterval
或者 setTimeout
实现。例如如果我们想要定时获取并刷新页面上的数据,可以结合Ajax写出如下实现:
setInterval(function() { $.get("/path/to/server", function(data, status) { console.log(data); }); }, 10000);
上面的程序会每隔10秒向服务器请求一次数据,并在数据到达后存储。这个实现方法通常可以满足简单的需求,然而同时也存在着很大的缺陷:在网络情况不稳定的情况下,服务器从接收请求、发送请求到客户端接收请求的总时间有可能超过10秒,而请求是以10秒间隔发送的,这样会导致接收的数据到达先后顺序与发送顺序不一致。于是出现了采用 setTimeout
的轮询方式:
function poll() { setTimeout(function() { $.get("/path/to/server", function(data, status) { console.log(data); // 发起下一次请求 poll(); }); }, 10000); }
程序首先设置10秒后发起请求,当数据返回后再隔10秒发起第二次请求,以此类推。这样的话虽然无法保证两次请求之间的时间间隔为固定值,但是可以保证到达数据的顺序。
长轮询
客户端要求较低 服务器相对较高
服务器不响应客户端而是将连接暂时保持住 n秒之后 没有消息,响应客户端
一定时间之后 断开连接 ,客户端重新发起请求
WebSocket
WebSocket 协议本质上是一个基于 TCP 的协议。
为了建立一个 WebSocket 连接,客户端浏览器首先要向服务器发起一个 HTTP 请求,这个请求和通常的 HTTP 请求不同,包含了一些附加头信息,其中附加头信息"Upgrade: WebSocket"表明这是一个申请协议升级的 HTTP 请求,服务器端解析这些附加的头信息然后产生应答信息返回给客户端,客户端和服务器端的 WebSocket 连接就建立起来了,双方就可以通过这个连接通道自由的传递信息,并且这个连接会持续存在直到客户端或者服务器端的某一方主动的关闭连接。
WebSocket 属性
-
0 - 表示连接尚未建立。
-
1 - 表示连接已建立,可以进行通信。
-
2 - 表示客户端请求关闭连接。
-
3 - 表示连接已经关闭或者连接不能打开。
WebSocket 事件
Socket.onopen | 连接建立时触发 |
Socket.onmessage | 客户端接收服务端数据时触发 |
Socket.onerror | 通信发生错误时触发 |
Socket.onclose | 连接关闭时触发 |
示例 :
对人聊天案例
# 后端代码 from flask import Flask, request, render_template from geventwebsocket.handler import WebSocketHandler # 提供WS协议处理 from geventwebsocket.server import WSGIServer # 承载服务 from geventwebsocket.websocket import WebSocket # 语法提示 app = Flask(__name__) # 实例化 user_list = [] @app.route('/mygs') # WS 访问路径 def mygs(): user_socket = request.environ.get('wsgi.websocket') # type:WebSocket # 语法提示 print(user_socket) if user_socket: user_list.append(user_socket) while 1: msg = user_socket.receive() for i in user_list: try: i.send(msg) except: continue @app.route('/gs') def gs(): # HTTP访问路径 return render_template('mygs.html') if __name__ == '__main__': ''' WSGIServer : 是一个WSGIServer服务,实例化WSGIServer对象 ('192.168.16.94', 9999) :('192.168.16.94', 9999) app : 处理逻辑的应用 handler_class : 处理请求 ''' http_server = WSGIServer(('192.168.16.94', 9999), app, handler_class=WebSocketHandler) http_server.serve_forever()
# 前端代码 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>群聊</title> </head> <body> <p> <input type="text" id="content"> <button id="btn">发送</button> </p> <div id="chat_list"> </div> </body> <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script> <script type="application/javascript"> var ws = new WebSocket("ws://192.168.16.94:9999/mygs"); //创建WS对象(连接) 用于发送和接收 // 监听电话 ws.onmessage = function (eventMessage) { // 客户端接收服务端数据时触发 console.log(eventMessage.data); var p = document.createElement("p"); // 新建标签 p.innerText = eventMessage.data; //填充内容 $('#chat_list').append(p) //追加到指定表现 }; $('#btn').on('click',function () { var content = $('#content').val(); // 获取标签内的数据 ws.send(content); // 通过WS进行传输 }); </script> </html>
多人聊天 -- 携带昵称版
# 前端你代码 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <p> <input type="text" id="name"> <button id="login">登陆</button> </p> <p> <input type="text" id="content"> <button id="btn">发送</button> </p> <div id="chat_list"> </div> <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script> </body> <script type="application/javascript"> var ws = null; $('#login').on('click', function () { // 用户登陆,收集用户名称 var name = $("#name").val(); ws = new WebSocket("ws://192.168.16.94:9999/mygs/" + name); // 用户名称作为参数发送 ws.onmessage = function (eventmessage) { // 等待服务的发送信息触发该函数 console.log(eventmessage); str_obj = JSON.parse(eventmessage.data); // 反序列化,得到用户输入的内容 var p = document.createElement('p'); // 新建标签 $(p).text(str_obj.user + ':' + str_obj.data); // 将用户输入内容填充至标签内, $('#chat_list').append(p) }; }) ; $('#btn').on('click', function () { // 点击发送按钮,触发该函数 var name = $('#name').val(); // 获取用户名称 var content = $('#content').val(); // 获取用户输入信息 var str_dic = { // 自定义数据结构 'user': name, 'data': content }; ws.send(JSON.stringify(str_dic)) // 发送至后端 }) </script> </html>
# 后端代码 # -*- coding: utf-8 -*- # Date: 2019/7/15 from flask import Flask, request, render_template from geventwebsocket.handler import WebSocketHandler # 提供WS协议处理 from geventwebsocket.server import WSGIServer # 承载服务 from geventwebsocket.websocket import WebSocket # 语法提示 app = Flask(__name__) # 实例化 user_dic = {} # 存放用户 @app.route('/mygs/<name>') # WS 访问路径 def mygs(name): user_socket = request.environ.get('wsgi.websocket') # type:WebSocket # 语法提示 if user_socket: user_dic.setdefault(name,user_socket) while True: msg = user_socket.receive() # 等待信息 for i in user_dic.values(): # 循环,每个用户都发送信息 try: i.send(msg) except: continue @app.route('/gs') def gs(): # HTTP访问路径 return render_template('umygs.html') if __name__ == '__main__': ''' WSGIServer : 是一个WSGIServer服务,实例化WSGIServer对象 ('192.168.16.94', 9999) :('192.168.16.94', 9999) app : 处理逻辑的应用 handler_class : 处理请求 ''' http_server = WSGIServer(('192.168.16.94', 9999), app, handler_class=WebSocketHandler) http_server.serve_forever()
一对一聊天
# 前端代码 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <p> <input type="text" id="name"> <button id="login">登陆</button> </p> <P> 给<input type="text" id="to_user"> 发送<input type="text" id="info"> <button id="btn">ok</button> </P> <div id="chat_list"> </div> </body> <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script> <script type="application/javascript"> var ws = null; $('#login').on('click', function () { var name = $('#name').val(); ws = new WebSocket('ws://192.168.16.94:9999/onetoone/' + name); ws.onmessage = function (evenmessage) { var str_data = JSON.parse(evenmessage.data); var p = document.createElement('p'); $(p).text(str_data.name + ":" + str_data.data); $('#chat_list').append(p); }; }); $('#btn').on('click', function () { var name = $('#name').val(); var to_user = $('#to_user').val(); var info = $('#info').val(); var send_data = { 'name': name, 'to_user': to_user, 'data': info }; ws.send(JSON.stringify(send_data)) }) </script> </html>
# 后端代码 from flask import Flask, request, render_template from geventwebsocket.handler import WebSocketHandler # 提供WS协议处理 from geventwebsocket.server import WSGIServer # 承载服务 from geventwebsocket.websocket import WebSocket # 语法提示 import json app = Flask(__name__) # 实例化 user_dic = {} # 存放用户 @app.route('/onetoone/<name>') # WS 访问路径 def onetoone(name): user_socket = request.environ.get('wsgi.websocket') # type:WebSocket # 语法提示 if user_socket: user_dic.setdefault(name, user_socket) while 1: msg = user_socket.receive() msg_dic = json.loads(msg) # 反序列化信息 to_user = msg_dic.get('to_user') # 获取目标用户 to_user_socket = user_dic.get(to_user) to_user_socket.send(msg) @app.route('/sl') def sl(): return render_template('onetoone.html') if __name__ == '__main__': http_socket = WSGIServer(('192.168.16.94',9999),app,handler_class=WebSocketHandler) http_socket.serve_forever()