一、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请求有几点不同:

  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聊天

二、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知识、轮询、长轮询、长连接的更多相关文章

  1. php websocket-网页实时聊天之PHP实现websocket(ajax长轮询和websocket都可以时间网络聊天室)

    php websocket-网页实时聊天之PHP实现websocket(ajax长轮询和websocket都可以时间网络聊天室) 一.总结 1.ajax长轮询和websocket都可以时间网络聊天室 ...

  2. 轮询、长轮询和websocket

    一.轮询 在一些需要进行实时查询的场景下应用比如投票系统: 大家一起在一个页面上投票 在不刷新页面的情况下,实时查看投票结果 1.后端代码 from flask import Flask, rende ...

  3. 了解轮询、长轮询、长连接、websocket

    业务开发中我们往往会有一些需要即时通信的场景,比如微信扫码登录.聊天功能. 下面这四种方式都可以实现即时通信. 轮询: 浏览器通过定时器每隔一段时间向服务器端发送请求,服务器端收到请求并响应请求.没有 ...

  4. Tornado长轮询和WebSocket

    Http协议是一种请求响应式协议, 不允许服务端主动向客户端发送信息. 短轮询是一种简单的实现服务端推送消息的解决方案, 客户端以一定间隔自动向服务端发送刷新请求, 服务端返回要推送的消息作为响应. ...

  5. 轮询、长轮询、长连接、websocket

    Web端即时通讯技术:即时通讯技术简单的说就是实现这样一种功能:服务器端可以即时地将数据的更新或变化反应到客户端,例如消息即时推送等功能都是通过这种技术实现的.但是在Web中,由于浏览器的限制,实现即 ...

  6. python之轮询、长轮询、websocket

    轮询 ajax轮询 ,ajax轮询 的原理非常简单,让浏览器隔个几秒就发送一次请求,询问服务器是否有新信息. 1.后端代码 from flask import Flask,render_templat ...

  7. 短连接、长连接、轮询、长轮询、WebSocket

    短连接 建立连接——数据传输——关闭连接...建立连接——数据传输——关闭连接 定义:短连接是指通讯双方有数据交互时,就建立一个连接,数据发送完成后,则断开此连接,即每次连接只完成一项业务的发送. 应 ...

  8. 你想了解的轮询、长轮询和websocket都在这里了

    日常生活中,有很多需要数据的实时更新,比如群聊信息的实时更新,还有投票系统的实时刷新等 实现的方式有很多种,比如轮询.长轮询.websocket 轮询 轮询是通过设置页面的刷新频率(设置多长时间自动刷 ...

  9. 轮询、长轮询、长连接、socket连接、WebSocket

    轮询:客户端定时向服务器发送Ajax请求,服务器接到请求后马上返回响应信息并关闭连接. 优点:后端程序编写比较容易. 缺点:请求中有大半是无用,浪费带宽和服务器资源.(而每一次的 HTTP 请求和应答 ...

随机推荐

  1. 增强 Jupyter Notebook的功能

    增强 Jupyter Notebook的功能 Jupyter Notebook 是所有开发者共享工作的神器,它为共享 Notebooks 提供了一种便捷方式:结合文本.代码和图更快捷地将信息传达给受众 ...

  2. Neo4j清空所有数据

    两种方法: 一.用下列 Cypher 语句: match (n) detach delete n 二. 1.停掉服务: 2.删除 graph.db 目录: 3.重启服务. 原文地址:http://ne ...

  3. postman中x-www-form-urlencoded与form-data的区别

    这是W3C定义的两种不同的表格类型,如果你想发送简单的text/ASCII数据,使用x-www-form-urlencoded , 这是默认的形式. 如果你想发送非ASCII文本或者大的二进制数据,使 ...

  4. ls, dir, vdir - 列目录内容

    ls [选项] [文件名...] POSIX 标准选项: [-CFRacdilqrtu1] GNU 选项 (短格式): [-1abcdfgiklmnopqrstuxABCDFGLNQRSUX] [-w ...

  5. Django发送邮件功能

    以126邮箱为例 1 首先进126邮箱设置,开启: POP3/SMTP服务 IMAP/SMTP服务 成功开启后会获得一个授权码.   2. setting.py配置: # 配置发送邮箱 # 需要登录网 ...

  6. C#索引器3 重载

    7.索引器  重载 public class Demo { private Hashtable name = new Hashtable(); public string this[int index ...

  7. 代理层Nginx限流(降级)预案

    典型服务架构介绍 预案适用场景 监控指标 操作手册 相关文档 操作方法 配置语法 配置样例 配置解释 注意事项 典型服务架构介绍 典型的互联网服务访问链路都是分层结构的,从流量入口,到应用层,到后端资 ...

  8. Revolver Maps-3D地球仪网站定制

    这是个网站统计的小插件,大家可以看到那些国家,哪些城市对本网站进行了访问,很直观的一种表现方式. 这个小插件不需要你写任何代码,只需要去它官方网站定制你自己需要的代码.它的地址是:http://www ...

  9. boost::regex

    https://blog.51cto.com/liam2199/2108548 正则表达式

  10. [每日一学]apache camel|BDD方式开发apache camel|Groovy|Spock

    开发apache camel应用,最好的方式就是tdd,因为camel的每个组件都是相互独立并可测试的. 现在有很多好的测试框架,用groovy的Spock框架的BDD(行为测试驱动)是比较优秀和好用 ...