WebSocket是HTML5新增的协议,它的目的是在浏览器和服务器之间创建一个不受限的双向通讯的通道,好比说,服务器能够在任意时刻发送消息给浏览器。html
为何传统的HTTP协议不能作到WebSocket实现的功能?这是由于HTTP协议是一个请求-响应协议,请求必须先由浏览器发给服务器,服务器才能响应这个请求,再把数据发送给浏览器。换句话说,浏览器不主动请求,服务器是无法主动发数据给浏览器的。前端
WebSocket 使得客户端和服务器之间的数据交换变得更加简单,容许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只须要完成一次握手,二者之间就直接能够建立持久性的链接,并进行双向数据传输。web
也有人说,HTTP协议其实也能实现啊,好比用轮询或者Comet。json
轮询是指浏览器经过JavaScript启动一个定时器,而后以固定的间隔给服务器发请求,询问服务器有没有新消息。flask
缺点:浪费客户端资源后端
浏览器和服务器之间能够创建无限制的全双工通讯,任何一方均可以主动发消息给对方。浏览器
浏览器经过 JavaScript 向服务器发出创建 WebSocket 链接的请求,链接创建之后,客户端和服务器端就能够经过 TCP 链接直接交换数据。服务器
当你获取 Web Socket 链接后,你能够经过 send() 方法来向服务器发送数据,并经过 onmessage 事件来接收服务器返回的数据。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请求有几点不一样:
GET请求的地址不是相似/path/,而是以ws://开头的地址;
请求头Upgrade: websocket和Connection: Upgrade表示这个链接将要被转换为WebSocket链接;
Sec-WebSocket-Key是用于标识这个链接,并不是用于加密数据;
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
轮询:客户端定时向服务器发送Ajax请求,服务器接到请求后立刻返回响应信息并关闭链接。
长轮询:客户端向服务器发送Ajax请求,服务器接到请求后hold住链接,直到有新消息才返回响应信息并关闭链接(或到了设定的超时时间关闭链接),客户端处理完响应信息后再向服务器发送新的请求。
长链接:在页面里嵌入一个隐蔵iframe,将这个隐蔵iframe的src属性设为对一个长链接的请求,服务器端就能源源不断地往客户端输入数据。
链接保持 - Http 发起请求 在请求中写一个协议 - WebSocket - 服务器收到Websocket请求 ,自动保持此链接 - 永久不断开,除非主动断开 - 能够经过此链接主动找到客户端
如下是 WebSocket 对象的属性。假定咱们使用了以上代码建立了 Socket 对象:
类型 | 解释 |
---|---|
Socket.readyState | 只读属性 readyState 表示链接状态,能够是如下值:0 - 表示链接还没有创建。1 - 表示链接已创建,能够进行通讯。2 - 表示链接正在进行关闭。3 - 表示链接已经关闭或者链接不能打开。 |
Socket.bufferedAmount | 只读属性 bufferedAmount 已被 send() 放入正在队列中等待传输,可是尚未发出的 UTF-8 文本字节数。 |
如下是 WebSocket 对象的相关事件。假定咱们使用了以上代码建立了 Socket 对象:
事件 | 事件处理程序 | 描述 |
---|---|---|
open | Socket.onopen | 链接创建时触发 |
message | Socket.onmessage | 客户端接收服务端数据时触发 |
error | Socket.onerror | 通讯发生错误时触发 |
close | Socket.onclose | 链接关闭时触发 |
如下是 WebSocket 对象的相关方法。假定咱们使用了以上代码建立了 Socket 对象:
方法 | 描述 |
---|---|
Socket.send() | 使用链接发送数据 |
Socket.close() | 关闭链接 |
既然学习了websocket的使用,如今咱们就要基于flask来实现即时通讯的简易版,也就是群聊和私聊的网页版,对于页面效果,你们就别吐槽了,仅用做练习学习。
因为是基于flask来实现的websocket,咱们须要装flask和gevent-websocket
pip3 install flask
pip3 install gevent-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进行群聊,能够开多个服务器模拟多用户群聊,查看效果。
私聊后端代码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>