weroll 封装了 socket.io 库来实现Websocket长连接, 使用 weroll/web/Realtime 可以轻松实现基于Websocket的实时数据通讯.
Realtime 的基本使用方法如下:
var Realtime = require("weroll/web/Realtime");
/* 创建Realtime实例, 建立Websocket服务 */
var config = {
port: 3001,
debug:true,
allowGuest:true,
shakehand:false,
/* compress:true, */
/* shakehandTimeout:5000, */
/* enable cluster
cluster:{
enable:true,
redis:{
prefix:"WEBSOCKET_PREFIX",
"*":{
host:"127.0.0.1",
port:6379,
option: { auth_pass:"redis password" }
}
}
}
*/
};
Realtime.createServer(config).start();
/* 侦听客户端连接成功 */
Realtime.on("connection", function(socket) {
socket.on("hello", function(data) {
console.log('receive "hello" message from client: ', data);
socket.emit("bye", { "msg":"game over" });
});
});
客户端可以使用 socket.io-client 进行连接, 详细请看官方文档.
Realtime配置参数详细说明如下:
Option | Description |
port | 端口号 |
debug | 是否开启debug模式, 开启后可以看到Realtime的log. 如果不设置, 则使用global.VARS.debug. |
allowGuest | 是否允许游客匿名连接, true表示连接需要进过服务器握手和权限验证。默认是false |
shakehand | 是否需要握手, true表示客户端在连接后需要发送握手请求。默认是false |
shakehandTimeout | 设置握手超时时间, 默认是15秒, 意思是如果客户端在连接后超过指定时间没有完成握手, 服务器将自动断开连接 |
compress | 是否开启数据压缩, 默认是false. 开启之后, Realtime 会用 msgpack 对JSON数据进行压缩, 然后以Buffer形式和客户端通讯. |
cluster | 集群配置:
enable - 是否开启集群redis - 配置集群连接的Redis服务 redis.prefix - Redis中的键值前缀, 默认是realtime redis.* - Redis服务器连接配置 |
Realtime事件列表如下:
Event | Description |
start | 当start方法执行时抛出该事件, 侦听函数将得到require("socket.io")()返回的对象. |
connection | 当与客户端建立连接时, 侦听函数将得到客户端连接socket对象, 请参考官方文档. |
disconnect | 当与客户端连接断开时, 得到客户端连接socket对象. |
shakeHandStart | 当接到客户端握手请求时, 得到客户端连接socket对象. |
shakeHandComplete | 当客户端握手请求完成处理时, 得到客户端连接socket对象和握手是否成功的布尔值:
|
shakeHandSuccess | 当成功与客户端握手时, 得到客户端连接socket对象. 注意: 即使连接配置中shakehand设置为false, shakeHandSuccess事件也一样会触发. |
shakeHandFail | 当客户端握手失败或错误时, 得到客户端连接socket对象. |
通常情况下, 我们希望客户端需要经过授权才能连接Websocket, Realtime和WebApp及APIServer一样, 默认使用weroll/model/Session进行权限验证. Realtime通过握手请求进行权限验证, 握手成功之后, 客户端则可以继续使用连接, 否则服务器将强行断开连接.
Realtime的握手请求需要客户端在连接之后, 发送$init消息, 并附带userid, token和tokentimestamp数据(请参考Authorization文档), 示例代码如下:
/* client side */
<script type="text/javascript" src="js/socket.io.min.js"></script>
<script>
var sess = { userid:USER_ID, token:TOKEN, tokentimestamp:TOKEN_TIMESTAMP };
var socket = io("http://localhost:3001");
socket.on("connect", function() {
console.log('connected to ' + url);
console.log('start shakehand with session: ', sess);
//发送握手请求
socket.emit('$init', { _sess:sess });
});
//侦听握手请求处理结果
socket.on("$init", function(data) {
//握手成功
//data = { clientID:客户端连接的UUID }
socket.clientID = data.clientID;
console.log('shakehand success. clientID: ' + socket.clientID);
});
socket.on("disconnect", function() {
//如果握手失败或超过指定时间没有进行握手, 连接将被强行断开
socket.clientID = undefined;
console.log('disconnected');
});
</script>
握手成功之后, 服务器会返回$init消息, 并得到clientID. clientID是客户端连接的UUID, 如果服务端不需要权限验证, 在握手之后, clientID等于socket.id; 如果需要权限验证, 握手之后, clientID等于用户的userid.
如果服务端设置allowGuest = false, shakehand = false, 则客户端连接后不需要发送$init消息进行握手, 但是服务端依然会推送$init消息给客户端.
Realtime初始化时, 会得到一个weroll/net/Websocket对象的实例, 默认会将此实例注册到全局环境中, 因此在使用Realtime时, 不需要再用require进行导入.
如果你需要在一个项目中建立多个Websocket服务器, 可以自行维护Websocket对象池.
var Websocket = require("weroll/net/Websocket");
var Realtime = require("weroll/web/Realtime");
//server1被注册成为全局对象
var server1 = Realtime.createServer({ port:3001 });
//server1 is an instance of weroll/net/Websocket
server1.start();
console.log(server1 instanceof Websocket); //echo true
console.log(global.Realtime instanceof Websocket); //echo true
console.log(server1 === global.Realtime); //echo true
//设置第二个参数为true, 不让它注册成为全局对象
var server2 = Realtime.createServer({ port:3002 }, true);
//server2 is an instance of weroll/net/Websocket
server2.start();
/* somewhere */
//直接使用Realtime, 不需要require导入
Realtime.on("connection", function(socket) {
//your codes
});
一些常见的业务场景和示例代码:
/* 侦听客户端连接和客户端消息 */
Realtime.on("shakehandSuccess", function(socket) {
//侦听客户端发送的hello类型的消息
socket.on("hello", function(data) {
console.log('receive "hello" message from client: ', data);
//给客户端发送消息
socket.emit("bye", { "msg":"game over" });
});
});
/* 更简洁的消息处理方式 */
/* client side */
io.emit("m", [ "hello", { name:"Jay" } ]);
/* server side */
Realtime.on("hello", function(socket, data) {
console.log('receive "hello" message from client: ', data);
});
/* socket.helper对象 */
/* 加入房间 */
//callback
socket.helper.enterRoom(roomID, function(err) {
err && console.error(err);
});
//promise
socket.helper.enterRoom(roomID).then(function() {
//do something after enter room
}).catch(function(err) {
err && console.error(err);
});
//async & await
async function() {
await socket.helper.enterRoom(roomID);
}
/* 离开房间 */
socket.helper.leaveRoom(roomID);
/* 在房间中广播消息 */
socket.helper.broadcastToRoom(roomID, event, data);
/* 在房间中广播消息, 但不发送给socket本身 */
socket.helper.broadcastToRoomWithoutSender(roomID, event, data);
/* 广播消息给所有人 */
socket.helper.broadcast(event, data);
/* 广播消息给所有人, 除了socket本身 */
socket.helper.broadcastWithoutSender(event, data);
/* 给某个客户端发送消息 */
//如果同一个用户在多个客户端设备进行连接, 则此消息会发送给此用户的所有连接客户端
socket.helper.sendTo(clientID, event, data);
默认Realtime不会开启数据压缩, 需要在配置中设定 compress: true:
var config = {
port: 3001,
compress:true
};
Realtime.createServer(config).start();
开启之后, Realtime 会用 msgpack 对JSON数据进行压缩, 然后以Buffer形式和客户端通讯. 客户端需要使用msgpack库来进行解压缩, 得到原始的JSON数据。
/* client side */
<script type="text/javascript" src="js/msgpack.min.js"></script>
<script type="text/javascript" src="js/socket.io.min.js"></script>
<script>
//对数据进行解压处理
var parseData = function(data) {
if (data instanceof ArrayBuffer) data = msgpack.decode(new Uint8Array(data));
return data;
}
var sess = { userid:USER_ID, token:TOKEN, tokentimestamp:TOKEN_TIMESTAMP };
var socket = io("http://localhost:3001");
socket.on("connect", function() {
console.log('connected to ' + url);
console.log('start shakehand with session: ', sess);
//发送握手请求
socket.emit('$init', { _sess:sess });
});
//侦听握手请求处理结果
socket.on("$init", function(data) {
//握手成功
data = parseData(data);
//data = { clientID:客户端连接的UUID }
socket.clientID = data.clientID;
console.log('shakehand success. clientID: ' + socket.clientID);
});
//侦听其他消息
socket.on("chat", function(data) {
data = parseData(data);
console.log('chat message: ', data.msg);
});
</script>