《 Socket.IO》 解决 WebSocket 通信!

算法云航者
• 阅读 3809

大家好呀,我是小菜~

本文主要介绍 Socket.IO

微信公众号已开启,小菜良记,没关注的同学们记得关注哦!

在介绍 Socket.IO 之前, 我们先考虑一个问题, 如果这个时候有个需求, 类似实现人工客服的功能该如何实现?
《 Socket.IO》 解决 WebSocket 通信!

在线客服,需求理解起来很简单,就相当于一个 web 的聊天页面,也就是客户端能够 即时拉取到服务端的响应

当然, 作为接口工程师, 这并不是一个很难解决的问题, 我们可以提供一个获取聊天记录的接口, 通过该接口我们可以获取到

对方已经发送到消息. 那么问题又来了, 如何保证能够 即时 的获取到聊天记录呢? 想必这也不是问题, 前端可以通过定时器

的方式, 将间隔时间缩短到 100 毫秒, 这样子就已经实现了近实时的获取消息

setInterval(function () { 
    // do something
},100)

当我们写完以上代码上线后, 却通过监控可以发现, 上线后的服务器指标明显比之前有所提升

服务器是十分珍贵的资源, 那么为什么会发生这种情况呢? 回过头一想, 会发生这种情况也无可厚非, 每 100 毫秒就请求一

次后端, 如果有聊天记录产生, 那么这种请求就认为是有意义的, 但如果长时间未聊天, 每次请求返回都是空记录, 那么这种

频繁请求就是无意义的. 频繁请求会使服务器压力增大, 并且浪费带宽流量.

那么有没有别的方式可以解决?

我们也许可以使用 SSE 方式, SSE 并不是一个什么比较新颖的概念, 它出现的时间也很早

SSE 全称 Server-Sent Events,指的是网页自动获取来自服务器的更新,也就是自动化获取服务端推送至网页的数据,这是一个 H5 的属性,除了 IE,其他标准浏览器基本都兼容

这种方式不需要客户端定时去获取,而是服务端向客户端声明要发送流信息,然后连续不断地发送过来

《 Socket.IO》 解决 WebSocket 通信!
尽管这种方式不需要定时轮询, 但是它只能单工通信,建立连接后,只能由服务端发往客户端,且需要占用一个连接,如果需要客户端向服务端通信,那么需要额外再打开一个连接!

如果连接数过多会导致什么问题?

TCP 的连接数是有限的, SYN DDOS 洪水攻击, 就是利用 TCP 半连接的问题来攻击服务器

因此这也不是一种优雅的实现方式

其实到这里, 我们解决的思路已经很明确了, 就是在不浪费带宽的情况下如何让服务端将最新的消息以最快的速度发送给客

户端. 但是明显 HTTP 协议不适用, 它是会在服务端收到请求后才会做出回应. 因此为了解决这个问题, 那么就需要就需要讲

到一种通信协议, 那就是 WebSocket

WebSocket 是一种计算机通信协议,通过单个 TCP 连接提供全双工通信信道。

建立一个 WebSocket 连接,客户端会发送一个 WebSocket 握手请求,服务器为此返回一个 WebSocket 握手响应,如下

图所示。

《 Socket.IO》 解决 WebSocket 通信!
相比于传统 HTTP 的每次 请求-应答 都要客户端与服务端建立连接的模式, websocket 是一种 长连接 的模式, 一旦建

立起 websocekt 连接, 除非 client 或者 server 中有一端主动断开连接, 否则每次数据传输之前都不需要 HTTP 那样请求数

客户端请求

Upgrade: websocket
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
  • Upgrade 是为了表明这是一个 websocekt 类型请求, 意在告诉 server 需要将通信协议切换到 websocekt
  • Sec-WebSocket-Key是 client 发送的一个 base64 编码的密文 ,要求服务器用 Sec-WebSocket-Accept 头部中的密钥散列作为响应。这是为了防止缓存代理重新发送以前的 WebSocket 对话,并且不提供任何身份验证、隐私或完整性。

    服务端响应

Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat

握手从 HTTP 请求/响应开始,允许服务器在同一端口处理 HTTP 连接和 WebSocket 连接。一旦连接建立起来,通信就

切换到不符合 HTTP 协议的双向二进制协议。

《 Socket.IO》 解决 WebSocket 通信!

到这里其实方案已经出来了, 但是我们这篇文章的标题确实 Socket.IO, 既然都有了 Websocket, 为什么我们讲的是 Socket.IO ?

Socket.IO

在大家往下看之前先清楚这么一个观点:

Socket.IO 不是替代, 而是升级

Socket.IO 是一个库, 说到库其实我们都不陌生, 库是对已有的功能进行封装, 没错, 它是构建在 WebSocket 协议之上, 并提供额外的保证, 既然它是构建在 websocekt 之上, 说明它同样具有客户机与服务器之间延迟通信的功能.

Socket.IO可用于实现以下几种通信方式:

  • HTML 5中的WebSocket通信
  • 可在Flash中使用的WebSocket通信
  • XHR轮询
  • JSONP轮询
  • Forever Iframe

Socket.IO确保在实现这些通信方式时,客户端与服务器端可以使用相同的API。并具备以下特性:

  • HTTP 长轮询回退

如果不能建立 WebSocket 连接,连接将退回到 HTTP 长轮询。

  • 自动重新连接

在某些特定条件下,服务器和客户端之间的 WebSocket 连接可能会被中断,双方都不知道链接的断开状态。而 Socket.IO 包含一个 heartbeat 机制的原因,该机制定期检查连接的状态.当客户端最终断开连接时,它会自动重新连接,并且会出现指数级的回退延迟,以免压垮服务器

  • 数据包缓冲

当客户端断开连接时,数据包将自动缓冲,并在重新连接时发送

既然 Socket.IO 如此的美妙, 那么它该如何使用呢? 那么接下来就让我们创建一个自己的聊天室吧 !

本案例采用 NodeJS 环境搭建, 极其简单, 有条件的可以上手一试

聊天室

准备前提:

  • 确保安装了 Node.js 环境
  • 准备一个空文件夹

准备步骤很简单, 接下来我们就开始创建我们自己的聊天室

1. 创建 package.json 文件

我们在空目录下创建 package.json 文件, 内容如下:

{  
    "name": "c-chat",  
    "version": "0.0.1",  
    "description": "my first chat app",  
    "dependencies": {}
}

在当前目录执行命令 npm install express 安装web应用开发框架

2. 创建 index.js & index.html

在空目录下创建 index.js 文件, 内容如下:

const app = require('express')();
const http = require('http').Server(app);

app.get('/', (req, res) => {
   res.sendFile(__dirname + '/index.html');
});

http.listen(port, () => {
   console.log(`${port} 端口监听成功`);
});

接着创建 index.html 文件, 内容如下

<!DOCTYPE html>
<html>
 <head>
   <title>my chat</title>
   <style>
     body { margin: 0; padding-bottom: 3rem; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; }

     #form { background: rgba(0, 0, 0, 0.15); padding: 0.25rem; position: fixed; bottom: 0; left: 0; right: 0; display: flex; height: 3rem; box-sizing: border-box; backdrop-filter: blur(10px); }
     #input { border: none; padding: 0 1rem; flex-grow: 1; border-radius: 2rem; margin: 0.25rem; }
     #input:focus { outline: none; }
     #form > button { background: #333; border: none; padding: 0 1rem; margin: 0.25rem; border-radius: 3px; outline: none; color: #fff; }

     #messages { list-style-type: none; margin: 0; padding: 0; }
     #messages > li { padding: 0.5rem 1rem; }
     #messages > li:nth-child(odd) { background: #efefef; }
   </style>
 </head>
 <body>
   <ul id="messages"></ul>
   <form id="form" action="">
     <input id="input" /><button>Send</button>
   </form>
 </body>
</html>

为了测试 http 服务与我们的页面是否有效, 我们可以利用 node index.js 启动项目来验证
《 Socket.IO》 解决 WebSocket 通信!

到这里, 我们就已经能够成功访问到我们的页面, 接下来就开始通过 socket.io 来实现我们的聊天功能

3. 安装 socket.io 库

npm install socket.io 首先就需要执行以上命令来安装 socket.io

现在离目标已经实现一大半了

我们只需要修改部分内容便可以看到我们想要的效果

服务端
const { Server } = require("socket.io");
const io = new Server(server);

以上代码是为了引入 socket.io库, 并创建 websocket 服务, 然后便可以建立 socket 监听

io.on('connection', (socket) => {console.log('连接建立成功');});

在一个Socket.IO服务器创建之后,当客户端与服务器端建立连接时,触发Socket.IO服务器的connection事件,可以通过监听该事件并指定事件回调函数的方法指定当客户端与服务器端建立连接时所需执行的处理

客户端

在 index.html 页面, 我们添加以下代码来引入 socket.io.js, 并创建 socket 对象

<script src="/socket.io/socket.io.js"></script>
<script>
  var socket = io();
</script>

到这里为止就是加载 socket.io-client 所需的全部操作,该客户端公开了一个 io 全局(以及端点 GET/ socket.io/socket.io.js ) ,然后进行连接。我们可以重启下服务, 打开浏览器, 然后查看控制台结果
《 Socket.IO》 解决 WebSocket 通信!
可以看到连接已经成功建立了. 接下来就是最重要的环节了, 双方需要进行消息发送了, 在 IO 中任何可以被编码为 JSON 的对象都可以发送,并且还支持二进制数据

客户端

index.html 中需要修改的代码如下:

<script>
  var socket = io();

  var messages = document.getElementById('messages');
  var form = document.getElementById('form');
  var input = document.getElementById('input');

  form.addEventListener('submit', function(e) {
    e.preventDefault();
    if (input.value) {
      socket.emit('chat message', input.value);
      input.value = '';
    }
  });

  socket.on('chat message', function(msg) {
    var item = document.createElement('li');
    item.textContent = msg;
    messages.appendChild(item);
    window.scrollTo(0, document.body.scrollHeight);
  });
</script>

可以通过 emit 方法往服务端发送消息, 其中 chat message 为发送的目标地址

在emit方法中,使用三个参数

socket.emit(event, data, callback)

  • event参数值为一个用于指定事件名的字符串, 也就是目标主题
  • data参数值代表该事件中携带的数据,该数据将被对方接收,数据可以为一个字符串,也可以为一个对象
  • callback参数值为一个参数,用于指定一个当对方确认接收到数据时调用的回调函数
服务端

index.js 文件中需要修改的代码如下:

io.on('connection', (socket) => {
  socket.on('chat message', (msg) => {
    console.log('message: ' + msg);
  });
});

通过 socket.on() 的方式监听目标地址, 这有些类似于发布/订阅模式, 双方订阅同一个地址, 然后往这个通道中传递消息
在服务端我们同样可以使用 emit 方法往客户端发送消息, 我们可以利用 socket.emit() 进行发送

附: 完整代码

index.html

《 Socket.IO》 解决 WebSocket 通信!

index.js

《 Socket.IO》 解决 WebSocket 通信!

到这里就彻底结束了, 来吧, 伙计们, 现在重新启动项目, 然后打开两个浏览器访问 localhost:3000 地址, 来尝试和自己对话吧 !

命名空间

上面我们已经简单的实现了一个聊天室的功能, 主要利用到以下 api

  • socket.on() 监听事件
  • socket.emit() 消息发送

这两个是最基础的用法, 下面我们说一个扩展使用, 那就是命名空间

如果开发者想在一个特定的应用程序中完全控制消息与事件的发送,只需要使用一个默认的"/"命名空间就足够了。但是如果开发者需要将应用程序作为第三方服务提供给其他应用程序,则需要为一个用于与客户端连接的socket端口定义一个独立的命名空间。

在Socket.IO中,使用Socket.IO服务器对象的of方法定义命名空间,代码如下所示(代码中的io代表一个Socket.IO服务器对象)。

io.of(namespace)

下面我们看下如何使用:

  • 服务端
io.of("/chat").on("connection", (socket) => {
  // 订阅对应的主题
  socket.on("chat message", (msg) => {
    console.log("message: " + msg);
    socket.emit("chat message", msg);
  });
});
  • 客户端
var socket = io('http://localhost:3000/chats');

我们以上例子中定义了 chat 这个命名空间用于区分不同 socket 连接, 小伙伴们可以发挥想象这个可以应用到什么场景中 !

总结

SOCKET 是用来让不同电脑之间,不同进程之间互相通信的一套接口。Socket, 直译过来可以是“插座”,而在中文中往往会叫“套接字”。双方要建立连接, 首先就会申请一个 套接字 来传输消息

《 Socket.IO》 解决 WebSocket 通信!

不要空谈,不要贪懒,和小菜一起做个吹着牛X做架构的程序猿吧~点个关注做个伴,让小菜不再孤单。咱们下文见!

今天的你多努力一点,明天的你就能少说一句求人的话!

我是小菜,一个和你一起变强的男人。 💋

微信公众号已开启,小菜良记,没关注的同学们记得关注哦!

点赞
收藏
评论区
推荐文章
blmius blmius
4年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
Wesley13 Wesley13
4年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Stella981 Stella981
4年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Easter79 Easter79
4年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
4年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
4年前
PHP创建多级树型结构
<!lang:php<?php$areaarray(array('id'1,'pid'0,'name''中国'),array('id'5,'pid'0,'name''美国'),array('id'2,'pid'1,'name''吉林'),array('id'4,'pid'2,'n
Wesley13 Wesley13
4年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
4年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Python进阶者 Python进阶者
2年前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这
算法云航者
算法云航者
Lv1
谁没喝过酒尝过孤独,但却忘不了你
文章
3
粉丝
0
获赞
0