websocke

websocket

  • 介绍
  • 依赖
  • 配置类
  • 实现
  • 获取 session

介绍

WebSocket 是一种网络传输协议,可在单个 TCP 连接上进行全双工通信,位于 OSI 模型的应用层

早期,很多网站为了实现推送技术,所用的技术都是轮询(也叫短轮询)。轮询是指由浏览器每隔一段时间向服务器发出 HTTP 请求,然后服务器返回最新的数据给客户端。常见的轮询方式分为轮询与长轮询

在这种情况下,HTML5 定义了 WebSocket 协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。Websocket 使用 ws 或 wss 的统一资源标志符(URI),其中 wss 表示使用了 TLS 的 Websocket

普遍认为,WebSocket 的优点有如下几点:

1)较少的控制开销:在连接创建后,服务器和客户端之间交换数据时,用于协议控制的数据包头部相对较小;
2)更强的实时性:由于协议是全双工的,所以服务器可以随时主动给客户端下发数据。相对于 HTTP 请求需要等待客户端发起请求服务端才能响应,延迟明显更少;
3)保持连接状态:与 HTTP 不同的是,WebSocket 需要先创建连接,这就使得其成为一种有状态的协议,之后通信时可以省略部分状态信息;
4)更好的二进制支持:WebSocket 定义了二进制帧,相对 HTTP,可以更轻松地处理二进制内容;
5)可以支持扩展:WebSocket 定义了扩展,用户可以扩展协议、实现部分自定义的子协议。

依赖

        <dependency>
            <groupId>org.springframeworkgroupId>
            <artifactId>spring-websocketartifactId>
        dependency>
        <dependency>
            <groupId>javax.websocketgroupId>
            <artifactId>javax.websocket-apiartifactId>
        dependency>

配置类

配置类里将我们写的规则类注册进去

WebSocketHandlerRegistry 的 addHandler 方法是将 WebSocketHandler 和对应的 URL 路径注册到 WebSocketHandlerRegistry 中,以供后续的 WebSocket 连接请求进行匹配和处理。当有 WebSocket 连接请求到达时,WebSocketHandlerRegistry 会根据请求的 URL 路径找到对应的 WebSocketHandler,并将请求交给该 Handler 进行处理

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    @Autowired
    private MyWsHandler myWsHandler;

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(myWsHandler, "/ws/voice/remind.json")
                //允许跨域
                .setAllowedOrigins("*");
    }
}

实现

继承 AbstractWebSocketHandler,实现方法,即可自定义在连接、传入、中断等时候分别可以执行的操作。这里我们选择继承其子类 TextWebSocketHandler 来处理文本消息

@Component
@Slf4j
public class MyWsHandler extends TextWebSocketHandler {

    /**
     * 这个是管理 session 的类
     */
    @Resource
    WsSessionManager wsSessionManager;

    /**
     * 定义了客户端链接服务器的时候会执行什么操作
     */
    @Override
    public void afterConnectionEstablished(WebSocketSession session) {
        log.info("建立ws连接");
        wsSessionManager.add(session.getId(), session);
    }

    /**
     * handleTextMessage 方法是用来处理收到的文本消息的。当客户端发送文本消息到服务器端时,服务器端会调用 handleTextMessage 方法来处理该消息。在该方法中,开发者可以编写自定义的业务逻辑来处理文本消息,例如解析消息内容、调用其他服务进行处理等。同时,开发者还可以在该方法中向客户端发送文本消息,以实现双向通信
     */
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        log.info("发送文本消息");
        // 获得客户端传来的消息
        String payload = message.getPayload();
        log.info("server 接收到消息 " + payload);
        session.sendMessage(new TextMessage("server 发送给的消息 " + payload + ",发送时间:" + LocalDateTime.now().toString()));
    }

    /**
     * 出现错误与异常的时候执行的操作
     */
    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) {
        log.error("异常处理");
        wsSessionManager.removeAndClose(session.getId());
    }

    /**
     * 连接中断后执行的操作
     */
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
        log.info("关闭ws连接");
        wsSessionManager.removeAndClose(session.getId());
    }
}

我们能在建立连接的时候将一些信息放在 session 里,WebSocketSession::getAttributes 是一个获取 WebSocketSession 对象的属性的方法,这个方法返回一个 Map 对象,其中包含了 WebSocketSession 对象中所有的属性。这些属性是在 WebSocketSession 对象创建时通过 WebSocketHandler 的 afterConnectionEstablished 方法存储的

在afterConnectionEstablished方法中,WebSocketSession对象被创建并存储在内存中。此时,WebSocketSession对象的属性可以通过调用WebSocketSession的setAttribute方法进行设置。setAttribute方法接受两个参数,第一个参数是属性的名称,第二个参数是属性的值。

获取 session

我们的重头戏就是对 session 的操作,因为可以在用户登录登出时做记录,因此我们可以知道用户是否在线,从而实现在用户在线时对其发消息这个操作


/**
 * 管理 session 的类,完全可以在 MyWsHandler 中实现,但是我们应该尽可能将 SESSION_POOL 的操作与 handler 的定义剥离开来
 *
 * @author yifanxie.xie
 */
@Slf4j
@Component
public class WsSessionManager {
    /**
     * 保存连接 session 的地方
     */
    private static final ConcurrentHashMap<String, WebSocketSession> SESSION_POOL = new ConcurrentHashMap<>();

    /**
     * 添加 session
     *
     * @param key
     */
    public void add(String key, WebSocketSession session) {
        // 添加 session
        SESSION_POOL.put(key, session);
    }

    /**
     * 删除并同步关闭连接
     *
     * @param key
     */
    public void removeAndClose(String key) {
        WebSocketSession session = SESSION_POOL.remove(key);
        if (session != null) {
            try {
                // 关闭连接
                session.close();
            } catch (IOException e) {
                log.error("在关闭链接时出现异常", e);
            }
        }
    }

    /**
     * 获得 session
     *
     * @param key
     * @return
     */
    public WebSocketSession get(String key) {
        return SESSION_POOL.get(key);
    }
}

这里我们使用会话 id 来当做 map 的 key,但是在对特定用户发消息的时候谁会用 id 来对应用户啊。我们能在建立连接的时候用一些业务属性的唯一标识来作为 key,从而确定用户

你可能感兴趣的:(websocket,java,网络协议)