Socket.IO 实现了基于事件的实时双向通信。
可以工作在任意平台、浏览器或设备,专注于可靠性和速度。
配方旨在记录作者的实践过程,以及备注一些常用的api使用方法。示例
后台服务
我们使用了基于 Node.JS 的 web 框架 express
。 请确保安装了 [Node.JS](https://nodejs.org/)
。
后台依赖如下:
1 | "dependencies": { |
要保存 dependencies
信息, 可以用 npm install --save express
;
然后新建一个 index.js
文件来创建应用.
1 | var express = require('express'); |
接下来就是主角登场了。。。
1 | var io = require('socket.io')(server); |
我们通过传入 http
(HTTP 服务器) 对象初始化了 socket.io
的一个实例。 然后监听 connection
事件来接收 sockets
, 并将连接信息打印到控制台。当然有连接当然就有挂断了。
1 | //监听客户端连接,回调函数会传递本次连接的socket |
connection
事件很重要,以后所有的事件回送都建立在连接成功的基础上。
Socket.IO 的核心理念就是允许发送、接收任意事件和任意数据。任意能被编码为 JSON
的对象都可以用于传输。二进制数据 也是支持的。
接下来的目标就是让服务器将消息发送给其他用户。
要将事件发送给每个用户,Socket.IO 提供了 io.emit 方法:
1 | io.emit('some event', { for: 'everyone' }); |
要将消息发给除特定 socket 外的其他用户,可以用 broadcast 标志:
1 | io.on('connection', function(socket){ |
前端页面
前端页面要做的就很简单了;
在 index.html 的 </body>
标签中添加如下内容:
1 | <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.0.4/socket.io.js"></script> |
ES6、AMD加载方法如下:
1 | const io = require('socket.io-client'); |
这样就加载了 socket.io-client
。 socket.io-client
暴露了一个 io
全局变量,然后连接服务器。
请注意我们在调用 io()
时没有指定任何 URL,因为它默认将尝试连接到提供当前页面的主机。
现在前后端已经联通,node控制台将会出现如下信息:1
2
3
4
5
6a connected
disconnected
a connected
disconnected
a connected
disconnected
多次刷新会出现上面的信息。
现在前端页面与后台的交互方法如下:
1 | $(function () { |
就这样就实现了与后台的数据聊天交互。
进阶实例
目标我想实现一对一的交流、或者只跟后台的交互,不要跟其它用户交流。
思路,只有一个人的群聊?后台知道前台的唯一id?
首先第个页面要有个唯一id,那先用地址栏传参数吧。
1 | function getParam(name, href) { |
使用 connect
事件监听是否连接成功
1 | console.log(socket.id); // undefined |
发现有 socket.id
了就用不着地址栏传参数了吧
不过传参数也是一种方法,前台监听这个id,后台emit这个id
与后台交流
1 | // 后台server.js |
群聊
1、方法一,借助socket,设置groupid
1 | // 后台server.js |
2、方法二,借助io.of(‘/room1’)
1 | // 后台server.js |
但是这个方法有个限制,只能后台控制群组名称,不能前台自定义设置(个人粗浅理解,如有错误还请指正)
Socket.IO API
服务端API
1 | var path = require('path'); |
另外,Socket.IO提供了4个配置的API:io.configure, io.set, io.enable, io.disable。
其中io.set对单项进行设置,io.enable和io.disable用于单项设置布尔型的配置。
io.configure 可以让你对不同的生产环境(如devlopment,test等等)配置不同的参数。
new Server
1、new Server(httpServer[, options])
- httpServer (http.Server) 需要绑定的
server
. - options (Object)
- path (String): 要捕获的路径的名称 (
/socket.io
) - serveClient (Boolean): 是否为客户端文件提供服务 (
true
) - adapter (Adapter): 要使用的适配器。默认为带有套接字的适配器,是基于内存的
- origins (String): 允许的域名,默认:
*
- parser (Parser): 要使用的解析器,要与前端设置的一样.
- path (String): 要捕获的路径的名称 (
1 | const io = require('socket.io')(); |
2、new Server(port[, options])
3、new Server(options)
server.sockets
默认命名空间为: /
server.serveClient
server.serveClient([value])
value
(Boolean)- Returns
Server|Boolean
如果值为true
,连接的服务器将为客户端文件提供服务,此方法在调用attach
后无效.
如果未提供参数, 此方法将返回当前值。
1 | // 设置`server`的 `serveClient` 选项 |
server.path
server.path([value])
value
(Boolean)- Returns
Server|Boolean
个人理解主要是命名空间与前台配合,相当于分组
1 | const io = require('socket.io')(); |
server.adapter([value])
server.origins
1、[value], string
设置允许的域名,默认所有的都允许(*
)。
2、fn
提供一个回调方法包括两个参数,origin:String
和 callback(error, success)
,
返回一个布尔值,表示是否允许某个源访问
1 | io.origins((origin, callback) => { |
server.attach
1、server.attach(httpServer[, options])
2、server.attach(port[, options])
server.listen
1、server.listen(httpServer[, options])
2、server.listen(port[, options])
server.of(nsp)
初始化并查找一个路径的命名空间;
如果命名空间已经初始化, 它将立即返回。
1 | const adminNamespace = io.of('/admin'); |
server.close
参数:[callback]
关闭一个socket.io
的服务,当服务关闭的时候回调方法将会被调用。
1 | const Server = require('socket.io'); |
namespace.to(room)
与 namespace.in(room)
等价
只推送给客户端加入的房间
要发给多个房间,你可以多次调用 to
1 | const io = require('socket.io')(); |
namespace.emit
namespace.emit(eventName[, …args])
推送事件到已连接的客户端。两个示例:
1 | const io = require('socket.io')(); |
namespace.clients(callback)
得到已连接到当前命名空间的客户端 IDs
1 | const io = require('socket.io')(); |
一个等到当前命名空间下某个房间的所有客户端
1 | io.of('/chat').in('general').clients((error, clients) => { |
与广播一样, 默认的是默认命名空间中的所有客户端
1 | io.clients((error, clients) => { |
namespace.use(fn)
注册一个中间件,它是为每个传入的 socket
执行的函数, 并接收作为参数的socket
和一个函数, 可以选择延迟执行到下一个注册的中间件。
传递给中间件回调的 error
作为特殊的错误数据包发送到客户端。
1 | io.use((socket, next) => { |
socket.id
会话的唯一标识符,来自底层Client
。
socket.rooms
一个字符串哈希, 用于标识此客户端所位于的文件室, 按房间名称进行索引。1
2
3
4
5
6io.on('connection', (socket) => {
socket.join('room 237', () => {
let rooms = Objects.keys(socket.rooms);
console.log(rooms); // [ <socket.id>, 'room 237' ]
});
});
socket.client
socket.use
socket.send
socket.emit
socket.on(eventName, listener)
socket.once(eventName, listener)
socket.removeListener(eventName, listener)
socket.join(room[, callback])
socket.join(rooms[, callback])
socket.leave(room[, callback])
socket.to(room)
socket.in(room)
socket.compress(value)
socket.disconnect(close)
客户端API
1 | 客户端`socket.on()`监听的事件: |
io
io([url][, options])
, 始一个 socket
实例。
url
(String) (默认window.location
)options
(Object)forceNew
(Boolean) 是否重用现有连接
- 返回
Socket
初始化示例
多路复用
默认情况下,连接到不同的命名空间时使用单个连接(以最小化资源):
1 | const socket = io(); |
可以通过 forceNew
选项禁用该行为:
1 | const socket = io(); |
注意:重用同一个命名空间也将创建两个连接
1 | const socket = io(); |
用自定义 path
1 | const socket = io('http://localhost', { |
上面示例的请求网址将会是这样: localhost/myownpath/?EIO=3&transport=polling&sid=<id>
带有查询参数
1 | // 前端html 带上参数token |
带查询选项
1 | // 前端html 带上参数token |
还可以在重新连接时更新查询内容:
1 | // 前端html |
带自定义消息头
这仅在polling
参数里有效,默认是启用的。
使用websocket传输时不会附加自定义标题。
因为WebSocket握手不符合自定义标头。
(对于后台请参阅WebSocket协议RFC)
1 | // 前端html 带上参数token |
使用 websocket
传输
默认情况下,首先建立长轮询连接,然后升级为“更好”的传输(如WebSocket)。对传输要求不太高,可以跳过这个部分:
1 | // 前端html |
用自定义解析器
默认的解析器(支持Blob,File,二进制)传输时会有性能牺牲。
但可以提供自定义解析器来满足应用程序的需求。请看这里的例子。
1 | // 前端html |
Socket
socket.id
socket.id
为20位随机string,socket
实例的唯一标识符,在触发连接事件后设置, 并在重新连接事件之后更新。
1 | // 前端html |
socket.open()
同 socket.connect()
返回 Socket
, 手动打开与后台的连接。
1 | // 前端html |
它也可以用于手动重新连接:
1 | // 前端html |
socket.emit
向后台发送消息
socket.emit(eventName [,… args] [,ack]),
socket.send([… args] [,ack])二者等价
1 | socket.emit('hello', 'world'); |
该 ack
参数是可选的,用于服务器回调。
1 | socket.emit('ferret', 'tobi', (data) => { |
socket.on
注册事件监听, 不能放在connect里面,否则在联通重联时会多次注册
socket.on(eventName,callback)
1 | socket.on('news', (data) => { |
类似的方法 off
解除绑定与 on
相反,其它 once
绑定后仅可以调用一次就失效, hasListeners
暂未知道其用意
socket.compress
为后续事件的释放设置一个修饰符, 仅当该值为 true 时, 事件数据才会被压缩,在不调用 socket.compress
方法时, 默认为 true。
1 | socket.emit('an event', { some: 'data' }); // 等价于 socket.compress(true).emit('', '') |
socket.close
手动关闭 socket
实例连接,等价于 socket.disconnect()
不太擅长写作,组织有些混乱。。。费了老大劲。。。