WebSocket 是一种支持客户端与服务器在单个 TCP 连接上进行全双工通信的应用层通信协议。WebSocket 使得客户端和服务器之间的数据交换变得更加简单,它允许服务端主动向客户端推送数据(HTTP 的通信只能由客户端发起)。在 WebSocket API 中,浏览器和服务器只需要一次握手,两者之间就可以创建持久性连接,并进行双向数据传输。
本项目提供了基于 Spring Boot 的 WebSocket 服务器端,基于 SockJS+STOMP 的浏览器端和基于 StompProtocolAndroid 的安卓客户端。
服务器端目前有三个接收消息的端点:
/broadcast
该接口会转发它接收到的所有消息到 /b
端点的订阅者。
/group/{groupID}
这个端点用于模拟群组。举个例子,一个客户端发送消息给 /group/1
,所有订阅了端点 /g/1
的客户端都会接收到消息。如果要改变接收消息的端点,需要同时改变 Controller 和 WebSocketConfig 的相关代码。
/chat
/chat
是用于点对点通信,用与模拟私聊。如果 Alice(userID 为 1)想和 Bob(userID 为 2)聊天,她需要发送消息到 /chat
,并且在请求体中附带相关信息(json 化的 ChatMessage):
// js code
function sendMessage() {
var message = $('#message').val();
stompClient.send('/chat', {}, JSON.stringify({
'userID': 2,
'fromUserID': 1,
'message': "Hello Bob"})
);
}
userID
是必须的,这个属性会被服务端用来判断转发的端点:
simpMessagingTemplate.convertAndSendToUser(String.valueOf(chatMessage.getUserID()), "/msg", response);
通过以上的代码,Alice 的消息会被转发到 /user/2/msg
的订阅者。如果 Bob 订阅了他自己对应的端点,他将收到消息。
如果 Alice 也想收到发给她的消息,她也应当订阅她自己(本例中 Alice 应该订阅 /user/1/msg
):
stompClient.subscribe('/user/' + 1 + '/msg', function (response) {
showResponse(JSON.parse(response.body).responseMessage);
});
这样,当 Bob 给 Alice 发送消息的时候,Alice 会成功收到。
有时候,我们会希望我们的端点只供认证的用户使用,所以我们需要检查用户的身份。常用的做法是通过 Header 传递 Token,WebSocket 协议也支持与 HTTP 类似的 Header。
下面是一个通过 Header 传递 token
并验证的示例。
服务器端,我们需要在 controller 中端点方法中通过注解接收相应的 Header:
private String token = "this is a token generated by your code!";
@MessageMapping("/broadcast")
@SendTo("/b")
public Response say(Message message, @Header(value = "authorization") String authorizationToken) {
if (authorizationToken.equals(token)) {
System.out.println("Token check success!!!");
} else {
System.out.println("Token check failed!!!");
}
return new Response("Welcome, " + message.getName() + "!");
}
当通过客户端 STOMP 的 SEND
方法向服务器发送消息并附带 authorization
header 的时候,authorization 的值(即 token)会被服务器成功获取。
浏览器端,我们需要做的是在 SEND
方法中附带 HTTP Header:
stompClient.send(
'/broadcast',
{
"authorization": "this is a token generated by your code!"
},
JSON.stringify({'name': name})
);
现在,当我们向 /broadcast
发送消息的时候,authorization
也会被发送给服务器。
安卓端,跟浏览器端大体类似:
String token = "this is a token generated by your code!";
StompHeader authorizationHeader = new StompHeader("authorization", token);
stompClient.send(new StompMessage(
// STOMP 指令
StompCommand.SEND,
// STOMP headers
// 第一个 header 是必须的,其他的我们可以自定义
Arrays.asList(new StompHeader(StompHeader.DESTINATION, Const.broadcast), authorizationHeader),
// STOMP 荷载(即消息体)
jsonObject.toString())
).subscribe(...);
现在,我们可以在服务器生成 Token,并且在用户成功登录的时候发送给用户。当用户想要发送消息到端点时,需要先提供合法的 Token。