随着web技术的发展,使用场景和需求也愈来愈复杂,客户端再也不知足于简单的请求获得状态的需求。实时通信愈来愈多应用于各个领域。javascript
HTTP是最经常使用的客户端与服务端的通讯技术,可是HTTP通讯只能由客户端发起,没法及时获取服务端的数据改变。只能依靠按期轮询来获取最新的状态。时效性没法保证,同时更多的请求也会增长服务器的负担。html
WebSocket技术应运而生。java
不一样于HTTP半双工协议,WebSocket是基于TCP 链接的全双工协议,支持客户端服务端双向通讯。node
WebSocket
使得客户端和服务器之间的数据交换变得更加简单,容许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只须要完成一次握手,二者之间就直接能够建立持久性的链接,并进行双向数据传输。git
在WebSocket API
中,浏览器和服务器只须要作一个握手的动做,而后,浏览器和服务器之间就造成了一条快速通道。二者之间就直接能够数据互相传送。github
WebSocket对象一共支持四个消息 onopen, onmessage, onclose和onerror。web
经过javascript能够快速的创建一个WebSocket链接:express
var Socket = new WebSocket(url, [protocol] );
复制代码
以上代码中的第一个参数url
, 指定链接的URL。第二个参数 protocol
是可选的,指定了可接受的子协议。npm
同http协议使用http://
开头同样,WebSocket协议的URL使用ws://
开头,另外安全的WebSocket协议使用wss://
开头。api
Socket.onopen = function(evt) {};
复制代码
Socket.onerror = function(evt) { };
复制代码
Socket.onclose = function(evt) { };
复制代码
Socket.onmessage = function(evt) { };
复制代码
Socket.send();
复制代码
WebSocket是跟随HTML5一同提出的,因此在兼容性上存在问题,这时一个很是好用的库就登场了——Socket.io。
socket.io封装了websocket,同时包含了其它的链接方式,你在任何浏览器里均可以使用socket.io来创建异步的链接。socket.io包含了服务端和客户端的库,若是在浏览器中使用了socket.io的js,服务端也必须一样适用。
socket.io是基于 Websocket 的Client-Server 实时通讯库。
socket.io底层是基于engine.io这个库。engine.io为 socket.io 提供跨浏览器/跨设备的双向通讯的底层库。engine.io使用了 Websocket 和 XHR 方式封装了一套 socket 协议。在低版本的浏览器中,不支持Websocket,为了兼容使用长轮询(polling)替代。
Socket.io容许你触发或响应自定义的事件,除了connect,message,disconnect这些事件的名字不能使用以外,你能够触发任何自定义的事件名称。
const socket = io("ws://0.0.0.0:port"); // port为本身定义的端口号
let io = require("socket.io")(http);
io.on("connection", function(socket) {})
复制代码
1、发送数据
socket.emit(自定义发送的字段, data);
复制代码
2、接收数据
socket.on(自定义发送的字段, function(data) {
console.log(data);
})
复制代码
1、所有断开链接
let io = require("socket.io")(http);
io.close();
复制代码
2、某个客户端断开与服务端的连接
// 客户端
socket.emit("close", {});
复制代码
// 服务端
socket.on("close", data => {
socket.disconnect(true);
});
复制代码
有时候websocket有以下的使用场景:1.服务端发送的消息有分类,不一样的客户端须要接收的分类不一样;2.服务端并不须要对全部的客户端都发送消息,只须要针对某个特定群体发送消息;
针对这种使用场景,socket中很是实用的namespace和room就上场了。
先来一张图看看namespace与room之间的关系:
服务端
io.of("/post").on("connection", function(socket) {
socket.emit("new message", { mess: `这是post的命名空间` });
});
io.of("/get").on("connection", function(socket) {
socket.emit("new message", { mess: `这是get的命名空间` });
});
复制代码
客户端
// index.js
const socket = io("ws://0.0.0.0:****/post");
socket.on("new message", function(data) {
console.log('index',data);
}
//message.js
const socket = io("ws://0.0.0.0:****/get");
socket.on("new message", function(data) {
console.log('message',data);
}
复制代码
客户端
//可用于客户端进入房间;
socket.join('room one');
//用于离开房间;
socket.leave('room one');
复制代码
服务端
io.sockets.on('connection',function(socket){
//提交者会被排除在外(即不会收到消息)
socket.broadcast.to('room one').emit('new messages', data);
// 向全部用户发送消息
io.sockets.to(data).emit("recive message", "hello,房间中的用户");
}
复制代码
终于来到应用的阶段啦,服务端用node.js
模拟了服务端接口。如下的例子都在本地服务器中实现。
先来看看服务端,先来开启一个服务,安装express
和socket.io
npm install --Dev express
npm install --Dev socket.io
复制代码
let app = require("express")();
let http = require("http").createServer(handler);
let io = require("socket.io")(http);
let fs = require("fs");
http.listen(port); //port:输入须要的端口号
function handler(req, res) {
fs.readFile(__dirname + "/index.html", function(err, data) {
if (err) {
res.writeHead(500);
return res.end("Error loading index.html");
}
res.writeHead(200);
res.end(data);
});
}
io.on("connection", function(socket) {
console.log('链接成功');
//链接成功以后发送消息
socket.emit("new message", { mess: `初始消息` });
});
复制代码
核心代码——index.html(向服务端发送数据)
<div>发送信息</div>
<input placeholder="请输入要发送的信息" />
<button onclick="postMessage()">发送</button>
复制代码
// 接收到服务端传来的name匹配的消息
socket.on("new message", function(data) {
console.log(data);
});
function postMessage() {
socket.emit("recive message", {
message: content,
time: new Date()
});
messList.push({
message: content,
time: new Date()
});
}
复制代码
核心代码——message.html(从服务端接收数据)
socket.on("new message", function(data) {
console.log(data);
});
复制代码
实时通信效果
客户端所有断开链接
某客户端断开链接
namespace应用
加入房间
离开房间
npm install socket.io-client
const socket = require('socket.io-client')('http://localhost:port');
componentDidMount() {
socket.on('login', (data) => {
console.log(data)
});
socket.on('add user', (data) => {
console.log(data)
});
socket.on('new message', (data) => {
console.log(data)
});
}
复制代码
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cache-Control: no-cache
Connection: Upgrade
Cookie: MEIQIA_VISIT_ID=1IcBRlE1mZhdVi1dEFNtGNAfjyG; token=0b81ffd758ea4a33e7724d9c67efbb26; io=ouI5Vqe7_WnIHlKnAAAG
Host: 0.0.0.0:2699
Origin: http://127.0.0.1:5500
Pragma: no-cache
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
Sec-WebSocket-Key: PJS0iPLxrL0ueNPoAFUSiA==
Sec-WebSocket-Version: 13
Upgrade: websocket
User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1
复制代码
请求包说明:
应答包说明:
Connection: Upgrade
Sec-WebSocket-Accept: I4jyFwm0r1J8lrnD3yN+EvxTABQ=
Sec-WebSocket-Extensions: permessage-deflate
Upgrade: websocket
复制代码
EIO: 3
transport: websocket
sid: 8Uehk2UumXoHVJRzAAAA
复制代码
WebSocket协议使用帧(Frame)收发数据,在控制台->Frames中能够查看发送的帧数据。
其中帧数据前的数字表明什么意思呢?
这是 Engine.io协议,其中的数字是数据包编码:
<Packet type id> [<data>]
0 open——在打开新传输时从服务器发送(从新检查)
1 close——请求关闭此传输,但不关闭链接自己。
2 ping——由客户端发送。服务器应该用包含相同数据的乓包应答
客户端发送:2probe探测帧
3 pong——由服务器发送以响应ping数据包。
服务器发送:3probe,响应客户端
4 message——实际消息,客户端和服务器应该使用数据调用它们的回调。
5 upgrade——在engine.io切换传输以前,它测试,若是服务器和客户端能够经过这个传输进行通讯。若是此测试成功,客户端发送升级数据包,请求服务器刷新其在旧传输上的缓存并切换到新传输。
6 noop——noop数据包。主要用于在接收到传入WebSocket链接时强制轮询周期。
实例
以上的截图是上述例子中数据传输的实例,分析一下大概过程就是:
为了知道Client和Server连接是否正常,项目中使用的ClientSocket和ServerSocket都有一个心跳的线程,这个线程主要是为了检测Client和Server是否正常连接,Client和Server是否正常连接主要是用ping pong流程来保证的。
该心跳按期发送的间隔是socket.io默认设定的25m,在上图中也可观察发现。该间隔可经过配置修改。