前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Web实时通讯方式

Web实时通讯方式

原创
作者头像
查拉图斯特拉说
发布2024-05-16 19:48:37
1300
发布2024-05-16 19:48:37
举报
文章被收录于专栏:后端架构后端架构

前言

说到实时通讯,就不得不提 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/

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • Stomp方式
    • 后端代码
      • 暴露对外的监听topic
      • 配置拦截器和对外的URL
    • 前端代码
      • 操作演示
        • 小结
        • Websocket原生方式
          • 后端代码
            • 实现回调处理
            • 配置对外的URL
          • 操作演示
            • 小结
            • 总结
            • 引用
            相关产品与服务
            负载均衡
            负载均衡(Cloud Load Balancer,CLB)提供安全快捷的流量分发服务,访问流量经由 CLB 可以自动分配到云中的多台后端服务器上,扩展系统的服务能力并消除单点故障。负载均衡支持亿级连接和千万级并发,可轻松应对大流量访问,满足业务需求。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档