node.js基于socket.io快速实现一个实时通讯应用


Posted in Javascript onApril 23, 2019

随着web技术的发展,使用场景和需求也越来越复杂,客户端不再满足于简单的请求得到状态的需求。实时通讯越来越多应用于各个领域。

HTTP是最常用的客户端与服务端的通信技术,但是HTTP通信只能由客户端发起,无法及时获取服务端的数据改变。只能依靠定期轮询来获取最新的状态。时效性无法保证,同时更多的请求也会增加服务器的负担。

WebSocket技术应运而生。

WebSocket概念

不同于HTTP半双工协议,WebSocket是基于TCP 连接的全双工协议,支持客户端服务端双向通信。

WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。

WebSocket API中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。

node.js基于socket.io快速实现一个实时通讯应用

实现

原生实现

WebSocket对象一共支持四个消息 onopen, onmessage, onclose和onerror。

建立连接

通过javascript可以快速的建立一个WebSocket连接:

var Socket = new WebSocket(url, [protocol] );

以上代码中的第一个参数url, 指定连接的URL。第二个参数 protocol是可选的,指定了可接受的子协议。

同http协议使用http://开头一样,WebSocket协议的URL使用ws://开头,另外安全的WebSocket协议使用wss://开头。

当Browser和WebSocketServer连接成功后,会触发onopen消息。

Socket.onopen = function(evt) {};

如果连接失败,发送、接收数据失败或者处理数据出现错误,browser会触发onerror消息。

Socket.onerror = function(evt) { };

当Browser接收到WebSocketServer端发送的关闭连接请求时,就会触发onclose消息。

Socket.onclose = function(evt) { };

收发消息

当Browser接收到WebSocketServer发送过来的数据时,就会触发onmessage消息,参数evt中包含server传输过来的数据。

Socket.onmessage = function(evt) { };

send用于向服务端发送消息。

Socket.send();

socket

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)替代。

node.js基于socket.io快速实现一个实时通讯应用

API文档

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) {})

消息收发

一、发送数据

socket.emit(自定义发送的字段, data);

二、接收数据

socket.on(自定义发送的字段, function(data) {
    console.log(data);
  })

断开连接

一、全部断开连接

let io = require("socket.io")(http);
  io.close();

二、某个客户端断开与服务端的链接

// 客户端
  socket.emit("close", {});
// 服务端
  socket.on("close", data => {
    socket.disconnect(true);
  });

room和namespace

有时候websocket有如下的使用场景:1.服务端发送的消息有分类,不同的客户端需要接收的分类不同;2.服务端并不需要对所有的客户端都发送消息,只需要针对某个特定群体发送消息;

针对这种使用场景,socket中非常实用的namespace和room就上场了。

先来一张图看看namespace与room之间的关系:

node.js基于socket.io快速实现一个实时通讯应用

namespace

服务端

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);
  }

room

客户端

//可用于客户端进入房间;
  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,房间中的用户");   
  }

用socket.io实现一个实时接收信息的例子

终于来到应用的阶段啦,服务端用node.js模拟了服务端接口。以下的例子都在本地服务器中实现。

服务端

先来看看服务端,先来开启一个服务,安装expresssocket.io

安装依赖

npm install --Dev express
npm install --Dev socket.io

构建node服务器

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);
  });

效果

实时通讯效果

node.js基于socket.io快速实现一个实时通讯应用

客户端全部断开连接

node.js基于socket.io快速实现一个实时通讯应用

某客户端断开连接

node.js基于socket.io快速实现一个实时通讯应用

namespace应用

node.js基于socket.io快速实现一个实时通讯应用

加入房间

node.js基于socket.io快速实现一个实时通讯应用

离开房间

node.js基于socket.io快速实现一个实时通讯应用

框架中的应用

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)
    });
  }

分析webSocket协议

Headers

node.js基于socket.io快速实现一个实时通讯应用

请求包

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

请求包说明:

  • 必须是有效的http request 格式;
  • HTTP request method 必须是GET,协议应不小于1.1 如: Get / HTTP/1.1;
  • 必须包括Upgrade头域,并且其值为“websocket”,用于告诉服务器此连接需要升级到websocket;
  • 必须包括”Connection” 头域,并且其值为“Upgrade”;
  • 必须包括”Sec-WebSocket-Key”头域,其值采用base64编码的随机16字节长的字符序列;
  • 如果请求来自浏览器客户端,还必须包括Origin头域 。 该头域用于防止未授权的跨域脚本攻击,服务器可以从Origin决定是否接受该WebSocket连接;
  • 必须包括“Sec-webSocket-Version”头域,是当前使用协议的版本号,当前值必须是13;
  • 可能包括“Sec-WebSocket-Protocol”,表示client(应用程序)支持的协议列表,server选择一个或者没有可接受的协议响应之;
  • 可能包括“Sec-WebSocket-Extensions”, 协议扩展, 某类协议可能支持多个扩展,通过它可以实现协议增强;
  • 可能包括任意其他域,如cookie.

应答包

应答包说明:

Connection: Upgrade
  Sec-WebSocket-Accept: I4jyFwm0r1J8lrnD3yN+EvxTABQ=
  Sec-WebSocket-Extensions: permessage-deflate
  Upgrade: websocket
  • 必须包括Upgrade头域,并且其值为“websocket”;
  • 必须包括Connection头域,并且其值为“Upgrade”;
  • 必须包括Sec-WebSocket-Accept头域,其值是将请求包“Sec-WebSocket-Key”的值,与”258EAFA5-E914-47DA-95CA-C5AB0DC85B11″这个字符串进行拼接,然后对拼接后的字符串进行sha-1运算,再进行base64编码,就是“Sec-WebSocket-Accept”的值;
  • 应答包中冒号后面有一个空格;
  • 最后需要两个空行作为应答包结束。

请求数据

EIO: 3
  transport: websocket
  sid: 8Uehk2UumXoHVJRzAAAA
  • EIO:3 表示使用的是engine.io协议版本3
  • transport 表示传输采用的类型
  • sid: session id (String)

Frames

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连接时强制轮询周期。

实例

node.js基于socket.io快速实现一个实时通讯应用

node.js基于socket.io快速实现一个实时通讯应用

以上的截图是上述例子中数据传输的实例,分析一下大概过程就是:

  • connect握手成功
  • 客户端会发送2 probe探测帧
  • 服务端发送响应帧3probe
  • 客户端会发送内容为5的Upgrade帧
  • 服务端回应内容为6的noop帧
  • 探测帧检查通过后,客户端停止轮询请求,将传输通道转到websocket连接,转到websocket后,接下来就开始定期(默认是25秒)的 ping/pong
  • 客户端、服务端收发数据,4表示的是engine.io的message消息,后面跟随收发的消息内容

为了知道Client和Server链接是否正常,项目中使用的ClientSocket和ServerSocket都有一个心跳的线程,这个线程主要是为了检测Client和Server是否正常链接,Client和Server是否正常链接主要是用ping pong流程来保证的。

该心跳定期发送的间隔是socket.io默认设定的25m,在上图中也可观察发现。该间隔可通过配置修改。

node.js基于socket.io快速实现一个实时通讯应用

参考engine.io-protocol

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
JavaScript this 深入理解
Jul 30 Javascript
JavaScript中的私有/静态属性介绍
Jul 26 Javascript
文本有关的样式和jQuery求对象的高宽问题分别说明
Aug 30 Javascript
javascript折半查找详解
Jan 26 Javascript
JS实现进入页面时渐变背景色的方法
Feb 25 Javascript
JS实现生成会变大变小的圆环实例
Aug 05 Javascript
JavaScript数据结构与算法之集合(Set)
Jan 29 Javascript
教你JS中的运算符乘方、开方及变量格式转换
Aug 09 Javascript
Node.js Buffer用法解读
May 18 Javascript
快速解决vue动态绑定多个class的官方实例语法无效的问题
Sep 05 Javascript
Vue实现购物车的全选、单选、显示商品价格代码实例
May 06 Javascript
基于Vue CSR的微前端实现方案实践
May 27 Javascript
深入浅出 Vue 系列 -- 数据劫持实现原理
Apr 23 #Javascript
vue 中 beforeRouteEnter 死循环的问题
Apr 23 #Javascript
JavaScript中十种一步拷贝数组的方法实例详解
Apr 22 #Javascript
vue watch关于对象内的属性监听
Apr 22 #Javascript
vue项目中仿element-ui弹框效果的实例代码
Apr 22 #Javascript
对于防止按钮重复点击的尝试详解
Apr 22 #Javascript
Vue render函数实战之实现tabs选项卡组件
Apr 22 #Javascript
You might like
让你的PHP同时支持GIF、png、JPEG
2006/10/09 PHP
PHP动态生成javascript文件的2个例子
2014/04/11 PHP
ThinkPHP3.1新特性之多层MVC的支持
2014/06/19 PHP
php实现CSV文件导入和导出
2015/10/24 PHP
pjblog中的UBBCode.js
2007/04/25 Javascript
IE6/7/8中Option元素未设value时Select将获取空字符串
2011/04/07 Javascript
JavaScript中setAttribute用法介绍
2013/07/20 Javascript
实例代码详解javascript实现窗口抖动及qq窗口抖动
2016/01/04 Javascript
JavaScript仿百度图片浏览效果
2016/11/23 Javascript
如何在AngularJs中调用第三方插件库
2017/05/21 Javascript
vue使用stompjs实现mqtt消息推送通知
2017/06/22 Javascript
javascript 开发之网页兼容各种浏览器
2017/09/28 Javascript
thinkjs 文件上传功能实例代码
2017/11/08 Javascript
JS常用正则表达式超全集(密码强度校验,金额校验,IE版本,IPv4,IPv6校验)
2020/02/03 Javascript
python3+PyQt5实现使用剪贴板做复制与粘帖示例
2017/01/24 Python
python定向爬取淘宝商品价格
2018/02/27 Python
利用Python yagmail三行代码实现发送邮件
2018/05/11 Python
python实现自动登录
2018/09/17 Python
css3 实现圆形旋转倒计时
2018/02/24 HTML / CSS
Wedgwood英国官方网站:英式精致骨瓷餐具、礼品与生活精品,源于1759年
2019/09/02 全球购物
NYX Professional Makeup英国官网:美国平价专业彩妆品牌
2019/11/13 全球购物
解释一下抽象方法和抽象类
2016/08/27 面试题
英文版餐饮运营管理求职信
2013/11/06 职场文书
车间统计员岗位职责
2014/01/05 职场文书
医院总经理岗位职责
2014/02/04 职场文书
入党积极分子学习两会心得体会范文
2014/03/17 职场文书
十佳少年事迹材料
2014/12/25 职场文书
给领导的感谢信范文
2015/01/23 职场文书
2015年食堂工作总结报告
2015/04/23 职场文书
员工加薪申请报告
2015/05/15 职场文书
2015国庆节感想
2015/08/04 职场文书
2016年春季运动会广播稿
2015/08/19 职场文书
2016年五一促销广告语
2016/01/28 职场文书
创业计划书之养殖业
2019/10/11 职场文书
python中pandas对多列进行分组统计的实现
2021/06/18 Python
JVM的类加载器和双亲委派模式你了解吗
2022/03/13 Java/Android