Websocket


Realtime

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 集群配置:
cluster:{
    enable:true,
    redis:{
        prefix:"WEBSOCKET_PREFIX",
        "*":{
            host:"127.0.0.1",
            port:6379,
            option: { auth_pass:"redis password" }
        }
    }
}
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对象和握手是否成功的布尔值:
Realtime.on("shakeHandComplete", (socket, result)=>{
    console.log(result); //echo true or false
})
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>