zoukankan      html  css  js  c++  java
  • SpringBoot整合WebSocket实现前后端互推消息

      小编写这篇文章是为了记录实现WebSocket的过程,受不了啰嗦的同学可以直接看代码。

      前段时间做项目时设计了一个广播的场景,具体业务不再赘述,最终要实现的效果就是平台接收到的信息实时发布给所有的用户,其实就是后端主动向前端广播消息。

    这样的场景可以让前端轮询实现,但是要达到接近实时获取信息的效果就需要前端短周期的轮询,HTTP请求包含较长的头部,其中真正有效的数据可能只是很小的一

    部分,显然这样会浪费很多的带宽等资源,周期越短服务器压力越大,如果用户量太大的话就杯具了。所以小编就想到了WebSocket,可以完美实现需求。

      

      1、什么是WebSocket

      WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向

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

      在 WebSocket API 中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。HTML5 定

    义的 WebSocket 协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。

      2、实现原理

      可以看到,浏览器通过 JavaScript 向服务器发出建立 WebSocket 连接的请求,连接建立以后,客户端和服务器端就可以通过 TCP 连接直接交换数据。第一次握手是基

    于HTTP协议实现的,当获取 Web Socket 连接后,就可以通过 send() 方法来向服务器发送数据,并通过 onmessage 事件来接收服务器返回的数据。

      3、具体实现

      WebSocket的优点不言而喻,下面直接上代码。

      1、首先创建一个springboot项目,网上教程很多,也可以参考楼主的创建SpringBoot项目,很简单,最终的目录结构如下:

      2、项目的pom.xml如下:

      

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.2.5.RELEASE</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
        <groupId>com.winmine</groupId>
        <artifactId>WebSocket</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>WebSocket</name>
        <description>Demo project for Spring Boot</description>
    
        <properties>
            <java.version>1.8</java.version>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-thymeleaf</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-websocket</artifactId>
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
                <version>1.2.46</version>
            </dependency>
    
        </dependencies>
    
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    
    </project>
    View Code

      引入WebSocket的标签就是:

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

      3、application.properties中配置端口号,楼主的是22599的端口号。

    server.port=22599

      4、配置类

      

    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.socket.server.standard.ServerEndpointExporter;
    
    @Configuration
    public class WebSocketConfig {
        /**
         * ServerEndpointExporter 作用
         *
         * 这个Bean会自动注册使用@ServerEndpoint注解声明的websocket endpoint
         *
         * @return
         */
        @Bean
        public ServerEndpointExporter serverEndpointExporter() {
            return new ServerEndpointExporter();
        }
    }

      5、核心类

      

    
    
    package com.winmine.WebSocket.service;

    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.concurrent.ConcurrentHashMap;
    import java.util.concurrent.atomic.AtomicInteger;

    @ServerEndpoint("/webSocket/{sid}")
    @Component
    public class WebSocketServer {
    //静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
    private static AtomicInteger onlineNum = new AtomicInteger();

    //concurrent包的线程安全Set,用来存放每个客户端对应的WebSocketServer对象。
    private static ConcurrentHashMap<String, Session> sessionPools = new ConcurrentHashMap<>();

    //发送消息
    public void sendMessage(Session session, String message) throws IOException {
    if(session != null){
    synchronized (session) {
    // System.out.println("发送数据:" + message);
    session.getBasicRemote().sendText(message);
    }
    }
    }
    //给指定用户发送信息
    public void sendInfo(String userName, String message){
    Session session = sessionPools.get(userName);
    try {
    sendMessage(session, message);
    }catch (Exception e){
    e.printStackTrace();
    }
    }

    //建立连接成功调用
    @OnOpen
    public void onOpen(Session session, @PathParam(value = "sid") String userName){
    sessionPools.put(userName, session);
    addOnlineCount();
    System.out.println(userName + "加入webSocket!当前人数为" + onlineNum);
    try {
    sendMessage(session, "欢迎" + userName + "加入连接!");
    } catch (IOException e) {
    e.printStackTrace();
    }
    }

    //关闭连接时调用
    @OnClose
    public void onClose(@PathParam(value = "sid") String userName){
    sessionPools.remove(userName);
    subOnlineCount();
    System.out.println(userName + "断开webSocket连接!当前人数为" + onlineNum);
    }

    //收到客户端信息
    @OnMessage
    public void onMessage(String message) throws IOException{
    message = "客户端:" + message + ",已收到";
    System.out.println(message);
    for (Session session: sessionPools.values()) {
    try {
    sendMessage(session, message);
    } catch(Exception e){
    e.printStackTrace();
    continue;
    }
    }
    }

    //错误时调用
    @OnError
    public void onError(Session session, Throwable throwable){
    System.out.println("发生错误");
    throwable.printStackTrace();
    }

    public static void addOnlineCount(){
    onlineNum.incrementAndGet();
    }

    public static void subOnlineCount() {
    onlineNum.decrementAndGet();
    }

    }
     

      6、在Controller中跳转页面

    import com.winmine.WebSocket.service.WebSocketServer;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.ResponseBody;
    import org.springframework.web.servlet.ModelAndView;
    
    import java.util.HashMap;
    import java.util.Map;
    
    @Controller
    public class SocketController {
    
        @Autowired
        private WebSocketServer webSocketServer;
    
        @RequestMapping("/index")
        public String index() {
            return "index";
        }
    
        @GetMapping("/webSocket")
        public ModelAndView socket() {
            ModelAndView mav=new ModelAndView("/webSocket");
    //        mav.addObject("userId", userId);
            return mav;
        }
    
    
    }

      7、前端代码在webSocket.html中:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>WebSocket</title>
    
    </head>
    <body>
    <h3>hello socket</h3>
    <p>【userId】:<div><input id="userId" name="userId" type="text" value="10"></div>
    <p>【toUserId】:<div><input id="toUserId" name="toUserId" type="text" value="20"></div>
    <p>【toUserId】:<div><input id="contentText" name="contentText" type="text" value="hello websocket"></div>
    <p>操作:<div><a onclick="openSocket()">开启socket</a></div>
    <p>【操作】:<div><a onclick="sendMessage()">发送消息</a></div>
    </body>
    <script>
        
    
        var socket;
        function openSocket() {
            if(typeof(WebSocket) == "undefined") {
                console.log("您的浏览器不支持WebSocket");
            }else{
                console.log("您的浏览器支持WebSocket");
                //实现化WebSocket对象,指定要连接的服务器地址与端口  建立连接
                var userId = document.getElementById('userId').value;
                // var socketUrl="ws://127.0.0.1:22599/webSocket/"+userId;
                var socketUrl="ws://192.168.0.231:22599/webSocket/"+userId;
                console.log(socketUrl);
                if(socket!=null){
                    socket.close();
                    socket=null;
                }
                socket = new WebSocket(socketUrl);
                //打开事件
                socket.onopen = function() {
                    console.log("websocket已打开");
                    //socket.send("这是来自客户端的消息" + location.href + new Date());
                };
                //获得消息事件
                socket.onmessage = function(msg) {
                    var serverMsg = "收到服务端信息:" + msg.data;
                    console.log(serverMsg);
                    //发现消息进入    开始处理前端触发逻辑
                };
                //关闭事件
                socket.onclose = function() {
                    console.log("websocket已关闭");
                };
                //发生了错误事件
                socket.onerror = function() {
                    console.log("websocket发生了错误");
                }
            }
        }
        function sendMessage() {
            if(typeof(WebSocket) == "undefined") {
                console.log("您的浏览器不支持WebSocket");
            }else {
                // console.log("您的浏览器支持WebSocket");
                var toUserId = document.getElementById('toUserId').value;
                var contentText = document.getElementById('contentText').value;
                var msg = '{"toUserId":"'+toUserId+'","contentText":"'+contentText+'"}';
                console.log(msg);
                socket.send(msg);
            }
        }
    
        </script>
    </html>

      

      完成以上工作,就可以启动项目测试了。

      在浏览器登录系统,http://127.0.0.1:22599/webSocket

       开启socket并发送信息,后端打印:

       前端控制台打印:

       WebSocket跑通了,这是最基本的案例,把它放入自己的项目中可以按照具体业务进行改进

      PS:在开发中楼主发现部分IE浏览器的版本建立webSocket时失败,具体问题及解决方法请查看文章:

    IE浏览器连接WebSocket报错:java.lang.IllegalArgumentException: Invalid character found in the request target. The valid characters are defined in RFC 7230 and RFC 3986

      

  • 相关阅读:
    B+树Java代码实现以及测试
    TreeMap核心源码实现解析
    B树Java代码实现以及测试
    二叉树BinaryTree构建测试(无序)
    HashMap、HashTable差异详解
    TreeMap源码实现类中文全解析
    注解Annotation原理详解及其应用示例
    SpringCloud之Config配置中心+BUS消息总线原理及其配置
    在浏览器输入 URL 回车之后发生了什么
    SpringCloud之Zuul网关原理及其配置
  • 原文地址:https://www.cnblogs.com/JohanChan/p/12522001.html
Copyright © 2011-2022 走看看