WebSocket背景知识及简单实现-Java

一、背景知识

1.消息传输

为什么张三不能直接发消息给李四,而需要通过服务器中转?

①NAT背景下,不在同一局域网的两个内网的设备无法直接通信。IPv4地址不够用,无法给每个设备分配一个唯一的IP。张三,李四都是普通客户端,设备大概率没有外网IP,只有内网IP,若没有外网IP则不能被直接访问到,除非张三李四在同一局域网中。

②通过服务器中转,容易在服务器端记录历史消息,方便查询历史记录

IPv4地址不够用解决方法

(1)动态分配IP

(2)NAT网络地址转换:一个设备在进行上网时,IP数据报中的IP地址就会被NAT设备(通常是路由器)进行自动修改。缺点是网络环境复杂。

(3)IPv6

2、基于http实现消息的实时传输

**张三和李四不能直接通信(NAT)**必须是张三发消息给服务器,服务器转发给李四(服务器有外网IP,张三和李四都能访问到)张三(客户端)发消息(请求)给服务器,服务器主动发送响应给李四(客户端)

一般http系列的程序,第一种情况:客户端发起请求,服务器返回响应,客户端不发请求,服务器就不返回响应;第二种:服务器推送数据给客户端(不太支持)

第二种情况可以通过轮询实现:李四周期性地给服务器发送请求,如果有消息就获取,没有就sleep。但此种方式也会产生问题:消耗更多的系统资源,接收方在等待的过程中需要频繁地给服务器发送请求,这些请求中大部分是空请求;获取消息不够及时,需要等到下个请求才能拿到数据。提高轮询的频率,获取消息及时,但系统资源消耗更多;降低轮询的频率,系统资源消耗降低,但获取消息就不及时。

引入WebSocket解决消息推送问题。

3、基于WebSocket实现消息的实时传输

3.1简介

WebSocket是一个应用层协议,和http的地位是对等的,都是基于传输层的TCP实现的一个广泛被使用的应用层协议。WebSocket协议可以实现服务器给客户端主动推送数据(本身TCP就能让服务器给客户端主动推送数据,三次握手结束后客户端/服务器主动发消息都可以,业务角度上客户端主动发消息更常见)

实现【张三发消息,李四收到】的思路:服务器统一管理所有在线客户端的session,收到消息后遍历转发。先用集合存储所有的会话;然后约定格式,由服务器解析后只转发给指定客户端。

3.2报文格式

(1)FIN:是否要关闭WebSocket(此处的FIN不是TCP的FIN,只是需要通过FIN触发应用协议断开连接的操作)

(2)RSV:reserve保留位

(3)opcode:操作码,描述当前WebSocket数据帧的作用(取值为0x1表示为文本数据,0x2表示为二进制数据)

(4)MASK:是否开启掩码操作,避免缓冲区溢出

(5)payload length:载荷长度。2^7(0~127),最多能保存127字节。共三种模式,7bit,16bit,64bit。

(6)payload data:载荷数据

3.3握手过程

接下来浏览器和服务器之间相当于建立好了websocket的连接,可以使用websocket进行数据传输

!websocket基于http协议实现(❌️)

二、简单实现

1、实现方法

1、直接使用Tomcat提供的原生websocket API

2、使用Spring提供的websocket API(此处用这种)

2、举例:基于Spring的websocket API编写简单的hello world

Spring Web(提供 HTTP 基础能力,必须选)

WebSocket(核心依赖,直接提供 TextWebSocketHandler 类)

2.1服务器代码

(1)创建一个类,继承自TextWebSocketHandler (Spring内置的类)重写父类方法,处理websocket中的各个通信流程,同时加上**@Component**注解,注册到Spring中。

java 复制代码
package com.gitee.zhouzhou.testwebsocketapi.api;

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;

@Component
public class TestWebSocketAPI extends TextWebSocketHandler {
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        //websocket连接成功建立后自动调用
        System.out.println("TestAPI 连接成功!");
    }

    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        //websocket收到消息后自动调用
        System.out.println("TestAPI 收到消息!"+message.toString());
        //session会话记录通信双方是谁,session中持有websocket的通信连接
        session.sendMessage(message);
    }

    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        //websocket连接异常后自动调用
        System.out.println("TestAPI 连接异常!");
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        //websocket连接正常关闭后自动调用
        System.out.println("TestAPI 连接断开!");
    }

}

(2)把上述实例注册到Spring配置中,配置路由(哪个路径对应到上述的handler)创建另外一个类WebSocketConfig,实现websocket的配置interface

java 复制代码
package com.gitee.zhouzhou.testwebsocketapi.config;

import com.gitee.zhouzhou.testwebsocketapi.api.TestWebSocketAPI;
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;

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
    @Autowired
    private TestWebSocketAPI testWebSocketAPI;
    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        //把刚才创建好的handler类注册到具体的路径上
        //浏览器 websocket的请求路径是"/test"时,调用TestWebSocketAPI类中的方法
        registry.addHandler(testWebSocketAPI,"/test");
    }
}

2.2客户端代码

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <input type="text" id="message">
    <button id="send-button">发送</button>

    <script>
        //创建websocket实例
        let websocket=new WebSocket("ws://127.0.0.1:8080/test");//ws:websocket协议缩写

        //注册回调函数
        websocket.onopen=function(){
            //连接成功,自动执行
            console.log("websocket 连接成功!");
        }
        websocket.onclose=function(){
            //连接断开,自动执行
            console.log("websocket 连接断开!");
        }
        websocket.onerror=function(){
            //连接异常,自动执行
            console.log("websocket 连接异常!");
        }
        websocket.onmessage=function(e){
            //收到消息,自动执行
            console.log("websocket 收到消息!"+e.data);
        }

        //注册按钮点击事件
        let messageInput=document.querySelector('#message');
        let sendButton=document.querySelector('#send-button');
        sendButton.onclick=function(){
            console.log('发送消息:'+messageInput.value);
            websocket.send(messageInput.value);
        }
    </script>
</body>
</html>

2.3测试

相关推荐
SL_staff35 分钟前
3天上线OKR系统:一名HR与1名工程师如何用JVS完成全栈交付
java·低代码·开源
wzdark2 小时前
基于链表的内存池设计与内存复用机制4
java·数据结构·链表
cnkeysky2 小时前
idea 中的 maven 项目重新加载子模块
java·idea
Escalating_xu2 小时前
【System V 信号量】从 P/V 原语到 Builder 封装:写出可控、可清理的进程互斥组件
java·linux·开发语言·jvm
今天AI了吗2 小时前
什么是 AI Agent?它与直接调用大模型 API 有何区别
java·网络·人工智能·架构·java-ee
隐退山林2 小时前
JavaEE进阶:SpringAOP
java·java-ee
Wang's Blog3 小时前
Java框架快速入门: Spring Security+OAuth2之方法级安全注解
java·安全·spring
JavacKaka3 小时前
LiteFlow规则引擎实战博客-2026-09-09
java
学渣超3 小时前
记一次复杂业务功能重构——从过程式长链路,到模板方法构建体系
java·架构·代码规范
Bs_MoneyMagnet3 小时前
基于springboot+vue的旅游景区点评系统的设计与实现 源码+文档
java·vue.js·spring boot·后端·spring·旅游