一.WebSocket协议
- WebSocket协议是基于TCP的一种网络应用层协议,它实现了浏览器与服务器全双工(full-duplex)通信——允许服务器主动发送信息给客户端。
- 与Http协议对比
| WebSocket | Http | |
|---|---|---|
| 协议标识符 | ws/wss | http/https |
| 默认端口号 | 80/443 | 80/443 |
| 基础协议 | TCP/IP | TCP/IP |
| OSI模型 | 应用层 | 应用层 |
| 请求方式 | 客户端发起/服务端推送 | 客户端发起 |
二.STOMP
- STOMP即Simple (or Streaming) Text Orientated Messaging Protocol,简单(流)文本定向消息协议,它提供了一个可互操作的连接格式,允许STOMP客户端与任意STOMP消息代理(Broker)进行交互。STOMP协议由于设计简单,易于开发客户端,因此在多种语言和多种平台上得到广泛地应用。STOMP协议的前身是TTMP协议(一个简单的基于文本的协议),专为消息中间件设计。
- STOMP协议与HTTP协议很相似,它基于TCP协议,使用了以下命令: CONNECT/SEND/SUBSCRIBE/UNSUBSCRIBE/BEGIN/COMMIT/ABORT/ACK/NACK/DISCONNECT
- 支持订阅和发送消息、心跳检测、事务、Debug
三.WebSocket使用方案
1. Spring+SoketJS/WebSocketAPI+STOMP
- 后端基于Spring-WebSocket和Spring-Messaging,创建WebSocket服务和MessageBroker,使用MessageMapping的方式在一个服务上提供多个订阅目标地址,一个(或多个)订阅的目标地址对应一个业务逻辑;
- 前端使用SoketJS/WebSocketAPI和STOMPJS处理订阅和处理消息,并根据不同的业务逻辑订阅相匹配的目标地址,在该目标地址的CallBack函数中处理逻辑;
- 优劣势:好处在于使用MessageMapping,类似于url的方式进行业务处理,前后端的代码逻辑更清晰,后端配置好Server之后跟现在http请求的方式类似;支持订阅与广播;劣势在于方案有部分局限性,前端必须支持STOMP协议,前端需要引入第三方js包或做对应的开发;
2.Spring/Javax.websocket-api+WebSocketAPI
- 后端基于Spring-WebSocket/Javax.websocket-api,创建WebSocket服务,所有业务按照消息传递的参数区分,或根据不同的业务创建多个WebSocket服务;
- 前端按照约定创建一个或多个Client,根据不同的业务场景传递相应的业务标识参数及该业务场景需要的请求参数,然后后台返回数据也需要带有该业务场景的标识参数,前端根据业务标识调用相应的处理逻辑;
- 优劣势:优势在于减少第三方的依赖,所有逻辑自己开发处理,可控性强;劣势在于前后端使用业务标识参数的方式实现了路由选择的功能,前后端都需要维护一个业务标识参数对应匹配的处理业务逻辑的方法的主入口,后续若想要实现广播的需求后台需要维护一个用户Session的列表,再根据不同的业务进行广播;
- 该方案需要讨论WebSocket应用与多种业务场景时的前后端交互方式。
四.示例代码
1.基于javax.websocket包的实现
/**
* 必须支持javaee7以上的容器才能支持
*/
@ServerEndpoint("/websocket")
public class WebSocket {
private static final ConcurrentHashMap<String, WebSocket> WEBSOCKETS = new ConcurrentHashMap<>();
private Session session;
@OnOpen
public void onOpen(Session session) {
this.session = session;
WEBSOCKETS.put(session.getId(), this);
}
@OnMessage
public void onMessage(Session session, String message) throws IOException {
WEBSOCKETS.get(session.getId()).sendMessage(message);
}
private void sendMessage(String message) throws IOException {
this.session.getBasicRemote().sendText(message);
}
@OnError
public void onError(Session session, Throwable error) {
LOGGER.info("session:" + session.getId());
error.printStackTrace();
}
@OnClose
public void onClose(Session session) {
WEBSOCKETS.remove(session.getId());
}
}
2.基于Spring Message的实现
配置类主要代码
/**
* Spring 支持的STOMP协议的写法,使用@MessageMapping等注解即可使用
* 这是推荐的方式
*/
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketMessageBrokerConfig extends AbstractWebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
// template-ws就是webSocket的端点,客户端需要注册这个端点进行链接;
// withSockJS允许客户端利用sockJs进行浏览器兼容性处理
registry.addEndpoint("/ws").setAllowedOrigins("*").addInterceptors();
registry.addEndpoint("/ws/wsjs").withSockJS();
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/topic"); // 设置服务器广播消息的基础路径
registry.setApplicationDestinationPrefixes("/pre"); // 设置客户端订阅消息的基础路径
}
}
Controller主要代码
@Controller
public class WebSocketCtrl {
@Resource
private SimpMessagingTemplate template;
/**
* 广播消息,推送消息到所有订阅频道的客户端
*/
@MessageMapping("/ws/radio") // 接收客户端消息,路径匹配,功能类似于@RequestMapping
@SendTo("/topic/radio") // 广播消息
public ResponseResult radio(WsMessage message) {
return ResponseResult.successResult("message:" + message.getContent());
}
/**
* 点对点消息,推送消息到发送消息的客户端
*/
@MessageMapping("/ws/point")
@SendToUser(value = "/topic/point", broadcast = false) // 点对点消息,broadcast为是否广播消息,默认为true
public ResponseResult point(WsMessage message) {
return ResponseResult.successResult("message:" + message.getContent());
}
}
JS部分代码
function connect() {
// 几种链接方式,包括WebSocket和SockJS
var socket = new SockJS(contentPath + '/ws/wsjs');
var socket = new WebSocket('ws://' + window.location.host + contentPath + '/ws');
stompClient = Stomp.client('ws://' + window.location.host + contentPath + '/ws');
stompClient.connect({}, function (frame) {
setConnected(true);
console.log('Connected: ' + frame);
stompClient.subscribe('/topic/radio', function (xxxdiv) {
showMsg('radio-' + JSON.parse(xxxdiv.body).data);
});
stompClient.subscribe('/user/topic/point', function (xxxdiv) {
showMsg('point-' + JSON.parse(xxxdiv.body).data);
});
});
}
function disconnect() {
if (stompClient !== null) {
stompClient.disconnect();
}
setConnected(false);
console.log('Disconnected');
}
注:代码基于学习WebSocket时期的技术版本,仅供参考