这几天居家办公(居委会阿姨:每次流调咋都有你,草),闲暇时间写了下基于java的webservice soap 和 rpc socket的简单实现,其中一个是基于http协议,一个基于tcp/ip协议,一个是应用层协议,一个是传输层协议,在此之外,还想再看一个协议,就是websocket协议,这也是一个应用层协议,区分起来还挺麻烦的,所以在这里做个概念的梳理。

1、http协议和websocket协议的区别

HTTP 协议有一个缺陷:通信只能由客户端发起,做不到服务器主动向客户端推送信息。

WebSocket 协议 它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种

HTTP协议是非持久化的,单向的网络协议,在建立连接后只允许浏览器向服务器发出请求后,服务器才能返回相应的数据。

在WebSocket中,只需要服务器和浏览器通过HTTP协议进行一个握手的动作,然后单独建立一条TCP的通信通道进行数据的传送。做通讯持久化。

总结下来看,websocket协议是建立在TCP之上,可持久化的一种双向通信协议

2、websocket和socket的关系

1.PNG

RPC原理和基于Java的基本RPC实现使用了socket简单实现了远程rpc的调用,socket本身并不是协议,如上图是基于TCP/IP协议的一层封装接口。所以他是在传输层的,而websocket则是应用层协议了。这点区别很明显。看着两者很有关系,但是使用上好像完全不同

3、SpringBoot整合WebSocket简单实例

模拟下websocket协议的持久化和双向通信:

引依赖、配置文件:

<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>
        <!-- thymeleaf 模板的配置 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <!-- spring websocket的配置 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
            <version>2.2.5.RELEASE</version>
        </dependency>
    </dependencies>
#thymeleaf start
#spring.thymeleaf.mode=HTML5
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.servlet.content-type=text/html
#开发时关闭缓存,不然没法看到实时页面
spring.thymeleaf.cache=false
spring.thymeleaf.prefix=classpath:/templates/
#thymeleaf end


server.port=8082

配置类和控制器整合thymeleaf:

package com.jin.config;

/**
 * @author jinyunlong
 * @date 2022/1/14 16:48
 * @profession ICBC锅炉房保安
 */

import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;


@Configuration
@EnableWebSocketMessageBroker
public class WebsocketConfig extends AbstractWebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        //启用/userTest,/topicTest,两个消息前缀
        config.enableSimpleBroker("/userTest","/topicTest");
        //如果不设置下面这一句,用convertAndSendToUser来发送消息,前端订阅只能用/user开头。
        config.setUserDestinationPrefix("/userTest");
        //客户端(html等)向服务端发送消息的前缀
        config.setApplicationDestinationPrefixes("/app");
    }
    @Override
    public void registerStompEndpoints(StompEndpointRegistry stompEndpointRegistry) {
        //客户端和服务端进行连接的endpoint
        stompEndpointRegistry.addEndpoint("/websocket-endpoint").setAllowedOrigins("*").withSockJS();
    }
}

package com.jin.controller;

/**
 * @author jinyunlong
 * @date 2022/1/14 16:49
 * @profession ICBC锅炉房保安
 */

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

@Controller
@EnableScheduling
public class WebsocketMsgController {

    @Autowired
    private SimpMessagingTemplate messagingTemplate;

    @GetMapping("/")
    public String index() {
        return "index";
    }

    /**
     * index.html将message发送给后端,后端再将消息重组后发送到/topicTest/web-to-server-to-web
     * @param message
     * @return
     * @throws Exception
     */
    @MessageMapping("/send")
    @SendTo("/topicTest/web-to-server-to-web")
    public String send(String message) throws Exception {
        DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return "服务器将原消息返回: "+df.format(new Date())+" :" + message;
    }

    /**
     * 最基本的服务器端主动推送消息给前端
     * @return
     * @throws Exception
     */
    @Scheduled(fixedRate = 1000)
    public String serverTime() throws Exception {
        // 发现消息
        DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        messagingTemplate.convertAndSend("/topicTest/servertime", df.format(new Date()));
        return "servertime";
    }

    /**
     * 以下面这种方式发送消息,前端订阅消息的方式为: stompClient.subscribe('/userTest/hzb/info'
     * @return
     * @throws Exception
     */
    @Scheduled(fixedRate = 1000)
    public String serverTimeToUser() throws Exception {
        // 发现消息
        DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        //这里虽然没有指定发送前缀为/userTest,但是在WebsocketConfig.java中设置了config.setUserDestinationPrefix("/userTest"),
        //否则默认为/user
        messagingTemplate.convertAndSendToUser("hzb","/info", df.format(new Date()));
        return "serverTimeToUser";
    }
}

前台展示页面,有连接websocket、断开websocket、从服务器端推消息给前端、前端送数据给服务器端后返显数据4个功能。

<!DOCTYPE html>
<html>
<head>
    <title>玩转spring boot——websocket</title>
    <script src="//cdn.bootcss.com/angular.js/1.5.6/angular.min.js"></script>
    <script src="https://cdn.bootcss.com/sockjs-client/1.1.4/sockjs.min.js"></script>
    <script src="https://cdn.bootcss.com/stomp.js/2.3.3/stomp.min.js"></script>
    <script type="text/javascript">
        var stompClient = null;

        var app = angular.module('app', []);
        app.controller('MainController', function($rootScope, $scope, $http) {

            $scope.data = {
                connected : false,
                sendMessage : '',
                receivMessages : []
            };

            //连接
            $scope.connect = function() {
                var socket = new SockJS('/websocket-endpoint');
                stompClient = Stomp.over(socket);
                stompClient.connect({}, function(frame) {
                    // 订阅后端主动推消息到前端的topic
                    stompClient.subscribe('/topicTest/servertime', function(r) {
                        $scope.data.time = '当前服务器时间:' + r.body;
                        $scope.data.connected = true;
                        $scope.$apply();
                    });
                    // 阅后端主动推消息到前端的topic,只有指定的用户(hzb)收到的的消息
                    stompClient.subscribe('/userTest/hzb/info', function(r) {
                        $scope.data.hzbtime = '当前服务器时间:' + r.body;
                        $scope.data.connected = true;
                        $scope.$apply();
                    });
                    // 订阅前端发到后台,后台又将消息返回前台的topic
                    stompClient.subscribe('/topicTest/web-to-server-to-web', function(msg) {
                        $scope.data.receivMessages.push(msg.body);
                        $scope.data.connected = true;
                        $scope.$apply();
                    });


                    $scope.data.connected = true;
                    $scope.$apply();
                });
            };

            $scope.disconnect = function() {
                if (stompClient != null) {
                    stompClient.disconnect();
                }
                $scope.data.connected = false;
            }

            $scope.send = function() {
                stompClient.send("/app/send", {}, JSON.stringify({
                    'message' : $scope.data.sendMessage
                }));
            }
        });
    </script>
</head>
<body ng-app="app" ng-controller="MainController">
 
  <h2>websocket示例</h2> 
  <label>WebSocket连接状态:</label>
  <button type="button" ng-disabled="data.connected" ng-click="connect()">连接</button>
  <button type="button" ng-click="disconnect()" ng-disabled="!data.connected">断开</button>
  <br/>
  <br/>
  <div ng-show="data.connected">
    <h4>以下是websocket的服务端主动推送消息到页面的例子</h4>
        <label>{{data.time}}</label> <br/> <br/>
</div>
  <div ng-show="data.connected">
    <h4>以下是websocket的服务端主动推送消息到页面的例子,只有hzb这个用户收到</h4>
        <label>{{data.hzbtime}}</label> <br/> <br/>
</div>
  <div ng-show="data.connected">
    <h4>以下是websocket的客户端发消息到服务端,服务端再将该消息返回到客户端(页面)的例子</h4>
    <input type="text" ng-model="data.sendMessage" placeholder="请输入内容..." />
        <button ng-click="send()" type="button">发送</button>
        <br/>
        <table>
          <thead>
        <tr>
            <th>消息内容:</th>
          </tr>
          </thead>
          <tbody>
        <tr ng-repeat="messageContent in data.receivMessages">
              <td>{{messageContent}}</td>
          </tr>
          </tbody>
        </table>
</div>
</body>
</html>

测试结果,双向通讯和持久化均可实现:

2.PNG

参考文章:

1、【转】 springboot+websocket示例

2、WebSocket介绍和Socket的区别

3、Http、Socket、WebSocket之间联系与区别

4、一文吃透 WebSocket 原理


标题:WebSocket原理+SpringBoot整合WebSocket简单实例
作者:jyl
地址:http://jinyunlong.xyz/articles/2022/01/14/1642152203893.html