STOMP Client

Spring 提供了一个 STOMP over WebSocket 客户端和一个STOMP over TCP客户端。

首先,你可以创建并配置 WebSocketStompClient,如下例所示:

  1. WebSocketClient webSocketClient = new StandardWebSocketClient();
  2. WebSocketStompClient stompClient = new WebSocketStompClient(webSocketClient);
  3. stompClient.setMessageConverter(new StringMessageConverter());
  4. stompClient.setTaskScheduler(taskScheduler); // for heartbeats

在前面的例子中,你可以用 SockJsClient 取代 StandardWebSocketClient,因为它也是 WebSocketClient 的一个实现。SockJsClient 可以使用 WebSocket 或基于 HTTP 的传输作为退路。更多详情,请参见 SockJsClient

接下来,你可以建立一个连接并为 STOMP 会话提供一个处理程序,如下例所示:

  1. String url = "ws://127.0.0.1:8080/endpoint";
  2. StompSessionHandler sessionHandler = new MyStompSessionHandler();
  3. stompClient.connect(url, sessionHandler);

当会话可以使用时,处理程序会被通知,如下例所示:

  1. public class MyStompSessionHandler extends StompSessionHandlerAdapter {
  2. @Override
  3. public void afterConnected(StompSession session, StompHeaders connectedHeaders) {
  4. // ...
  5. }
  6. }

一旦会话建立,任何有效载荷都可以被发送,并通过配置的 MessageConverter 进行序列化,如下例所示:

  1. session.send("/topic/something", "payload");

你也可以对目的地进行订阅。订阅方法需要一个处理程序来处理订阅上的消息,并返回一个订阅句柄,你可以用它来取消订阅。对于每个收到的消息,处理程序可以指定有效载荷应该被反序列化的目标对象类型,如下面的例子所示:

  1. session.subscribe("/topic/something", new StompFrameHandler() {
  2. @Override
  3. public Type getPayloadType(StompHeaders headers) {
  4. return String.class;
  5. }
  6. @Override
  7. public void handleFrame(StompHeaders headers, Object payload) {
  8. // ...
  9. }
  10. });

为了启用 STOMP 心跳,你可以用一个 TaskScheduler 来配置 WebSocketStompClient,并可选择自定义心跳间隔(10 秒为写入不活动,会导致发送心跳,10 秒为读取不活动,会关闭连接)。

WebSocketStompClient 只在不活动的情况下发送心跳,即在没有其他消息被发送的情况下发送。当使用 外部代理 时,这可能是一个挑战,因为具有非代理目的地的消息代表活动,但实际上并没有转发到代理。在这种情况下,你可以在初始化外部代理时配置一个 TaskScheduler,确保在只有非代理目的地的消息被发送时,心跳也被转发到代理。

:::info 当你使用 WebSocketStompClient 进行性能测试以模拟来自同一台机器的数千个客户端时,请考虑关闭心跳,因为每个连接都会安排自己的心跳任务,而这对于在同一台机器上运行的大量客户端来说并不优化。 :::

STOMP 协议还支持收据(receipts),客户端必须添加一个收据头(receipt ),服务器在处理完发送或订阅后会用一个 RECEIPT 帧来回应。为了支持这一点,StompSession 提供了 setAutoReceipt(boolean)功能,使每一个后续的发送或订阅事件都会添加一个 receipt头。另外,你也可以手动添加一个接收头到 StompHeaders 中。发送和订阅都返回一个 Receiptable 的实例,你可以用它来注册接收成功和失败的回调。对于这个功能,你必须用一个 TaskScheduler 和收据过期前的时间量(默认为 15 秒)来配置客户端。

请注意,StompSessionHandler 本身就是一个 StompFrameHandler,这让它除了处理信息处理中的异常的 handleException 回调和处理包括 ConnectionLostException 在内的传输级错误的 handleTransportError 之外,还能处理 ERROR 帧。

一个例子

这里使用 Java 客户端来实现 这个前端 html 中的功能

  1. package cn.mrcode.study.springdocsread;
  2. import org.springframework.messaging.converter.StringMessageConverter;
  3. import org.springframework.messaging.simp.stomp.StompCommand;
  4. import org.springframework.messaging.simp.stomp.StompFrameHandler;
  5. import org.springframework.messaging.simp.stomp.StompHeaders;
  6. import org.springframework.messaging.simp.stomp.StompSession;
  7. import org.springframework.messaging.simp.stomp.StompSessionHandlerAdapter;
  8. import org.springframework.web.socket.WebSocketHttpHeaders;
  9. import org.springframework.web.socket.client.standard.StandardWebSocketClient;
  10. import org.springframework.web.socket.messaging.WebSocketStompClient;
  11. import org.springframework.web.socket.sockjs.client.RestTemplateXhrTransport;
  12. import org.springframework.web.socket.sockjs.client.SockJsClient;
  13. import org.springframework.web.socket.sockjs.client.Transport;
  14. import org.springframework.web.socket.sockjs.client.WebSocketTransport;
  15. import java.lang.reflect.Type;
  16. import java.util.ArrayList;
  17. import java.util.List;
  18. import java.util.concurrent.TimeUnit;
  19. /**
  20. * @author mrcode
  21. */
  22. public class WebSocketClientTest {
  23. public static void main(String[] args) throws InterruptedException {
  24. // WebSocketClient webSocketClient = new StandardWebSocketClient();
  25. List<Transport> transports = new ArrayList<>(2);
  26. transports.add(new WebSocketTransport(new StandardWebSocketClient()));
  27. transports.add(new RestTemplateXhrTransport());
  28. SockJsClient webSocketClient = new SockJsClient(transports);
  29. WebSocketStompClient stompClient = new WebSocketStompClient(webSocketClient);
  30. stompClient.setMessageConverter(new StringMessageConverter());
  31. String url = "ws://127.0.0.1:8080/portfolio";
  32. // 添加头
  33. final WebSocketHttpHeaders headers = new WebSocketHttpHeaders();
  34. // 链接 heaer
  35. final StompHeaders connectHeaders = new StompHeaders();
  36. connectHeaders.setLogin("user1");
  37. connectHeaders.setPasscode("123456");
  38. // 链接到服务端
  39. stompClient.connect(url, headers, connectHeaders,new StompSessionHandlerAdapter() {
  40. @Override
  41. public void afterConnected(StompSession session, StompHeaders connectedHeaders) {
  42. // 订阅用户消息
  43. session.subscribe("/user/exchange/amq.direct/trade", new StompFrameHandler() {
  44. @Override
  45. public Type getPayloadType(StompHeaders headers) {
  46. return String.class;
  47. }
  48. @Override
  49. public void handleFrame(StompHeaders headers, Object payload) {
  50. System.out.println("收到订阅的 /user/exchange/amq.direct/trade 消息:" + payload);
  51. }
  52. });
  53. // 发送更新消息
  54. session.send("/app/trade", "更新消息");
  55. }
  56. @Override
  57. public void handleException(StompSession session, StompCommand command, StompHeaders headers, byte[] payload, Throwable exception) {
  58. exception.printStackTrace();
  59. }
  60. @Override
  61. public void handleTransportError(StompSession session, Throwable exception) {
  62. exception.printStackTrace();
  63. }
  64. });
  65. TimeUnit.SECONDS.sleep(1000);
  66. }
  67. }

执行测试后,就能看到控制台输出的

  1. 收到订阅的 /user/exchange/amq.direct/trade 消息:消息已经处理完成:更新消息