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测试

相关推荐
lld9510271 小时前
(一)云回测:量化策略上线前的必经之路
java·服务器·数据库
云云只是个程序马喽1 小时前
海外短剧系统开发_云微传媒:多语言短剧平台定制与变现解决方案
java·php
plainGeekDev1 小时前
RecyclerView.Adapter → ListAdapter
java·kotlin·gradle
J2虾虾2 小时前
Spring AI Alibaba - 人工介入(Human-in-the-Loop)
java·人工智能·spring
Old Uncle Tom2 小时前
Harness Engineering 综述
java·开发语言·数据库
星原望野2 小时前
JAVA:策略模式的实战使用
java·开发语言·策略模式
LJianK12 小时前
java多态
java·开发语言·python
z落落2 小时前
C# 构造函数(无参/有参/重载/this)+析构函数(终结器)|GC 垃圾回收
java·开发语言·c#
武子康2 小时前
Java-12 深入浅出 MyBatis 二级缓存详解:跨 SqlSession 共享与失效机制
java·后端