• WebSocket的简单应用


    1、由来

    我们熟悉的Http协议是一种无状态、无连接、单向的应用层协议,它采用了请求/响应模型。通信请求只能由客户端(浏览器)发起,服务端对请求做出响应处理,Http协议无法实现服务器向客户端发送消息(在服务器端发送变化的时候 比如发送公告)。在这种情况下websocket就应运而生。

    Http这种单向请求,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。大多数的web应用程序都是通过频繁的的异步javaScript和ajax请求进行长轮询。效率低下,非常的浪费资源。

    webSocket链接允许客户端和服务端进行全双工通信,以便任意一方都可以建立连接将数据推送到另一端。webSocket只需要建立一次来链接,就可以一直保持链接状态。这相比于轮询方式的不停建立连接显然效率要大大提高。

     

    2、样式

     

    3、简介 

    有一些浏览器中缺少对WebSocket的支持,而SockJS是一个浏览器的JavaScript库,它提供了一个类似于网络的对象,SockJS提供了一个连贯的,跨浏览器的JavaScriptAPI,它在浏览器和Web服务器之间创建了一个低延迟、全双工、跨域通信通道。SockJS的一大好处在于提供了浏览器兼容性。即优先使用原生WebSocket,如果浏览器不支持WebSocket,会自动降为轮询的方式。

    1. <script src="/js/appjs/oa/webSocket/stomp.min.js">script>


    发起连接

    1. //创建连接对象 未连接
    2. var sock = new SockJS("/endpointChat");
    3. // 获取 STOMP 子协议客户端对象
    4. var stomp = Stomp.over(sock);
    5. //方法签名
    6. stomp.connect(headers, connectCallback, errorCallback);

    说明:
    1) socket连接对象也可通过WebSocket(不通过SockJS)连接

    var socket=new WebSocket("/spring-websocket-portfolio/portfolio");

    其中
    headers表示客户端的认证信息,如:

    1. var headers = {
    2. login: 'mylogin',
    3. passcode: 'mypasscode',
    4. // additional header
    5. 'client-id': 'my-client-id'
    6. };

    若无需认证,直接使用空对象 “{}” 即可;

    connectCallback 表示连接成功时(服务器响应 CONNECTED 帧)的回调方法;
    errorCallback 表示连接失败时(服务器响应 ERROR 帧)的回调方法,非必须;

    断开连接

    stomp.disconnect();

    发送信息

    连接成功后,客户端可使用 send() 方法向服务器发送信息

    client.send( url, headers, body);

     其中
    url 为服务器 controller中 @MessageMapping 中匹配的URL,字符串,必须参数;
    headers 为发送信息的header,JavaScript 对象,可选参数;
    body 为发送信息的 body,字符串,可选参数;

    1. var payload = JSON.stringify({'message':username})
    2. stomp.send("/app/welcome",{},payload);
    3. @Controller
    4. public class WebSocketController {
    5. @MessageMapping("/welcome") // 浏览器发送请求通过@messageMapping 映射/welcome 这个地址。
    6. //@SendTo("/topic/getResponse") // 服务器端有消息时,会订阅@SendTo 中的路径的浏览器发送消息。
    7. @SendTo("/queue/notifications")
    8. public Response say(Message message) throws Exception {
    9. Thread.sleep(1000);
    10. return new Response("Welcome, " + message.getMessage() + " !");
    11. }
    12. }

    订阅、接收信息

    STOMP 客户端要想接收来自服务器推送的消息,必须先订阅相应的URL,即发送一个 SUBSCRIBE 帧,然后才能不断接收来自服务器的推送消息;
    订阅和接收消息通过 subscribe() 方法实现:

    subscribe(destination url, callback, headers)

    其中
    destination url 为服务器 @SendTo 匹配的 URL,字符串;
    callback 为每次收到服务器推送的消息时的回调方法,该方法包含参数 message;
    headers 为附加的headers,JavaScript 对象;什么作用?
    该方法返回一个包含了id属性的 JavaScript 对象,可作为 unsubscribe() 方法的参数;

    例:

    1. stomp.subscribe('/topic/getResponse', function (message) { //订阅/topic/getResponse 目标发送的消息。这个是在控制器的@SendTo中定义的。
    2. if (message.body) {
    3. alert("got message with body " + message.body)
    4. } else {
    5. alert("got empty message");
    6. }
    7. });

    取消订阅 

    1. var subscription = client.subscribe(...);
    2. subscription.unsubscribe();

    JSON 支持

    STOMP 帧的 body 必须是 string 类型,若希望接收/发送 json 对象,可通过 JSON.stringify() and JSON.parse() 实现;
    例: 

    1. var quote = {symbol: 'APPL', value: 195.46};
    2. client.send("/topic/stocks", {}, JSON.stringify(quote));
    3. client.subcribe("/topic/stocks", function(message) {
    4. var quote = JSON.parse(message.body);
    5. alert(quote.symbol + " is at " + quote.value);
    6. });

    4、使用

    导入依赖

    1. <dependency>
    2.    <groupId>org.springframework.bootgroupId>
    3.    <artifactId>spring-boot-starter-websocketartifactId>
    4. dependency>

    配置类

    1. /**
    2. * 通过EnableWebSocketMessageBroker 开启使用STOMP协议来传输基于代理(message broker)的消息,
    3. * 此时浏览器支持使用@MessageMapping 就像支持@RequestMapping一样。
    4. */
    5. @Configuration
    6. @EnableWebSocketMessageBroker
    7. public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
    8. /**
    9. * 扫描@ServerEndpoint,将@ServerEndpoint修饰的类注册为websocket
    10. * 如果使用外置tomcat,则不需要此配置
    11. */
    12. @Bean
    13. public ServerEndpointExporter serverEndpointExporter()
    14. {
    15. return new ServerEndpointExporter();
    16. }
    17. @Override
    18. public void registerStompEndpoints(StompEndpointRegistry registry) { //endPoint 注册协议节点,并映射指定的URl
    19. //注册一个名字为"endpointChat" 的endpoint,并指定 SockJS协议。 点对点-用
    20. registry.addEndpoint("/endpointChat").withSockJS();
    21. }
    22. @Override
    23. public void configureMessageBroker(MessageBrokerRegistry registry) {
    24. // 设置消息代理前缀
    25. // 即如果消息的前缀是 /topic ,就会将消息转发给消息代理(broker),
    26. // 再由消息代理将消息广播给当前连接的客户端。
    27. //点对点式增加一个/queue 消息代理
    28. registry.enableSimpleBroker("/queue", "/topic");
    29. //客户端向服务端发起请求时,需要以/app为前缀。
    30. registry.setApplicationDestinationPrefixes("/app");
    31. }
    32. }

    方式一 监控连接

    后台自己实现Endpoint,前端使用内置的WebSocket。

    1. @ServerEndpoint("/websocket")
    2. @Component //放到spring容器中
    3. @Slf4j
    4. public class WebSocketServer{
    5. /**
    6. * 所有连接的客户端
    7. */
    8. private static ConcurrentHashMap clients = new ConcurrentHashMap<>();
    9. /**
    10. * 建立连接时调用的方法
    11. */
    12. @OnOpen
    13. public void onOpen(Session session) {
    14. clients.put(session.getId(),session);
    15. //向特定用户发送消息,使用的session是接收方的session
    16. session.getAsyncRemote().sendText("已加入群聊");
    17. }
    18. /**
    19. * 连接关闭时调用的方法
    20. */
    21. @OnClose
    22. public void onClose(Session session) {
    23. clients.remove(session.getId());
    24. session.getAsyncRemote().sendText("已退出群聊");
    25. }
    26. /**
    27. * 收到客户端发送过来的消息时调用的方法
    28. * @param msg 客户端用户发送过来的消息,二进制可以声明为byte[]
    29. */
    30. @OnMessage
    31. public void onMessage(String msg) {
    32. //群发消息
    33. for (Session session : clients.values()) {
    34. session.getAsyncRemote().sendText(msg);
    35. }
    36. }
    37. /**
    38. * 发生错误时调用的方法
    39. */
    40. @OnError
    41. public void onError(Session session, Throwable e) {
    42. log.error("发送错误的sessionId:"+session.getId()+",错误信息:"+e.getMessage());
    43. }
    44. }

    方式二 

    1、启用STOMP功能

        STOMP 的消息根据前缀的不同分为三种。如下,以 /app 开头的消息都会被路由到带有@MessageMapping 或 @SubscribeMapping 注解的方法中;以/topic 或 /queue 开头的消息都会发送到STOMP代理中,根据你所选择的STOMP代理不同,目的地的可选前缀也会有所限制;以/user开头的消息会将消息重路由到某个用户独有的目的地上。

     

    2、处理来自客户端的STOMP消息

        服务端处理客户端发来的STOMP消息,主要用的是 @MessageMapping 注解。如下:

    1. @MessageMapping("/welcome") // 浏览器发送请求通过@messageMapping 映射/welcome 这个地址。
    2. @SendTo("/queue/notifications") // 服务器端有消息时,会订阅@SendTo 中的路径的浏览器发送消息。
    3. public Response say(Message message) throws Exception {
    4. Thread.sleep(1000);
    5. return new Response("Welcome, " + message.getMessage() + " !");
    6. }

     

     2.3、尤其注意,这个处理器方法有一个返回值,这个返回值并不是返回给客户端的,而是转发给消息代理的,如果客户端想要这个返回值的话,只能从消息代理订阅。@SendTo 注解重写了消息代理的目的地,如果不指定@SendTo,帧所发往的目的地会与触发处理器方法的目的地相同,只不过会添加上“/topic”前缀。

        2.4、如果客户端就是想要服务端直接返回消息呢?听起来不就是HTTP做的事情!即使这样,STOMP 仍然为这种一次性的响应提供了支持,用的是@SubscribeMapping注解,与HTTP不同的是,这种请求-响应模式是异步的...

    1. @SubscribeMapping("/getShout")
    2. public Shout getShout(){
    3. Shout shout = new Shout();
    4. shout.setMessage("Hello STOMP");
    5. return shout;
    6. }

    3、发送消息到客户端

    3.1 在处理消息之后发送消息

        正如前面看到的那样,使用 @MessageMapping 或者 @SubscribeMapping 注解可以处理客户端发送过来的消息,并选择方法是否有返回值。

        如果 @MessageMapping 注解的控制器方法有返回值的话,返回值会被发送到消息代理,只不过会添加上"/topic"前缀。可以使用@SendTo 重写消息目的地;

        如果 @SubscribeMapping 注解的控制器方法有返回值的话,返回值会直接发送到客户端,不经过代理。如果加上@SendTo 注解的话,则要经过消息代理。

    3.2 在应用的任意地方发送消息

        spring-websocket 定义了一个 SimpMessageSendingOperations 接口(或者使用SimpMessagingTemplate ),可以实现自由的向任意目的地发送消息,并且订阅此目的地的所有用户都能收到消息。

    1. @Autowired
    2. private SimpMessagingTemplate template;
    3. /**
    4. * 广播消息,不指定用户,所有订阅此的用户都能收到消息
    5. * @param shout
    6. */
    7. @MessageMapping("/broadcastShout")
    8. public void broadcast(Shout shout) {
    9. template.convertAndSend("/topic/shouts", shout);
    10. }
    • convertAndSendToUser方法

        除了convertAndSend()以外,SimpMessageSendingOperations 还提供了convertAndSendToUser()方法。按照名字就可以判断出来,convertAndSendToUser()方法能够让我们给特定用户发送消息。

    1. @MessageMapping("/singleShout")
    2. public void singleUser(Shout shout, StompHeaderAccessor stompHeaderAccessor) {
    3. String message = shout.getMessage();
    4. LOGGER.info("接收到消息:" + message);
    5. Principal user = stompHeaderAccessor.getUser();
    6. simpMessageSendingOperations.convertAndSendToUser(user.getName(), "/queue/shouts", shout);
    7. }

  • 相关阅读:
    驱动开发day4
    Docker容器与DockerFile开发详解
    IntelliJ IDEA 2022.2保姆级安装教学
    算法训练Day28 | LeetCode93.复原IP地址(回溯算法中的切割问题2);78 子集(每个节点都收集结果);90.子集II(子集问题+去重)
    Feign源码解析5:loadbalancer
    【网络编程】进程间的通信
    Js实现时间函数
    多目标应用:多目标蜣螂优化算法求解多旅行商问题(Multiple Traveling Salesman Problem, MTSP)
    docker 安卓部署RabbitMQ
    MLIR笔记(2)
  • 原文地址:https://blog.csdn.net/qq_56800327/article/details/126332453