WebSocket跟常规的http协议的区别和优缺点这里大概描述一下

一、websocket与http

http协议是用在应用层的协议,他是基于tcp协议的,http协议建立链接也必须要有三次握手才能发送信息。http链接分为短链接,长链接,短链接是每次请求都要三次握手才能发送自己的信息。即每一个request对应一个response。长链接是在一定的期限内保持链接。保持TCP连接不断开。客户端与服务器通信,必须要有客户端发起然后服务器返回结果。客户端是主动的,服务器是被动的。 
WebSocket是HTML5中的协议, 他是为了解决客户端发起多个http请求到服务器资源浏览器必须要经过长时间的轮训问题而生的,他实现了多路复用,他是全双工通信。在webSocket协议下客服端和浏览器可以同时发送信息。

二、HTTP的长连接与websocket的持久连接

HTTP1.1的连接默认使用长连接(persistent connection),

即在一定的期限内保持链接,客户端会需要在短时间内向服务端请求大量的资源,保持TCP连接不断开。客户端与服务器通信,必须要有客户端发起然后服务器返回结果。客户端是主动的,服务器是被动的。

在一个TCP连接上可以传输多个Request/Response消息对,所以本质上还是Request/Response消息对,仍然会造成资源的浪费、实时性不强等问题。

如果不是持续连接,即短连接,那么每个资源都要建立一个新的连接,HTTP底层使用的是TCP,那么每次都要使用三次握手建立TCP连接,即每一个request对应一个response,将造成极大的资源浪费。

长轮询,即客户端发送一个超时时间很长的Request,服务器hold住这个连接,在有新数据到达时返回Response

websocket的持久连接  只需建立一次Request/Response消息对,之后都是TCP连接,避免了需要多次建立Request/Response消息对而产生的冗余头部信息。

Websocket只需要一次HTTP握手,所以说整个通讯过程是建立在一次连接/状态中,而且websocket可以实现服务端主动联系客户端,这是http做不到的。

springboot集成websocket的不同实现方式:

pom添加依赖

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
 因涉及到js连接服务端,所以也写了对应的html,这里集成下thymeleaf模板,前后分离的项目这一块全都是前端做的

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
配置文件:

server:
port: 8885

#添加Thymeleaf配置
thymeleaf:
cache: false
prefix: classpath:/templates/
suffix: .html
mode: HTML5
encoding: UTF-8
content-type: text/html

1:自定义WebSocketServer,使用底层的websocket方法,提供对应的onOpen、onClose、onMessage、onError方法

1.1:添加webSocketConfig配置类

/**
* 开启WebSocket支持
* Created by huiyunfei on 2019/5/31.
*/
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
1.2:添加webSocketServer服务端类

package com.example.admin.web;

/**
* Created by huiyunfei on 2019/5/31.
*/

@ServerEndpoint("/websocket/{sid}")
@Component
@Slf4j
public class WebSocketServer {
//静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
private static int onlineCount = 0;
//concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<WebSocketServer>();

//与某个客户端的连接会话,需要通过它来给客户端发送数据
private Session session;

//接收sid
private String sid="";

*/
/**
* 连接建立成功调用的方法*//*
@OnOpen
public void onOpen(Session session, @PathParam("sid") String sid) {
this.session = session;
webSocketSet.add(this); //加入set中
addOnlineCount(); //在线数加1
log.info("有新窗口开始监听:"+sid+",当前在线人数为" + getOnlineCount());
this.sid=sid;
try {
sendMessage("连接成功");
} catch (IOException e) {
log.error("websocket IO异常");
}
}
*/
/**
* 连接关闭调用的方法
*//*
@OnClose
public void onClose() {
webSocketSet.remove(this); //从set中删除
subOnlineCount(); //在线数减1
log.info("有一连接关闭!当前在线人数为" + getOnlineCount());
}
*/
/**
* 收到客户端消息后调用的方法
*
* @param message 客户端发送过来的消息*//*
@OnMessage
public void onMessage(String message, Session session) {
log.info("收到来自窗口"+sid+"的信息:"+message);
//群发消息
for (WebSocketServer item : webSocketSet) {
try {
item.sendMessage(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
*/
/**
*
* @param session
* @param error
*//*
@OnError
public void onError(Session session, Throwable error) {
log.error("发生错误");
error.printStackTrace();
}
*/
/**
* 实现服务器主动推送
*//*
public void sendMessage(String message) throws IOException {
this.session.getBasicRemote().sendText(message);
}
*/
/**
* 群发自定义消息
* *//*
public static void sendInfo(String message,@PathParam("sid") String sid) throws IOException {
log.info("推送消息到窗口"+sid+",推送内容:"+message);
for (WebSocketServer item : webSocketSet) {
try {
//这里可以设定只推送给这个sid的,为null则全部推送
if(sid==null) {
item.sendMessage(message);
}else if(item.sid.equals(sid)){
item.sendMessage(message);
}
} catch (IOException e) {
continue;
}
}
}
public static synchronized int getOnlineCount() {
return onlineCount;
}
public static synchronized void addOnlineCount() {
WebSocketServer.onlineCount++;
}
public static synchronized void subOnlineCount() {
WebSocketServer.onlineCount--;
}
public static CopyOnWriteArraySet<WebSocketServer> getWebSocketSet() {
return webSocketSet;
}
}
1.3:添加对应的controller

@Controller
@RequestMapping("/system")
public class SystemController {
//页面请求
@GetMapping("/index/{userId}")
public ModelAndView socket(@PathVariable String userId) {
ModelAndView mav=new ModelAndView("/socket1");
mav.addObject("userId", userId);
return mav;
}
//推送数据接口
@ResponseBody
@RequestMapping("/socket/push/{cid}")
public Map pushToWeb(@PathVariable String cid, String message) {
Map result = new HashMap();
try {
WebSocketServer.sendInfo(message,cid);
result.put("code", 200);
result.put("msg", "success");
} catch (IOException e) {
e.printStackTrace();
}
return result;
}
 1.4:提供socket1.html页面

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"></meta>
<title>Title</title>
</head>
<body>
hello world!

</body>
<script>
var socket;
if(typeof(WebSocket) == "undefined") {
console.log("您的浏览器不支持WebSocket");
}else{
console.log("您的浏览器支持WebSocket");
//实现化WebSocket对象,指定要连接的服务器地址与端口 建立连接
//等同于
index = new WebSocket("ws://localhost:8885/websocket/2");
//socket = new WebSocket("${basePath}websocket/${cid}".replace("http","ws"));
//打开事件
index.onopen = function() {
console.log("Socket 已打开");
//socket.send("这是来自客户端的消息" + location.href + new Date());
};
//获得消息事件
index.onmessage = function(msg) {
console.log(msg.data);
//发现消息进入 开始处理前端触发逻辑
};
//关闭事件
index.onclose = function() {
console.log("Socket已关闭");
};
//发生了错误事件
index.onerror = function() {
alert("Socket发生了错误");
//此时可以尝试刷新页面
}
//离开页面时,关闭socket
//jquery1.8中已经被废弃,3.0中已经移除
// $(window).unload(function(){
// socket.close();
//});
}
</script>
</html>
 总结:

浏览器debug访问 localhost:8885/system/index/1跳转到socket1.html,js自动连接server并传递cid到服务端,服务端对应的推送消息到客户端页面(cid区分不同的请求,server里提供的有群发消息方法)

2.1:基于STOMP协议的WebSocket

使用STOMP的好处在于,它完全就是一种消息队列模式,你可以使用生产者与消费者的思想来认识它,发送消息的是生产者,接收消息的是消费者。而消费者可以通过订阅不同的destination,来获得不同的推送消息,不需要开发人员去管理这些订阅与推送目的地之前的关系,spring官网就有一个简单的spring-boot的stomp-demo,如果是基于springboot,大家可以根据spring上面的教程试着去写一个简单的demo。

提供websocketConfig配置类

/**
* @Description:
registerStompEndpoints(StompEndpointRegistry registry)
configureMessageBroker(MessageBrokerRegistry config)
这个方法的作用是定义消息代理,通俗一点讲就是设置消息连接请求的各种规范信息。
registry.enableSimpleBroker("/topic")表示客户端订阅地址的前缀信息,也就是客户端接收服务端消息的地址的前缀信息(比较绕,看完整个例子,大概就能明白了)
registry.setApplicationDestinationPrefixes("/app")指服务端接收地址的前缀,意思就是说客户端给服务端发消息的地址的前缀
* @Author:hui.yunfei@qq.com
* @Date: 2019/5/31
*/
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {

// 这个方法的作用是添加一个服务端点,来接收客户端的连接。
// registry.addEndpoint("/socket")表示添加了一个/socket端点,客户端就可以通过这个端点来进行连接。
// withSockJS()的作用是开启SockJS支持,
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/socket").withSockJS();
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
//表示客户端订阅地址的前缀信息,也就是客户端接收服务端消息的地址的前缀信息
registry.enableSimpleBroker("/topic");
//指服务端接收地址的前缀,意思就是说客户端给服务端发消息的地址的前缀
registry.setApplicationDestinationPrefixes("/app");
}
}
 2.2:controller提供对应请求的接口

//页面请求
@GetMapping("/socket2")
public ModelAndView socket2() {//@PathVariable String userId
ModelAndView mav=new ModelAndView("html/socket2");
//mav.addObject("userId", userId);
return mav;
}

/**
* @Description:这个方法是接收客户端发送功公告的WebSocket请求,使用的是@MessageMapping
* @Author:hui.yunfei@qq.com
* @Date: 2019/5/31
*/
@MessageMapping("/change-notice")//客户端访问服务端的时候config中配置的服务端接收前缀也要加上 例:/app/change-notice
@SendTo("/topic/notice")//config中配置的订阅前缀记得要加上
public CustomMessage greeting(CustomMessage message){
System.out.println("服务端接收到消息:"+message.toString());
//我们使用这个方法进行消息的转发发送!
//this.simpMessagingTemplate.convertAndSend("/topic/notice", value);(可以使用定时器定时发送消息到客户端)
// @Scheduled(fixedDelay = 1000L)
// public void time() {
// messagingTemplate.convertAndSend("/system/time", new Date().toString());
// }
//也可以使用sendTo发送
return message;
}
2.3:提供socket2.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8" />
<title>Spring Boot+WebSocket+广播式</title>

</head>
<body onload="disconnect()">
<noscript><h2 style="color: #ff0000">貌似你的浏览器不支持websocket</h2></noscript>
<div>
<div>
<button id="connect" onclick="connect();">连接</button>
<button id="disconnect" disabled="disabled" onclick="disconnect();">断开连接</button>
</div>
<div id="conversationDiv">
<label>输入你的名字</label><input type="text" id="name" />
<button id="sendName" onclick="sendName();">发送</button>
<p id="response"></p>
</div>
</div>
<script th:src="@{/js/sockjs.min.js}"></script>
<script th:src="@{/js/stomp.min.js}"></script>
<script th:src="@{/js/jquery-3.2.1.min.js}"></script>
<script type="text/javascript">
var stompClient = null;

function setConnected(connected) {
document.getElementById('connect').disabled = connected;
document.getElementById('disconnect').disabled = !connected;
document.getElementById('conversationDiv').style.visibility = connected ? 'visible' : 'hidden';
$('#response').html();
}

function connect() {
var socket = new SockJS('/socket'); //1
stompClient = Stomp.over(socket);//2
stompClient.connect({}, function(frame) {//3
setConnected(true);
console.log('开始进行连接Connected: ' + frame);
stompClient.subscribe('/topic/notice', function(respnose){ //4
showResponse(JSON.parse(respnose.body).responseMessage);
});
});
}

function disconnect() {
if (stompClient != null) {
stompClient.disconnect();
}
setConnected(false);
console.log("Disconnected");
}

function sendName() {
var name = $('#name').val();
stompClient.send("/app/change-notice", {}, JSON.stringify({ 'name': name }));//5
}

function showResponse(message) {
var response = $("#response");
response.html(message);
}
</script>
</body>
</html>
2.4:对应的js引用可以去网上下载

2.5:浏览器debug访问localhost:8885/system/socket2,点击连接连接到服务器,数据内容可以推送到服务器以及服务器消息回推。

2.6:实现前端和服务端的轮训可以页面Ajax轮训也可以后端添加定时器

@Component
@EnableScheduling
public class TimeTask {
private static Logger logger = LoggerFactory.getLogger(TimeTask.class);

@Scheduled(cron = "0/20 * * * * ?")
public void test(){
System.err.println("********* 定时任务执行 **************");
CopyOnWriteArraySet<WebSocketServer> webSocketSet =
WebSocketServer.getWebSocketSet();
int i = 0 ;
webSocketSet.forEach(c->{
try {
c.sendMessage(" 定时发送 " + new Date().toLocaleString());
} catch (IOException e) {
e.printStackTrace();
}
});

System.err.println("/n 定时任务完成.......");
}
}
代码在https://github.com/huiyunfei/spring-cloud.git 的admin项目里

基于STOMP协议的广播模式和点对点模式消息推送可参考:
https://www.cnblogs.com/hhhshct/p/8849449.html

https://www.cnblogs.com/jmcui/p/8999998.html
————————————————
版权声明:本文为CSDN博主「不偷腥的mao」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/huiyunfei/article/details/90719351

springboot集成websocket的两种实现方式的更多相关文章

  1. Springboot中IDE支持两种打包方式,即jar包和war包

    Springboot中IDE支持两种打包方式,即jar包和war包 打包之前修改pom.xml中的packaging节点,改为jar或者war    在项目的根目录执行maven 命令clean pa ...

  2. springboot之jackson的两种配置方式

    springboot 针对jackson是自动化配置的,如果需要修改,有两种方式: 方式一:通过application.yml 配置属性说明:## spring.jackson.date-format ...

  3. springboot工程pom的两种配置方式

    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/20 ...

  4. 【websocket】spring boot 集成 websocket 的四种方式

    集成 websocket 的四种方案 1. 原生注解 pom.xml <dependency> <groupId>org.springframework.boot</gr ...

  5. SpringBoot整合Servlet的两种方式

    SpringBoot整合Servlet有两种方式: 1.通过注解扫描完成Servlet组件的注册: 2.通过方法完成Servlet组件的注册: 现在简单记录一下两种方式的实现 1.通过注解扫描完成Se ...

  6. SpringBoot从入门到精通二(SpringBoot整合myBatis的两种方式)

    前言 通过上一章的学习,我们已经对SpringBoot有简单的入门,接下来我们深入学习一下SpringBoot,我们知道任何一个网站的数据大多数都是动态的,也就是说数据是从数据库提取出来的,而非静态数 ...

  7. 关于springboot项目的jar和war两种打包方式部署的区别

    关于springboot项目的jar和war两种打包方式部署的区别 关于springboot项目的jar和war两种打包方式部署的区别? https://bbs.csdn.net/topics/392 ...

  8. 【SpringBoot】05.SpringBoot整合Listener的两种方式

    SpringBoot整合Listener的两种方式: 1.通过注解扫描完成Listener组件的注册 创建一个类实现ServletContextListener (具体实现哪个Listener根据情况 ...

  9. 【SpringBoot】03.SpringBoot整合Servlet的两种方式

    SpringBoot整合Servlet的两种方式: 1. 通过注解扫描完成Servlet组件注册 新建Servlet类继承HttpServlet 重写超类doGet方法 在该类使用注解@WebServ ...

随机推荐

  1. centos7 搭建FTP

    通过yum安装vsftpd yum install -y vsftpd 修改vsftpd的配置文件 vim /etc/vsftpd/vsftpd.conf 修改配置文件如下: 1.不允许匿名访问 an ...

  2. 如何在CentOS 7上安装Memcached(缓存服务器)

    首先更新本地软件包索引,然后使用以下yum命令从官方CentOS存储库安装Memcached. yum update yum install memcached 接下来,我们将安装libmemcach ...

  3. post请求导出Excel表格

    axios.interceptors.response.use((response) =>{ if(response.config && response.config.resp ...

  4. Win7 双系统安装Centos7,并由windows引导程序引导

    1. 在windows磁盘管理中,压缩卷,腾出40G,需保证一个磁盘设备最多只有3个主分区2. 网上下载centos7的dvd.iso3. 使用UltraISO刻录到U盘4. 重启系统F12使用usb ...

  5. 深入理解 iptables 和 netfilter 架构

    [译] 深入理解 iptables 和 netfilter 架构 Published at 2019-02-18 | Last Update 译者序 本文翻译自 2015 年的一篇英文博客 A Dee ...

  6. 关于比较js中两个对象相等 ==

    “如果两个操作数都是对象,则比较他们是不是同一个对象(引用的对象在内存中的地址一样),如果两个操作数都指向同一个对象,则相等操作符返回true,否则,返回false”. 我做了一个例子 functio ...

  7. 【转】TCP/IP网络协议各层首部

    ​ 数据包封装流程(逐层封装,逐层解封) 二层帧(二层帧中目的地址6个字节,源地址6个字节,长度/类型2个字节,二层帧共18个字节) ip头部(ip头部20字节) tcp头部(tcp头部20个字节): ...

  8. AnroidStudio gradle版本和android插件的版本依赖

  9. POJ 3229:The Best Travel Design

    Description Dou Nai ), and the end of the travel route hours on traveling every day. Input There are ...

  10. Android使用glide加载.9图片的方法

    我们在开发过程中会经常使用.9图片, 因为它可以使图片拉伸的时候,保证其不会失真. 而我们把.9图片放在服务器端,通过glide直接加载,会报错. 我们的解决方法是 通过sdk的aapt工具 把.9图 ...