WebSocket知识、轮询、长轮询、长连接
一、WebSocket理论知识
1.什么是websocket
WebSocket是HTML5新增的协议,它的目的是在浏览器和服务器之间建立一个不受限的双向通信的通道,比如说,服务器可以在任意时刻发送消息给浏览器。
为什么传统的HTTP协议不能做到WebSocket实现的功能?这是因为HTTP协议是一个请求-响应协议,请求必须先由浏览器发给服务器,服务器才能响应这个请求,再把数据发送给浏览器。换句话说,浏览器不主动请求,服务器是没法主动发数据给浏览器的。
WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
2.为什么使用Websocket
也有人说,HTTP协议其实也能实现啊,比如用轮询或者Comet。
轮询
轮询是指浏览器通过JavaScript启动一个定时器,然后以固定的间隔给服务器发请求,询问服务器有没有新消息。
缺点:浪费客户端资源
websocket
浏览器和服务器之间可以建立无限制的全双工通信,任何一方都可以主动发消息给对方。
浏览器通过 JavaScript 向服务器发出建立 WebSocket 连接的请求,连接建立以后,客户端和服务器端就可以通过 TCP 连接直接交换数据。
当你获取 Web Socket 连接后,你可以通过 send() 方法来向服务器发送数据,并通过 onmessage 事件来接收服务器返回的数据。
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请求有几点不同:
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
4.轮询、长轮询、长连接概念
轮询:客户端定时向服务器发送Ajax请求,服务器接到请求后马上返回响应信息并关闭连接。
- 优点:后端程序编写比较容易。
- 缺点:请求中有大半是无用,浪费带宽和服务器资源。(而每一次的 HTTP 请求和应答都带有完整的 HTTP 头信息,这就增加了每次传输的数据量)
- 实例:适于小型应用。
长轮询:客户端向服务器发送Ajax请求,服务器接到请求后hold住连接,直到有新消息才返回响应信息并关闭连接(或到了设定的超时时间关闭连接),客户端处理完响应信息后再向服务器发送新的请求。
- 优点:在无消息的情况下不会频繁的请求,节省了网络流量,解决了服务端一直疲于接受请求的窘境
- 缺点:服务器hold连接会消耗资源,需要同时维护多个线程,服务器所能承载的TCP连接数是有上限的,这种轮询很容易把连接数顶满。
- 实例:WebQQ、Hi网页版、Facebook IM。
长连接:在页面里嵌入一个隐蔵iframe,将这个隐蔵iframe的src属性设为对一个长连接的请求,服务器端就能源源不断地往客户端输入数据。
连接保持 - Http 发起请求 在请求中写一个协议 - WebSocket - 服务器收到Websocket请求 ,自动保持此连接 - 永久不断开,除非主动断开 - 可以通过此连接主动找到客户端
- 优点:消息即时到达,不发无用请求。
- 缺点:服务器维护一个长连接会增加开销。
- 实例:Gmail聊天
二、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() | 关闭连接 |
三、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>
WebSocket知识、轮询、长轮询、长连接的更多相关文章
- php websocket-网页实时聊天之PHP实现websocket(ajax长轮询和websocket都可以时间网络聊天室)
php websocket-网页实时聊天之PHP实现websocket(ajax长轮询和websocket都可以时间网络聊天室) 一.总结 1.ajax长轮询和websocket都可以时间网络聊天室 ...
- 轮询、长轮询和websocket
一.轮询 在一些需要进行实时查询的场景下应用比如投票系统: 大家一起在一个页面上投票 在不刷新页面的情况下,实时查看投票结果 1.后端代码 from flask import Flask, rende ...
- 了解轮询、长轮询、长连接、websocket
业务开发中我们往往会有一些需要即时通信的场景,比如微信扫码登录.聊天功能. 下面这四种方式都可以实现即时通信. 轮询: 浏览器通过定时器每隔一段时间向服务器端发送请求,服务器端收到请求并响应请求.没有 ...
- Tornado长轮询和WebSocket
Http协议是一种请求响应式协议, 不允许服务端主动向客户端发送信息. 短轮询是一种简单的实现服务端推送消息的解决方案, 客户端以一定间隔自动向服务端发送刷新请求, 服务端返回要推送的消息作为响应. ...
- 轮询、长轮询、长连接、websocket
Web端即时通讯技术:即时通讯技术简单的说就是实现这样一种功能:服务器端可以即时地将数据的更新或变化反应到客户端,例如消息即时推送等功能都是通过这种技术实现的.但是在Web中,由于浏览器的限制,实现即 ...
- python之轮询、长轮询、websocket
轮询 ajax轮询 ,ajax轮询 的原理非常简单,让浏览器隔个几秒就发送一次请求,询问服务器是否有新信息. 1.后端代码 from flask import Flask,render_templat ...
- 短连接、长连接、轮询、长轮询、WebSocket
短连接 建立连接——数据传输——关闭连接...建立连接——数据传输——关闭连接 定义:短连接是指通讯双方有数据交互时,就建立一个连接,数据发送完成后,则断开此连接,即每次连接只完成一项业务的发送. 应 ...
- 你想了解的轮询、长轮询和websocket都在这里了
日常生活中,有很多需要数据的实时更新,比如群聊信息的实时更新,还有投票系统的实时刷新等 实现的方式有很多种,比如轮询.长轮询.websocket 轮询 轮询是通过设置页面的刷新频率(设置多长时间自动刷 ...
- 轮询、长轮询、长连接、socket连接、WebSocket
轮询:客户端定时向服务器发送Ajax请求,服务器接到请求后马上返回响应信息并关闭连接. 优点:后端程序编写比较容易. 缺点:请求中有大半是无用,浪费带宽和服务器资源.(而每一次的 HTTP 请求和应答 ...
随机推荐
- Restful风格API中用put还是post做新增操作有什么区别?
Restful风格API中用put还是post做新增操作有什么区别? 转 头条面试归来,有些话想和Java开发者说!>>> 这个是华为面试官问我的问题,回来我找了很多资料,想验证这个 ...
- C++ delete仍可访问的问题
C++ delete和置为NULL 先上一段代码: class Object { public: explicit Object(int num) : m_num(num){} void functi ...
- linux中的文件类型以及查看文件类型的方法
Linux文件类型和文件的文件名所代表的意义是两个不同的概念,在linux中文件类型与文件扩展名没有关系.它不像Windows那样是依靠文件后缀名来区分文件类型的,在linux中文件名只是为了方便操作 ...
- css实现斑马线效果
文本实现斑马线效果 <style> p { font-size: 17px; line-height: 25px; background-color: antiquewhite; back ...
- jQuery进阶第二天(2019 10.10)
一.事件流程 1.事件的三要素: 事件源:发生事件的对象 事件类型:类型比如单击.双击.鼠标的移入.移除 事件处理程序: 触发事件之后做些什么,事件处理的函数 <body> <but ...
- Beta冲刺-(2/3)
这个作业属于哪个课程 https://edu.cnblogs.com/campus/xnsy/SoftwareEngineeringClass1/ 这个作业要求在哪里 https://edu.cnbl ...
- idea 2018版最新激活注册方法
1. 下载破解补丁文件,路径为:http://idea.lanyus.com/jar/JetbrainsCrack-2.7-release-str.jar 2.将补丁放在安装包的/bin路径下,如图中 ...
- 【NOIP2013模拟联考6】选课
题目 你真的认为选课是那么容易的事吗?HYSBZ的ZY同志告诉你,原来选课也会让人产生一种想要回到火星的感觉.假设你的一周有n天,那么ZY编写的选课系统就会给你n堂课.但是该系统不允许在星期i和星期i ...
- 【leetcode】581. Shortest Unsorted Continuous Subarray
题目如下: 解题思路:本题我采用的是最简单最直接最粗暴的方法,把排序后的nums数组和原始数组比较即可得到答案. 代码如下: /** * @param {number[]} nums * @retur ...
- js+css--单选按钮,自定义选中的颜色???(性别按钮,男女)
效果图: html: <div class="item"><div class="rad"></div><span c ...