这几天居家办公(居委会阿姨:每次流调咋都有你,草),闲暇时间写了下基于java的webservice soap 和 rpc socket的简单实现,其中一个是基于http协议,一个基于tcp/ip协议,一个是应用层协议,一个是传输层协议,在此之外,还想再看一个协议,就是websocket协议,这也是一个应用层协议,区分起来还挺麻烦的,所以在这里做个概念的梳理。
1、http协议和websocket协议的区别
HTTP 协议有一个缺陷:通信只能由客户端发起,做不到服务器主动向客户端推送信息。
WebSocket 协议 它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。
HTTP协议是非持久化的,单向的网络协议,在建立连接后只允许浏览器向服务器发出请求后,服务器才能返回相应的数据。
在WebSocket中,只需要服务器和浏览器通过HTTP协议进行一个握手的动作,然后单独建立一条TCP的通信通道进行数据的传送。做通讯持久化。
总结下来看,websocket协议是建立在TCP之上,可持久化的一种双向通信协议。
2、websocket和socket的关系
在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>
测试结果,双向通讯和持久化均可实现:
参考文章:
3、Http、Socket、WebSocket之间联系与区别
标题:WebSocket原理+SpringBoot整合WebSocket简单实例
作者:jyl
地址:http://jinyunlong.xyz/articles/2022/01/14/1642152203893.html