[WEB知多少]Socket.IO基本概念

很多时候我们都会在实际应用中使用 socket.io 库作为 websocket 业务场景的高级实现, 它基于包括但不限于 websocket 的各种技术, 实现了统一的跨平台的基于 HTTP 协议的双向通讯过程.

开始使用 socket.io, 需要了解几个基本概念

  1. HTTP 协议
  2. Namespace
  3. Room

HTTP 协议

之所以在此单独把 HTTP 本身再强调一遍, 是因为我觉得对于大多数普通入门级别的前端开发者来说可能会对 url 中的 “主机(host)” 和 “路径(path)” 两者没有足够清晰的认知, 会下意识的把 url 当做一个整体.

// 我们暂时不考虑端口
// {Protocol}{host}{path}
http://www.abc.com/hello

协议是 http://, 主机是 www.abc.com, 路径是 hello.

当浏览器对该地址提交 GET 请求时, 实际上是

  1. 先与主机 www.abc.com 的 80 端口 建立 网络套接字(socket)
  2. 发送 HTTP 协议请求头 GET / HTTP/1.1\n\n
  3. 理解了上边两个独立的过程, 可以明白:

浏览器访问一个 url 的资源, 本质上是通过一个已有的 socket 发送特定 HTTP 指令(GET POST 等), 并在其后携带 path 参数.

当这个与 主机 www.abc.com 的 80 端口之前通讯的 socket不存在时则建立一个新的 socket, 再发送 HTTP 报文.

这是 HTTP 的本质.

说以我们能理解:

  1. HTTP 服务是针对 主机-端口 实现的, 与路径无关
  2. Websocket 服务亦是如此.
  3. 不存在 “我们给路径 /my-webscoket-path 部署 websocket” 的说法.
  4. 给网站部署 websocket 是针对全站的, 是针对的 80 端口上的 HTTP 服务实现做扩充.

Namespace 名称空间

OK, 我们再看一下 socket.io-client 的连接动作

io.connect("ws://my-host/");
// 或者
io.connect("ws://my-host/somepath")

有了上一个小节的强调基础, 我们能够理解上述两者, 其实在连接服务器的环节是一致的, 都是尝试与主机 my-host 建立 ws 协议的握手. 只不过两者在握手时携带了不同的 path, 在 socket.io 的世界里, 我们把它称为 namespace.

Namespace 是一个人为的概念, 是用于在业务层面路由而设计的. 在 socket.io 中, 基于 namespace 可以划分出完全独立的一级分组. 一个 socket 对象一定从属于一个明确的 namespace, 如未指定则默认从属于 ‘/’.

Room 房间

与 namespace 类似, room 也是设计用于划分分组. 区别在于:

namespace 的分组规则借用了 HTTP 的 path, 可以直接在握手 url 中提现. room 则是一个 “软实现”, 基于一个已经建立的(并从属于一个namespace)ws通讯链路, 由后端从业务逻辑上划分.
后端在接受到来自前端的连接请求并正常建立好连接后, 使用 join() 函数实现.

io.on('connection', (socket)=>{
   socket.join('my-room');
}));

一个 socket 对象也一定存在于一个房间中, 如未指定则默认加入了 default 房间.

Broadcast 广播

广播动作, 从字面上很容易理解. 即将一条信息发送给多个接受者.

显然, namespace 和 room 在广播中用于限定接受广播消息的人群.

socket.io 在广播的时候, 是以 namespace 作为单位的. 同一个广播限定于一个 namespace 内部. 广播在没有明确限定 room 的时候, 不会考虑 room 概念. 以及, 没有申明 namespace 的时候广播给 ‘/’.

// 所有连接到 '/' 的用户都会收到消息 
socketio.send("Some message.");
// 显示声明了 namsepace
socketio.of('/my-namespace').send('some message to exist namespace');
// 显示声明了 namsepace 和 room
socketio.of('/my-namespace').in('my-room').send('some message to exist namespace and room');
// 注意这里是使用某一个 socket 连接实例进行广播, 将发送给和该实例同一个 namespace
socket.broadcast.emit('some message to my namespace')


玩的愉快 : )