JAVA (Springboot) 集成Websocket

一、简介

1、WebSocket的基本概念

WebSocket是一种在单个TCP连接上进行全双工通信的协议。它允许服务端主动向客户端推送数据,同时也允许客户端向服务器发送数据。WebSocket通信协议于2011年被IETF定为标准RFC 6455,并由RFC7936补充规范。WebSocket API也被W3C定为标准。

2、WebSocket的工作原理

握手过程:客户端向服务器发送一个HTTP请求,请求建立WebSocket连接。服务器收到请求后,会返回一个HTTP响应,响应中包含一个101 Switching Protocols状态码,表示同意建立WebSocket连接。
数据传输:一旦WebSocket连接建立成功,服务器和客户端就可以通过这条TCP连接进行实时、双向的数据传输。数据格式可以是JSON、二进制或文本。

3.WebSocket的实现

实现WebSocket通信通常需要使用JavaScript和后端语言(如Node.js、Python、Java等)。在客户端,可以使用JavaScript中的WebSocket API来创建和管理WebSocket连接;在服务器端,则需要使用相应的后端语言来监听WebSocket连接、处理客户端的请求和发送数据给客户端。

二、功能

WebSocket的特点

1.实时性强:由于WebSocket是全双工的通信协议,服务器可以主动向客户端推送数据,因此实时性很强。
2.较少的控制开销:在连接创建后,服务器和客户端之间交换数据时,用于协议控制的数据包头部相对较小。在不包含扩展的情况下,对于服务器到客户端的内容,此头部大小只有2至10字节(和数据包长度有关);对于客户端到服务器的内容,此头部还需要加上额外的4字节的掩码。
3.支持二进制数据:WebSocket定义了二进制帧,因此可以更轻松地处理二进制内容。
4.跨域支持:WebSocket协议可以跨域使用,允许不同源的客户端与服务器进行通信。

WebSocket的应用场景

1.即时通讯:如聊天应用、在线客服系统等,用户可以实时地发送和接收消息。
2.实时数据展示:如实时股票行情、实时天气更新等,通过WebSocket可以实时地推送数据给前端。
3.多人游戏:WebSocket可以实现多人在线游戏,玩家可以实时地进行交互、通信。
4.实时协作:如实时协同编辑器,多个用户可以同时编辑一个文档,并实时地看到其他用户的操作。
5.数据监控:WebSocket可以用于实时监控系统的运行状态、日志更新等,便于及时发现和解决问题。

三、依赖配置

   <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-websocket</artifactId>
    </dependency>

四、开启WebSocket支持

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
import org.springframework.context.annotation.Bean;  
import org.springframework.context.annotation.Configuration;  
import org.springframework.web.socket.server.standard.ServerEndpointExporter;  
  
/**  
 * WebSocket配置类,用于开启WebSocket支持  
 * 通过此类配置,Spring Boot应用程序可以自动注册WebSocket端点,使得使用JSR 356 (Java API for WebSocket) 定义的端点能够被自动发现和使用。  
 */  
@Configuration  
public class WebSocketConfig {  
  
    /**  
     * 定义一个Bean,类型为ServerEndpointExporter,用于自动注册实现了WebSocket规范(JSR 356)的@ServerEndpoint注解声明的WebSocket端点。  
     *  
     * <p>  
     * 当在Spring Boot应用程序中使用@ServerEndpoint注解声明WebSocket端点时,需要此Bean来确保端点能够被正确地注册和暴露。  
     * </p>  
     *  
     * @return ServerEndpointExporter的实例,用于自动注册WebSocket端点  
     */  
    @Bean  
    public ServerEndpointExporter serverEndpointExporter() {  
        return new ServerEndpointExporter();  
    }  
}

五、服务端处理WebSocket类

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 *WebSocket服务器端点类,用于处理WebSocket连接的建立、消息接收、关闭等事件。
 */
@Slf4j
@ServerEndpoint(value = "/websocket/{Id}")// 声明WebSocket服务器端点路径,其中{Id}为路径参数(自行配置)
@Component
public class WebSocketServer {

    /**
     * 用于存储Id到session的映射
     */
    private static final Map<String, Session> SESSIONS_BY_ORDER_ID = new ConcurrentHashMap<>();


    /**
     * WebSocket连接打开时调用。
     *
     * @param session WebSocket会话对象
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("Id") String Id) {
        // 假设Id不为空且有效
        if (Id != null && !Id.isEmpty()) {
            SESSIONS_BY_ORDER_ID.put(Id, session);
        }
        log.debug("成功建立连接,ID:{}", Id);
    }

    /**
     * WebSocket连接关闭时调用。
     *
     * @param session WebSocket会话对象
     */
    @OnClose
    public void onClose(Session session) {
        // 从Id到session的映射中移除当前会话
        SESSIONS_BY_ORDER_ID.entrySet().removeIf(entry -> entry.getValue().equals(session));
        log.debug("成功关闭连接");
    }


    /**
     * 接收到WebSocket消息时调用。
     *
     * @param message 接收到的文本消息
     * @param session WebSocket会话对象
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        log.info("接收到的消息: {},来自会话ID:{}", message, session.getId());
    }

    /**
     * WebSocket连接发生错误时调用。
     *
     * @param session WebSocket会话对象
     * @param error   发生的异常
     */
    @OnError
    public void onError(Session session, Throwable error) {
        log.error("websocket发生错误,会话ID:{}", session.getId(), error);
    }

    /**
     * 根据Id向对应的WebSocket会话发送消息。
     *
     * @param Id ID
     */
    public void sendMessageById(String Id) {
        Session session = SESSIONS_BY_ORDER_ID.get(Id);
        if (session != null && session.isOpen()) {
            try {
                session.getBasicRemote().sendText(Id);
            } catch (IOException e) {
                log.error("根据ID发送消息时发生错误:{}", Id, e);
            }
        } else {
            log.warn("找不到与ID匹配的会话:{}", Id);
        }
    }

    /**
     * 向所有与指定Id关联的WebSocket会话发送消息。
     *
     * @param Id ID
     * @param message 要发送的文本消息
     */
    public void fanoutMessageById(String Id, String message) {
        SESSIONS_BY_ORDER_ID.entrySet().stream()
                .filter(entry -> Id.equals(entry.getKey()))
                .forEach(entry -> {
                    Session session = entry.getValue();
                    if (session.isOpen()) {
                        try {
                            session.getBasicRemote().sendText(message);
                        } catch (IOException e) {
                            log.warn("向ID:{}的会话发送消息时发生异常", Id, e);
                        }
                    }
                });
    }
}
到此为止,我们的WebSocket服务端已经配置完毕。

六、调用方式

根据Id向对应的WebSocket会话发送消息。
WebSocketServer.sendMessageByOrderId(latestrId.get());

七、Nginx配置

  #websocket wss请求
   location /websocket/ {  
        # 将请求代理到指定的域名和端口上。
        proxy_pass https://域名:端口;   
        # 设置代理的HTTP版本为1.1,因为WebSocket连接需要HTTP/1.1的Upgrade和Connection头部
        proxy_http_version 1.1;  
        # 这对于WebSocket连接是必须的,因为WebSocket是通过HTTP的Upgrade机制来“升级”到WebSocket协议的
        proxy_set_header Upgrade $http_upgrade;  
        proxy_set_header Connection $connection_upgrade;  
        # 将客户端请求的Host头部传递给后端服务器  
        # 这对于后端服务器基于虚拟主机(Virtual Host)的配置来路由请求是必要的 
        proxy_set_header Host $host;  
        # 如果请求中包含Upgrade头部,则绕过缓存  
        # 这对于WebSocket连接是必要的,因为WebSocket连接是长连接,不适合被缓存 
        proxy_cache_bypass $http_upgrade;  
        # 如果请求中包含Upgrade头部,则不缓存响应  
        # 类似于proxy_cache_bypass,这确保WebSocket响应不会被缓存
        proxy_no_cache $http_upgrade;  
        # 设置代理读取超时时间为300秒(5分钟)  
        proxy_read_timeout 300; 
    }  


  #websocket ws请求
  location /websocket/ {  
        # 将请求代理到指定的IP地址和端口上。
        proxy_pass http://IP地址:端口;   
        # 设置代理的HTTP版本为1.1,因为WebSocket连接需要HTTP/1.1的Upgrade和Connection头部
        proxy_http_version 1.1;  
        # 这对于WebSocket连接是必须的,因为WebSocket是通过HTTP的Upgrade机制来“升级”到WebSocket协议的
        proxy_set_header Upgrade $http_upgrade;  
        proxy_set_header Connection $connection_upgrade;  
        # 将客户端请求的Host头部传递给后端服务器  
        # 这对于后端服务器基于虚拟主机(Virtual Host)的配置来路由请求是必要的 
        proxy_set_header Host $host;  
        # 如果请求中包含Upgrade头部,则绕过缓存  
        # 这对于WebSocket连接是必要的,因为WebSocket连接是长连接,不适合被缓存 
        proxy_cache_bypass $http_upgrade;  
        # 如果请求中包含Upgrade头部,则不缓存响应  
        # 类似于proxy_cache_bypass,这确保WebSocket响应不会被缓存
        proxy_no_cache $http_upgrade;  
        # 设置代理读取超时时间为300秒(5分钟)  
        proxy_read_timeout 300; 
    }  

八、异常

Websocket异常接收不到请求 添加@EnableWebSocket注解
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;

/**
 * 启动程序
 *
 */
@EnableWebSocket
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class TestApplication {

    public static void main(String[] args) {
        SpringApplication.run(TestApplication.class, args);
    }

}
  • 9
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值