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>开启socket</div>
<p>【操作】:<div>发送消息</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


原网址: 访问
创建于: 2020-12-02 17:51:50
目录: default
标签: 无

请先后发表评论
  • 最新评论
  • 总共0条评论