Web实时通讯方式

2024-05-16 19:48:37 浏览数 (2)

前言

说到实时通讯,就不得不提 WebSocket 技术。WebSocket 建立持久、双向的通信通道,大幅降低了延迟,非常适合即时互动应用,如在线聊天、实时监控等。

WebSocket 依靠持久性 TCP 连接实现高效通信,同时支持灵活的数据格式。因此 WebSocket 为实时通讯应用提供了高效可靠的解决方案,广泛应用于各类互联网应用中。

Stomp方式

后端代码

暴露对外的监听topic

代码语言:javascript复制
package com.example.messagingstompwebsocket;

import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Controller;
import org.springframework.web.util.HtmlUtils;

@Controller
public class GreetingController {


  @MessageMapping("/hello")
  @SendTo("/topic/greetings")
  public Greeting greeting(HelloMessage message) throws Exception {
    Thread.sleep(1000); // simulated delay
    return new Greeting("Hello, "   HtmlUtils.htmlEscape(message.getName())   "!");
  }

}

配置拦截器和对外的URL

代码语言:javascript复制
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

  @Override
  public void configureMessageBroker(MessageBrokerRegistry config) {
    config.enableSimpleBroker("/topic");
    config.setApplicationDestinationPrefixes("/app");
  }

  @Override
  public void registerStompEndpoints(StompEndpointRegistry registry) {
    registry.addEndpoint("/gs-guide-websocket");
  }

  @Bean
	public AuthenticationInterceptor authenticationInterceptor() {
		return new AuthenticationInterceptor();
	}

	@Override
	public void configureClientInboundChannel(ChannelRegistration registration) {
		// 添加拦截器,处理客户端发来的请求
		registration.interceptors(authenticationInterceptor());
	}


}

前端代码

静态页面

代码语言:javascript复制
<!DOCTYPE html>
<html>
<head>
    <title>Hello WebSocket</title>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
    <link href="/main.css" rel="stylesheet">
    <script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/@stomp/stompjs@7.0.0/bundles/stomp.umd.min.js"></script>
    <script src="/app.js"></script>
</head>
<body>
<noscript><h2 style="color: #ff0000">Seems your browser doesn't support Javascript! Websocket relies on Javascript being
    enabled. Please enable
    Javascript and reload this page!</h2></noscript>
<div id="main-content" class="container">
    <div class="row">
        <div class="col-md-6">
            <form class="form-inline">
                <div class="form-group">
                    <label for="connect">WebSocket connection:</label>
                    <button id="connect" class="btn btn-default" type="submit">Connect</button>
                    <button id="disconnect" class="btn btn-default" type="submit" disabled="disabled">Disconnect
                    </button>
                </div>
            </form>
        </div>
        <div class="col-md-6">
            <form class="form-inline">
                <div class="form-group">
                    <label for="name">What is your name?</label>
                    <input type="text" id="name" class="form-control" placeholder="Your name here...">
                </div>
                <button id="send" class="btn btn-default" type="submit">Send</button>
            </form>
        </div>
    </div>
    <div class="row">
        <div class="col-md-12">
            <table id="conversation" class="table table-striped">
                <thead>
                <tr>
                    <th>Greetings</th>
                </tr>
                </thead>
                <tbody id="greetings">
                </tbody>
            </table>
        </div>
    </div>
</div>
</body>
</html>

js代码

代码语言:javascript复制
const stompClient = new StompJs.Client({
    brokerURL: 'ws://localhost:8080/gs-guide-websocket'
});

stompClient.onConnect = (frame) => {
    setConnected(true);
    console.log('Connected: '   frame);
    stompClient.subscribe('/topic/greetings', (greeting) => {
        showGreeting(JSON.parse(greeting.body).content);
    });
};

stompClient.onWebSocketError = (error) => {
    console.error('Error with websocket', error);
};

stompClient.onStompError = (frame) => {
    console.error('Broker reported error: '   frame.headers['message']);
    console.error('Additional details: '   frame.body);
};

function setConnected(connected) {
    $("#connect").prop("disabled", connected);
    $("#disconnect").prop("disabled", !connected);
    if (connected) {
        $("#conversation").show();
    }
    else {
        $("#conversation").hide();
    }
    $("#greetings").html("");
}

function connect() {
    stompClient.activate();
}

function disconnect() {
    stompClient.deactivate();
    setConnected(false);
    console.log("Disconnected");
}

function sendName() {
    stompClient.publish({
        destination: "/app/hello",
        body: JSON.stringify({'name': $("#name").val()})
    });
}

function showGreeting(message) {
    $("#greetings").append("<tr><td>"   message   "</td></tr>");
}

$(function () {
    $("form").on('submit', (e) => e.preventDefault());
    $( "#connect" ).click(() => connect());
    $( "#disconnect" ).click(() => disconnect());
    $( "#send" ).click(() => sendName());
});

操作演示

小结

ws stomp方式适合需要快速、实时地交流信息的场景,比如聊天室、股票行情等。它可以让一个服务器同时与多个客户端进行双向沟通,实现信息的快速传递和共享。这种方式还可以搭配消息中间件,提高系统的可靠性和负载均衡能力,非常适合处理大量实时数据的应用。

Websocket原生方式

后端代码

实现回调处理

继承TextWebSocketHandler实现回调方式

代码语言:javascript复制
import cn.hutool.core.util.StrUtil;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @author steven
 */
@Log4j2
@Component
public class WebSocketHandler extends TextWebSocketHandler {

    @Autowired
    private MqService mqService;

    @Autowired
    private WsSessionManager wsSessionManager;

    @Autowired
    private InvokeWsCmd invokeWsCmd;

    public static final int coreSize = Runtime.getRuntime().availableProcessors();

    private ThreadPoolExecutor executor =  new ThreadPoolExecutor(2 * coreSize, 2 * coreSize,
            0L, TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue<Runnable>());

    /**
     * socket 建立成功事件
     *
     * @param session
     * @throws Exception
     */
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        wsSessionManager.wsOpen(session);
    }

    /**
     * 接收消息事件
     *
     * @param session
     * @param message
     * @throws Exception
     */
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        executor.execute(()->{
            handleMessage(session, message);
        });
    }

    private void handleMessage(WebSocketSession session, TextMessage message) {
        // 获得客户端传来的消息
        String token = (String) session.getAttributes().get("token");
        log.info("server 接收到 {} 发送的 {}", token, message.getPayload());
        if (StrUtil.isBlankIfStr(message.getPayload())) {
            wsSessionManager.validateParameterFailed(session);
            return;
        }
        invokeWsCmd.run(session.getId(), message.getPayload());
    }

    /**
     * socket 断开连接时
     *
     * @param session
     * @param status
     * @throws Exception
     */
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        /**
         * 用户退出,移除缓存
         */
        wsSessionManager.wsClose(session.getId());
        String token = (String) session.getAttributes().get("token");
        log.info( "websocket id:{} 关闭, token:{}", session.getId(), token);
    }

}

配置对外的URL

代码语言:javascript复制
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;

/**
 * @author Steven
 */
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    @Autowired
    private WebSocketHandler websocketHandle;

    @Autowired
    private WebSocketInterceptor webSocketInterceptor;

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler( websocketHandle, "/api/ws/market")
                .addInterceptors( webSocketInterceptor )
                .setAllowedOrigins("*");
    }
}

操作演示

小结

ws 原生方式非常适合需要实时、双向通信的应用场景,如聊天室、在线游戏等。它可以持续保持连接,大幅降低通信延迟,非常适合对实时性有要求的应用。同时 ws 在资源消耗上更高效,适合用于手机、物联网等资源受限设备。此外,ws 原生方式允许自定义消息格式,灵活性强。总的来说,ws 原生方式非常适合各种实时互动类应用。

总结

ws stomp 方式的优点是支持发布-订阅模式,适合一对多通信场景,并可搭配消息中间件实现负载均衡和容错,非常适合大数据实时处理等需要高效消息队列的场景。但它也增加了系统复杂度,可能会有一定性能损失。

相比之,ws 原生方式更加简单直接,实现了真正的实时双向通信,延迟低,资源消耗小,非常适合要求高实时性和资源受限的应用,如聊天室、在线游戏等。不过它不支持发布-订阅模式,需自行实现负载均衡和容错。

总的来说,选择时需权衡具体需求。ws stomp 方式适合需要消息队列、负载均衡等高级特性的场景,而 ws 原生方式更适合追求极致实时性和资源效率的应用。

引用

https://spring.io/guides/gs/messaging-stomp-websocket/

0 人点赞