Spring 提供了一个 SockJS Java 客户端,无需使用浏览器即可连接到远程 SockJS 端点。当需要在公共网络上的两个服务器之间进行双向通信时,这可能特别有用(也就是说,在网络代理可能排除使用 WebSocket 协议的地方)。SockJS Java 客户端对于测试目的也非常有用(例如,模拟大量的并发用户)。

SockJS Java 客户端支持 websocket、xhr-streaming 和 xhr-polling 传输。其余的只有在浏览器中使用才有意义。

你可以用 WebSocketTransport 来配置:

  • JSR-356 运行时中的 StandardWebSocketClient。
  • JettyWebSocketClient,通过使用 Jetty 9+ 本地 WebSocket API。
  • Spring 的 WebSocketClient 的任何实现。

根据定义,XhrTransport 同时支持 xhr-streaming 和 xhr-polling,因为从客户的角度来看,除了用于连接到服务器的 URL 外,没有任何区别。目前,有两种实现方式:

  • RestTemplateXhrTransport 使用 Spring 的 RestTemplate 进行 HTTP 请求。
  • JettyXhrTransport 使用 Jetty 的 HttpClient 进行 HTTP 请求。

下面的例子展示了如何创建一个 SockJS 客户端并连接到一个 SockJS 端点:

  1. List<Transport> transports = new ArrayList<>(2);
  2. transports.add(new WebSocketTransport(new StandardWebSocketClient()));
  3. transports.add(new RestTemplateXhrTransport());
  4. SockJsClient sockJsClient = new SockJsClient(transports);
  5. sockJsClient.doHandshake(new MyWebSocketHandler(), "ws://example.com:8080/sockjs");

:::info SockJS 使用 JSON 格式的数组来传递信息。默认情况下,Jackson 2 被使用,并且需要在 classpath 中出现。另外,你可以配置一个SockJsMessageCodec 的自定义实现,并在 SockJsClient 上配置它。 :::

为了使用 SockJsClient 来模拟大量的并发用户,你需要配置底层的 HTTP 客户端(用于 XHR 传输)以允许足够数量的连接和线程。下面的例子展示了如何用 Jetty 做到这一点:

  1. HttpClient jettyHttpClient = new HttpClient();
  2. jettyHttpClient.setMaxConnectionsPerDestination(1000);
  3. jettyHttpClient.setExecutor(new QueuedThreadPool(1000));

下面的例子显示了服务器端 SockJS 相关的属性(详见 javadoc),你也应该考虑定制这些属性:

  1. @Configuration
  2. public class WebSocketConfig extends WebSocketMessageBrokerConfigurationSupport {
  3. @Override
  4. public void registerStompEndpoints(StompEndpointRegistry registry) {
  5. registry.addEndpoint("/sockjs").withSockJS()
  6. // 将 streamBytesLimit 属性设为 512KB(默认为 128KB-128*1024)。
  7. .setStreamBytesLimit(512 * 1024)
  8. // 设置 httpMessageCacheSize 属性为 1,000(默认为 100)。
  9. .setHttpMessageCacheSize(1000)
  10. // 将 disconnectDelay 属性设置为 30 属性秒(默认为 5 秒 - 5*1000)。
  11. .setDisconnectDelay(30 * 1000);
  12. }
  13. // ...
  14. }

一个例子

前面的 demo 例子 上修改部分代码

  1. package cn.mrcode.study.springdocsread.websocket;
  2. import org.springframework.context.annotation.Bean;
  3. import org.springframework.context.annotation.Configuration;
  4. import org.springframework.web.socket.WebSocketHandler;
  5. import org.springframework.web.socket.config.annotation.EnableWebSocket;
  6. import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
  7. import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
  8. import org.springframework.web.socket.server.standard.TomcatRequestUpgradeStrategy;
  9. /**
  10. * @author mrcode
  11. */
  12. @Configuration
  13. @EnableWebSocket
  14. public class MyWebSocketConfig implements WebSocketConfigurer {
  15. @Override
  16. public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
  17. registry.addHandler(myHandler(), "/myHandler")
  18. .withSockJS(); // 改成这个
  19. }
  20. @Bean
  21. public WebSocketHandler myHandler() {
  22. return new MyHandler();
  23. }
  24. /*
  25. spring 为了支持每种容器自己的 websocket 升级策略,抽象了 RequestUpgradeStrategy,
  26. <p>对 tomcat 提供了 TomcatRequestUpgradeStrategy 策略</p>
  27. 如果不申明这个,就会在启动的时候抛出异常:No suitable default RequestUpgradeStrategy found
  28. */
  29. @Bean
  30. public TomcatRequestUpgradeStrategy tomcatRequestUpgradeStrategy() {
  31. return new TomcatRequestUpgradeStrategy();
  32. }
  33. }

然后启动项目就可以了。浏览器里面我就不测试了。自己看 Github 的官方文档如何使用 SockJS 链接后端,这里用本章说到的 Spring Java 客户端来对 SockJS 发起测试

  1. package cn.mrcode.study.springdocsread.test;
  2. import org.springframework.util.concurrent.ListenableFuture;
  3. import org.springframework.web.socket.TextMessage;
  4. import org.springframework.web.socket.WebSocketSession;
  5. import org.springframework.web.socket.client.standard.StandardWebSocketClient;
  6. import org.springframework.web.socket.sockjs.client.RestTemplateXhrTransport;
  7. import org.springframework.web.socket.sockjs.client.SockJsClient;
  8. import org.springframework.web.socket.sockjs.client.Transport;
  9. import org.springframework.web.socket.sockjs.client.WebSocketTransport;
  10. import java.io.IOException;
  11. import java.util.ArrayList;
  12. import java.util.List;
  13. import cn.mrcode.study.springdocsread.websocket.MyHandler;
  14. /**
  15. * @author mrcode
  16. */
  17. public class SockjsTest {
  18. public static void main(String[] args) {
  19. List<Transport> transports = new ArrayList<>(2);
  20. transports.add(new WebSocketTransport(new StandardWebSocketClient()));
  21. transports.add(new RestTemplateXhrTransport());
  22. SockJsClient sockJsClient = new SockJsClient(transports);
  23. final ListenableFuture<WebSocketSession> future = sockJsClient.doHandshake(new MyHandler(), "ws://localhost:8080/myHandler");
  24. sockJsClient.start(); // 开始
  25. // 添加一个回调,链接成功后,就可以通过 websocketSession 往服务端发起信息了
  26. future.addCallback(t -> {
  27. try {
  28. t.sendMessage(new TextMessage("可以发送信息了:你好"));
  29. } catch (IOException e) {
  30. throw new RuntimeException(e);
  31. }
  32. }, fall ->{
  33. System.out.println(fall);
  34. });
  35. }
  36. }

启动这个测试后,就可以往服务端发送消息了。