基于HTTP协议
半双工
轮询:
Polling: 定时请求,服务器端在收到请求后,不论是否有数据更新,都直接进行响应缺点是这种方式由于需要不断的建立 http 连接,严重浪费了服务器端和客户端的资源、网络带宽。当用户增加时,服务器端的压力就会变大,这是很不合理 的。。
var xhr = new XMLHttpRequest();
setInterval(function(){
xhr.open('GET','/user');
xhr.onreadystatechange = function(){ };
xhr.send();
},1000)
LongPolling: (1)先由Browser发来请求,若有新数据要传,立即响应,Browser收到响应,再发送HTTP;(2)若无更新数据,WebServer保持住请求,等有数据来再去响应,若一直无数据来,则请求过期,Browser收到过期响应,JS处理重新请求。在某种程度上减少了网络带宽和CPU利用率等问题。缺点在于,连接挂起也 会导致资源的浪费。
function ajax(){
var xhr = new XMLHttpRequest();
xhr.open('GET','/user');
xhr.onreadystatechange = function(){ ajax(); };
xhr.send();
}
SSE(长连接,Server-Sent Events)
服务器使用
流信息向客户端推送信息。。
严格地说,http 协议无法做到服务器主动推送信息。但是,有一种变通方法,就是服务器向客户端声明,接下来要发送的是流信息。也就是说,发送的不是一次性的数据包,而是一个数据流,会连续不断地发送过来。这时,客户端不会关闭连接,会一直等着服务器发过来的新的数据流,视频播放就是这样的例子。(单向通信!)
基于WebSocket协议
全双工
WebSocket
WebSocket 是一个
全双工的协议,也就是通信双方是平等的,可以相互发送消息,是二进制帧结构,虽然有帧,但并没有http2式的流结构,也就不存在多路复用。而 SSE 的 方式是单向通信的,只能由服务器端向客户端推送信息,如果客户端需要发送信息就是属于下一个 http 请求了。相互沟通的 Header 很小,大概只有2Bytes
缺点是在服务器端的配置比较复杂。
Socket.io:
- 是websocket的父集,应用层协议;
- 基于事件双向实时通信;
- 建立于tcp之上,建立连接后传输阶段不再需要http;webSocket没有使用TCP的“IP+PORT”,而是沿用URI格式,开头的协议名是
ws、wss
借助http完成握手:
GET
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Key: base64的16字节随机数,作为认证密钥
Sec-WebSocket-Version: 协议版本号,当前必须是13响应报文:
101 Switching Protocals
Sec-Websocket-Accept: Sec-Websocket-Key + 专用UUID(258EAFA5-E914-47DA-95CA-C5AB0DC85B11),在计算SHA-1。
四种Web即时通信技术比较
- 从兼容性角度考虑,短轮询>长轮询>长连接SSE>WebSocket;
- 从性能方面考虑,WebSocket>长连接SSE>长轮询>短轮询。
Socket.io
socket.io是一个跨浏览器支持WebSocket的实时通讯的JS。
Socket.io支持及时、双向、基于事件的交流,可在不同平台、浏览器、设备上工作,可 靠性和速度稳定。最典型的应用场景如:
- 实时分析:将数据推送到客户端,客户端表现为实时计数器、图表、日志客户。
- 实时通讯:聊天应用
- 二进制流传输:socket.io支持任何形式的二进制文件传输,例如图片、视频、音频 等。
- 文档合并:允许多个用户同时编辑一个文档,并能够看到每个用户做出的修改
Socket.io实际上是WebSocket的父集,Socket.io封装了WebSocket和轮询等方法,会根据情况选择方法来进行通讯。
Socket.io将WebSocket和Polling机制以及其它的实时通信方式封装成通用的接口,并在 服务端实现了这些实时机制相应代码。这就是说,WebSocket仅仅是Socket.io实现实时 通信的一个子集,那么Socket.io都实现了Polling中那些通信机制呢?
- Adobe Flash Socket
大部分PC浏览器都支持的Socket模式,不过是通过第三方嵌入到浏览器,不在W3C 规范内,可能将逐步被淘汰。况且,大部分手机浏览器并不支持此种模式。\ - AJAX Long Polling
定时向服务端发送请求,缺点是给服务端带来压力并出现信息更新不及时的现象。 - AJAX multipart streaming
在XMLHttpRequest对象上使用某些浏览器支持的multi-part标志,AJAX请求被发送给 服务端并保持打开状态(挂起状态),每次需要向客户端发送信息,就寻找一个挂起 的HTTP请求响应给客户端,并且所有的响应都会通过统一连接来写入。 - Forever Iframem
永存的Iframe设计了一个置于页面中隐藏的iframe标签,该标签的src属性指向返回服 务端时间的Servlet路径。每次在事件到达时,Servlet写入并刷新一个新的Script标 签,该标签内部带有JS代码,iframe的内容被附加上script标签,标签中的内容就会得 到执行。这种方式的缺点是接收数据都是由浏览器通过HTML标签来处理的,因此无 法知道连接何时在哪一端被断开,而且iframe标签在浏览器中将被逐步取消。 - JSONP Polling
JSONP轮询基本与HTTP轮询一样,不同之处则是JSONP可发出跨域请求。
时提供了服务端和客户端的API
服务端绑定HTTP服务器实例
服务端socket.io必须绑定一个 http.Server 实例,因为WebSocket协议是构建在HTTP协 议之上的,所以在创建WebSocket服务时需调用HTTP模块并调用其下 createServer() 方 法,将生成的server作为参数传入socket.io。
绑定 http.Server 可使用隐式绑定和显式绑定:
- 隐式绑定
socket.io内部实例化并监听 http.Server ,通过实例化时传入端口或者在实例化后调用 listen 或 attach 函数进行隐式绑定。
// 实例化时传入端口
require('socket.io')(3000)
// 通过listen或attach函数绑定
let io = require('socket.io')
io.listen(3000);
// io.attach(3000);
- 显式绑定
// 实例化时绑定
let httpServer = require('http').Server();
let io = require('socket.io')(httpServer);
httpServer.listen(3000);
//通过listen或attach绑定
let httpServer = require('http').Server();
let io = require('socket.io')();
io.listen(httpServer);
// io.attach(httpServer);
httpServer.listen(3000);
- express
let app = require('express');
let httpServer= require('http').Server(app);
let io = require('socket.io')(httpServer);
app.listen(3000);
- koa
let app = require('koa')();
let httpServer = require('http').Server(app.callback());
let io = require('socket.io')(httpServer);
app.listen(3000);
建立连接
当服务端和客户端连接成功时,服务端会监听到 connection 和 connect 事件,客户端会监 听到 connect 事件,断开连接时服务端对应到客户端的socket与客户端均会监听到disconcect 事件
/*服务端*/
// 服务端绑定HTTP服务器实例
let httpServer = require('http').Server();
let io = require('socket.io')(httpServer);
httpServer.listen(3000);
// 服务端监听连接状态:io的connection事件表示客户端与服务端成功建立连接,它接收一个回调函数,
io.on('connection', (socket)=>{
console.log('client connect server, ok!');
// io.emit()方法用于向服务端发送消息,
// 参数1表示自定义的数据名,参数2表示需要配合事件传入的
io.emmit('server message', {msg:'client connect server success'});
// socket.broadcast.emmit()表示向除了自己以外的客户端发送消息
socket.broadcast.emmit('server message', {msg:'broadcast'});
// 监听断开连接状态:socket的disconnect事件表示客户端与服务端断开连接
socket.on('disconnect', ()=>{
console.log('connect disconnect');
});
// 与客户端对应的接收指定的消息
socket.on('client message', (data)=>{
cosnole.log(data);
// hi server
});
socket.disconnect();
});
/*客户端*/
<script src="http://cdn.socket.io/stable/socket.io.js"></script>
<script>
// socket.io引入成功后,可通过io()生成客户端所需的socket对象。
let socket = io('http://127.0.0.0:3000');
// socket.emmit()用户客户端向服务端发送消息,
// 服务端与之对应的是socket.on()来接收信息。
socket.emmit('client message', {msg:'hi, server'});
// socket.on()用于接收服务端发来的消息
socket.on('connect', ()=>{
console.log('client connect server');
});
socket.on('disconnect', ()=>{
console.log('client disconnect');
});
</script>