springboot集成websocke(介绍 +实现)

一、WebSocket简介

webSocket是什么:

1、WebSocket是一种在单个TCP连接上进行全双工通信的协议

2、WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据

3、在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输

4、WebSocket 是一种标准协议,用于在客户端和服务端之间进行双向数据传输。但它跟 HTTP 没什么关系,它是一种基于 TCP 的一种独立实现。

以前客户端想知道服务端的处理进度,要不停地使用 Ajax 进行轮询,让浏览器隔个几秒就向服务器发一次请求,这对服务器压力较高。另外一种轮询就是采用 long poll 的方式,这就跟打电话差不多,没收到消息就一直不挂电话,也就是说,客户端发起连接后,如果没消息,就一直不返回 Response 给客户端,连接阶段一直是阻塞的。

而 WebSocket 解决了 HTTP 的这几个难题。首先,当服务器完成协议升级后( HTTP -> WebSocket ),服务端可以主动推送信息给客户端,解决了轮询造成的同步延迟问题。由于 WebSocket 只需要一次 HTTP 握手,服务端就能一直与客户端保持通讯,直到关闭连接,这样就解决了服务器需要反复解析 HTTP 协议,减少了资源的开销。

WebSocket协议支持(在受控环境中运行不受信任的代码的)客户端与(选择加入该代码的通信的)远程主机之间进行全双工通信。用于此的安全模型是Web浏览器常用的基于原始的安全模式。 协议包括一个开放的握手以及随后的TCP层上的消息帧。 该技术的目标是为基于浏览器的、需要和服务器进行双向通信的(服务器不能依赖于打开多个HTTP连接(例如,使用XMLHttpRequest或和长轮询))应用程序提供一种通信机制。

 

websocket 是一个基于应用层的网络协议,建立在tcp 协议之上,和 http 协议可以说是兄弟的关系,但是这个兄弟有点依赖 http ,为什么这么说呢?我们都知道 HTTP 实现了三次握手来建立通信连接,实际上 websocket 的创始人很聪明,他不想重复的去造轮子,反正我兄弟已经实现了握手了,我干嘛还要重写一套呢?先让它去冲锋陷阵呢,我坐收渔翁之利不是更香 吗,所以一般来说,我们会先用 HTTP 先进行三次握手,再向服务器请求升级为websocket 协议,这就好比说,嘿兄弟你先去给我排个队占个坑位建个小房子,到时候我在把这房子改造成摩天大楼。而且一般来说 80 和 443 端口一般 web 服务端都会外放出去,这样可以有效的避免防火墙的限制。当然,你创建的 websocket 服务端进程的端口也需要外放出去。

很多人会想问,web开发 使用 HTTP 协议不是已经差不多够用了吗?为什么还要我再多学一种呢?这不是搞事情嘛,仔细想想,一门新技术的产生必然有原因的,如果没有需求,我们干嘛那么蛋疼去写那么多东西,就是因为 HTTP 这个协议有些业务需求支持太过于鸡肋了,从 HTTP 0.9 到现在的 HTTP3.0 ,HTTP协议可以说说是在普通的web开发领域已经是十分完善且高效的了,说这个协议养活了全球半数的公司也不为过吧,像 2.0 服务器推送技术,3.0 采用了 UDP 而放弃了原来的 TCP ,这些改动都是为了进一步提升协议的性能,然而大家现在还是基本使用的 HTTP 1.1 这个最为经典的协议, 也是让开发者挺尴尬的。

绝大多数的web开发都是应用层开发者,大多数都是基于已有的应用层去开发应用,可以说我们最熟悉、日常打交道最多的就是应用层协议了,底下 TCP/IP 协议我们基本很少会去处理,当然大厂可能就不一样了,自己弄一套协议也是正常的,这大概也是程序员和码农的区别吧,搬砖还是创新,差别还是很大的。网络这种分层协议的好处我在之前的文章也说过了,这种隔离性很方便就可以让我们基于原来的基础去拓展,具有较好的兼容性。

总的来说,它就是一种依赖HTTP协议的,支持全双工通信的一种应用层网络协议。

二、WebSocket产生背景

简单的说,WebSocket协议之前,双工通信是通过多个http链接来实现,这导致了效率低下。WebSocket解决了这个问题。下面是标准RFC6455中的产生背景概述。

长久以来, 创建实现客户端和用户端之间双工通讯的web app都会造成HTTP轮询的滥用: 客户端向主机不断发送不同的HTTP呼叫来进行询问。

这会导致一系列的问题:

  • 1.服务器被迫为每个客户端使用许多不同的底层TCP连接:一个用于向客户端发送信息,其它用于接收每个传入消息。
  • 2.有些协议有很高的开销,每一个客户端和服务器之间都有HTTP头。
  • 3.客户端脚本被迫维护从传出连接到传入连接的映射来追踪回复。

一个更简单的解决方案是使用单个TCP连接双向通信。 这就是WebSocket协议所提供的功能。 结合WebSocket API ,WebSocket协议提供了一个用来替代HTTP轮询实现网页到远程主机的双向通信的方法。

WebSocket协议被设计来取代用HTTP作为传输层的双向通讯技术,这些技术只能牺牲效率和可依赖性其中一方来提高另一方,因为HTTP最初的目的不是为了双向通讯。

三、WebSocket实现原理

在实现websocket连线过程中,需要通过浏览器发出websocket连线请求,然后服务器发出回应,这个过程通常称为“握手” 。在 WebSocket API,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。在此WebSocket 协议中,为我们实现即时服务带来了两大好处:

1. Header:互相沟通的Header是很小的-大概只有 2 Bytes。

2. Server Push:服务器的推送,服务器不再被动的接收到浏览器的请求之后才返回数据,而是在有新数据时就主动推送给浏览器。

四、WebSocket 优点

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

1)较少的控制开销:在连接创建后,服务器和客户端之间交换数据时,用于协议控制的数据包头部相对较小;

2)更强的实时性:由于协议是全双工的,所以服务器可以随时主动给客户端下发数据。相对于 HTTP 请求需要等待客户端发起请求服务端才能响应,延迟明显更少;

3)保持连接状态:与 HTTP 不同的是,WebSocket 需要先创建连接,这就使得其成为一种有状态的协议,之后通信时可以省略部分状态信息;

4)更好的二进制支持:WebSocket 定义了二进制帧,相对 HTTP,可以更轻松地处理二进制内容;

5)可以支持扩展:WebSocket 定义了扩展,用户可以扩展协议、实现部分自定义的子协议。

由于 WebSocket 拥有上述的优点,所以它被广泛地应用在即时通讯/IM、实时音视频、在线教育和游戏等领域。

对于前端开发者来说,要想使用 WebSocket 提供的强大能力,就必须先掌握 WebSocket API,下面带大家一起来认识一下 WebSocket API。

简单例子使用:

环境配置

开发环境配置为:

JDK 8

IntelliJ IDEA 2020.1.2 x64

服务端实现

本章节介绍服务端的实现。

1、引入Jar包

首先,在IDEA中创建一个Maven项目,在pom.xml文件中引入所需要的Jar包,如下所示:

<dependencies>

<dependency>

<groupId>org.springframework.boot</groupId>

        <artifactId>spring-boot-starter-web</artifactId>

    </dependency>

    <dependency>

        <groupId>org.springframework.boot</groupId>

        <artifactId>spring-boot-starter-websocket</artifactId>

    </dependency>

</dependencies>

一个是springboot的starter,另一个是springboot内部集成的websocket。

2、Springboot集成的Websocket

使用Springboot集成的websocket需要使用到以下几个类/接口:

WebSocketHandler:WebSocket消息以及生命周期事件的处理器。

WebSocketConfigurer:对WebSocket进行配置,包括配置拦截器、配置接口地址。

HttpSessionHandshakeInterceptor:拦截器,可以对Websocket通信过程中的请求进行拦截处理。

除了上述三个官方提供的类/接口之外,我们还需要实现一个WebSocket的包装类,用于为每一个WebSocket实例添加额外的信息,比如客户端的ID。在Spring内,WebSocket实例以WebSocketSession形式存在,每个session都代表了一个服务端与客户端的会话。

2.1、创建WebSocket包装类

import org.springframework.web.socket.WebSocketSession;

public class WebSocketBean {

    private WebSocketSession webSocketSession;

    private int clientId;

    public int getClientId() {

        return clientId;

    }

    public void setClientId(int clientId) {

        this.clientId = clientId;

    }

    

    public WebSocketSession getWebSocketSession() {

        return webSocketSession;

    }

    public void setWebSocketSession(WebSocketSession webSocketSession) {

        this.webSocketSession = webSocketSession;

    }

}

这里的包装类很简单,仅添加了一个客户端ID的属性,这里仅作为简单的示例。

2.2、实现WebSocketHandler接口

WebSocketHandler接口用于处理WebSocket的消息,Spring提供了一个抽象类AbstractWebSocketHandler实现了WebSocketHandler接口,因此我们可以直接继承抽象类,重写需要实现的方法即可。

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.web.socket.CloseStatus;

import org.springframework.web.socket.TextMessage;

import org.springframework.web.socket.WebSocketSession;

import org.springframework.web.socket.handler.AbstractWebSocketHandler;

import java.util.Map;

import java.util.concurrent.ConcurrentHashMap;

import java.util.concurrent.atomic.AtomicInteger;

public class MyWebsocketHandler extends AbstractWebSocketHandler {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    private static final Map<String, WebSocketBean> webSocketBeanMap;

    private static final AtomicInteger clientIdMaker;   //仅用用于标识客户端编号

    static {

        webSocketBeanMap = new ConcurrentHashMap<>();

        clientIdMaker = new AtomicInteger(0);

    }

    @Override

    public void afterConnectionEstablished(WebSocketSession session) throws Exception {

     //当WebSocket连接正式建立后,将该Session加入到Map中进行管理

        WebSocketBean webSocketBean = new WebSocketBean();

        webSocketBean.setWebSocketSession(session);

        webSocketBean.setClientId(clientIdMaker.getAndIncrement());

        webSocketBeanMap.put(session.getId(), webSocketBean);

    }

    @Override

    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {

     //当连接关闭后,从Map中移除session实例

        webSocketBeanMap.remove(session.getId());

    }

    @Override

    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {

     //传输过程中出现了错误

        if (session.isOpen()){

            session.close();

        }

        webSocketBeanMap.remove(session.getId());

    }

    @Override

    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {

     //处理接收到的消息

        logger.info("Received message from client[ID:" + webSocketBeanMap.get(session.getId()).getClientId() +

                "]; Content is [" + message.getPayload() + "].");

        TextMessage textMessage = new TextMessage("Server has received your message.");

        session.sendMessage(textMessage);

    }

}

实现的逻辑很简单,在每个方法都有了注释。值得注意的是,这里在handleTextMessage处理接收到的消息,表示处理接收到的字符串消息。除此之外,AbstractWebSocketHandler还提供了handleBinaryMessage以及handlePongMessage,前者表示处理二进制消息,而后者表示处理心跳数据包的信息。

2.3、实现WebSocket拦截器

WebSocket拦截器可以在Websocket连接建立之前的权限校验等功能,Spring提供了HttpSessionHandshakeInterceptor这个接口作为拦截器。

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.http.server.ServerHttpRequest;

import org.springframework.http.server.ServerHttpResponse;

import org.springframework.http.server.ServletServerHttpRequest;

import org.springframework.web.socket.WebSocketHandler;

import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;

import java.util.Map;

public class MyWebSocketInterceptor extends HttpSessionHandshakeInterceptor {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    @Override

    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {

        logger.info("[MyWebSocketInterceptor#BeforeHandshake] Request from " + request.getRemoteAddress().getHostString());

        if (request instanceof ServletServerHttpRequest){

            ServletServerHttpRequest serverHttpRequest = (ServletServerHttpRequest) request;

            String token = serverHttpRequest.getServletRequest().getHeader("token");

            //这里做一个简单的鉴权,只有符合条件的鉴权才能握手成功

            if ("token-123456".equals(token)){

                return super.beforeHandshake(request, response, wsHandler, attributes);

            }else {

                return false;

            }

        }

        return super.beforeHandshake(request, response, wsHandler, attributes);

    }

    @Override

    public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception ex) {

        logger.info("[MyWebSocketInterceptor#afterHandshake] Request from " + request.getRemoteAddress().getHostString());

    }

}

从代码可以看出,笔者在握手之前对请求进行了一个简单的校验,符合条件的请求才会进行下一步的握手。

2.4、对WebSocket进行配置

该步骤实现Springboot对WebSocket的支持,包括了配置接口地址、配置拦截器等。

import org.springframework.context.annotation.Bean;

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;

import org.springframework.web.socket.server.standard.ServerEndpointExporter;

@Configuration

@EnableWebSocket

public class WebSocketConfiguration implements WebSocketConfigurer {

    @Bean

    public MyWebSocketInterceptor webSocketInterceptor(){return new MyWebSocketInterceptor();}

    @Bean

    public ServerEndpointExporter serverEndpointExporter(){return new ServerEndpointExporter();}

    @Override

    public void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {

        webSocketHandlerRegistry.addHandler(new MyWebsocketHandler(), "/websocket").addInterceptors(webSocketInterceptor());

    }

}

从上面的代码看出,笔者将websocket的接口地址放在了“/websocket”上,当客户端访问“/websocket”时,就会尝试与服务端进行握手连接。

2.5、配置Application主类

import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;

import org.springframework.boot.web.servlet.ServletComponentScan;

@SpringBootApplication

@ServletComponentScan

public class MainApplication {

    public static void main(String[] args) {

        SpringApplication.run(MainApplication.class);

    }

}

客户端实现

本小节介绍WebSocket的客户端实现,这里使用Java所提供的WebSocket库来实现。在IDEA中新建一个工程:WebsocketDemoClient,用来保存我们的客户端代码。在pol.xml文件中引入:

<dependencies>

<dependency>

<groupId>org.java-websocket</groupId>

<artifactId>Java-WebSocket</artifactId>

<version>1.5.1</version>

</dependency>

<dependency>

     <groupId>org.slf4j</groupId>

     <artifactId>slf4j-simple</artifactId>

     <version>1.7.25</version>

     <scope>compile</scope>

    </dependency>

</dependencies>

1、创建Websocket client

WebsocketClient提供了连接到服务端的WebSocket的一切必要准备,我们只需要继承该类,重写所需要的方法来实现我们的业务逻辑,就能方便地使用WebSocket了。

import org.java_websocket.client.WebSocketClient;

import org.java_websocket.handshake.ServerHandshake;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import java.net.URI;

public class MyWebSocketClient extends WebSocketClient {

    

    private Logger logger = LoggerFactory.getLogger(getClass());

    

    public MyWebSocketClient(URI serverUri) {

        super(serverUri);

    }

    @Override

    public void onOpen(ServerHandshake serverHandshake) {

        logger.info("[MyWebSocketClient#onOpen]The WebSocket connection is open.");

    }

    @Override

    public void onMessage(String s) {

        logger.info("[MyWebSocketClient#onMessage]The client has received the message from server." +

                "The Content is [" + s + "]");

    }

    @Override

    public void onClose(int i, String s, boolean b) {

        logger.info("[MyWebSocketClient#onClose]The WebSocket connection is close.");

    }

    @Override

    public void onError(Exception e) {

        logger.info("[MyWebSocketClient#onError]The WebSocket connection is error.");

    }

}

从上述代码可以看出,实现逻辑很简单,这里主要是打印出各个方法执行时的情况。

2、使用WebSocketClient

下面创建Main函数来使用我们的WebSocketClient客户端,同时创建一个定时任务,使得客户端可以连续不间断地给服务端发送消息,代码如下所示:

import java.net.URI;

import java.util.Timer;

import java.util.TimerTask;

import java.util.concurrent.atomic.AtomicInteger;

public class Main {

    private static final AtomicInteger count = new AtomicInteger(0);

    public static void main(String[] args) {

        URI uri = URI.create("ws://127.0.0.1:8080/websocket");  //注意协议号为ws

        MyWebSocketClient client = new MyWebSocketClient(uri);

        client.addHeader("token", "token-123456");              //这里为header添加了token,实现简单的校验

        try {

            client.connectBlocking();   //在连接成功之前会一直阻塞

            Timer timer = new Timer();

            MyTimerTask timerTask = new MyTimerTask(client);

            timer.schedule(timerTask, 1000, 2000);

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

    }

    static class MyTimerTask extends TimerTask{

        private final MyWebSocketClient client;

        public MyTimerTask(MyWebSocketClient client){

            this.client = client;

        }

        @Override

        public void run() {

            client.send("Test message from client, the number is " + count.getAndIncrement());

        }

    }

}

运行结果

服务端和客户端的代码编写完毕后,我们来看一下结果。先运行服务端代码,将服务端应用程序部署于8080端口中,然后运行客户端代码,结果分别如下图所示:

运行结果正常!客户端与服务端建立了WebSocket链路,并实时发送消息到服务端中。

感谢大家的阅读,觉得有所帮助的朋友点点赞。

完整素材及全部代码

   代码已上传csdn,0积分下载,觉得这片博文有用请留下你的点赞,有问题的朋友可以一起交流讨论。

基于springboot实现websocket客户端和服务端
 

 

  • 24
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
SpringBoot+SpringSecurity+Vue中实现动态路由的过程如下: 1. 在后端(SpringBoot)中,首先需要定义一个权限表,用于存储所有的权限信息,包括权限名称、权限标识等。 2. 在前端(Vue)中,需要定义一个路由表,用于存储所有的路由信息,包括路由路径、组件名称等。 3. 后端需要提供一个接口,用于获取当前用户的权限列表。该接口会根据用户的角色查询对应的权限,并返回给前端。 4. 前端在登录成功后,会调用后端接口获取当前用户的权限列表,并将权限列表存储到本地(如localStorage或vuex)中。 5. 前端在路由跳转时,会根据当前用户的权限列表动态生成路由。可以通过遍历权限列表,根据权限标识匹配路由表中的路由信息,将匹配到的路由添加到路由表中。 6. 前端在生成路由后,需要使用Vue Router的addRoutes方法将动态生成的路由添加到路由表中。 7. 前端在路由跳转时,会根据用户的权限判断是否有权限访问该路由。可以通过导航守卫的beforeEach方法,在路由跳转前进行权限判断。 8. 后端可以使用Spring Security的注解对接口进行权限控制。可以通过在接口上添加注解,指定需要的权限才能访问该接口。 9. 后端接口调用时,可以通过从redis中获取当前用户的权限列表,并进行权限判断。 10. 前端和后端通过接口交互,实现动态路由的权限控制。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小蜜蜂vs码农

你的鼓励将是我创作的最大动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值