spring boot websocket 客户端_Spring Boot集成WebSocket实战

程序新视界:一个“软实力”、“硬技术”同步成长的平台。

06635a9d77d122af421aaf1052810238.png

WebSocket简介

WebSocket协议是由HTML5定义的,基于TCP协议实现的一种网络协议,它实现了客户端与服务器全双工通信。也就是说通过该协议服务器可以主动发送信息给客户端。

何谓全双工

信息只能单向传送为单工;信息能双向传送但不能同时双向传送称为半双工,信息能够同时双向传送则称为全双工。

基本实现原理

WebSocket协议基于TCP协议实现,客户端和服务器只需要做一个握手的动作之后,形成了一条基于客户端和服务器之间的快速通道。后续两者之间便可以进行多次数据帧双向传输过程。

这样实现的目的是客户端和服务器进行频繁双向通信时,可以使服务器避免打开多个HTTP连接进行工作来节约资源,提高工作效率和资源利用率。

传统Web推送实现

在没有WebSocket协议之前,服务器如何向浏览器端推送消息?

此时,通常的实现方式是在页面通过Ajax定时轮询,比如每隔1秒中向服务器发送一次HTTP请求,询问服务器是否有新消息,服务器返回结果。

这种形式缺点很明显,也就是说浏览器需要不断的向服务器发出HTTP请求,而HTTP请求包含较长的头部,有效信息相对较少,这样会造成很大的浪费。

再试想一下,如果同时打开很多浏览器页面,同时每个请求都会出现阻塞请求,会对服务器造成一定的并发量和资源浪费。

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

WebSocket协议本质上是一个基于TCP的协议,因此与HTTP协议没有什么关系。

WebSocket的特点

基于上面所述WebSocket的机制,它拥有以下特点:

  • 首先最大的特点就是:开通了双工异步通信的功能,客户端和服务器可以双向平等对话。

  • 建立在TCP协议之上,服务器端的实现比较容易。

  • 与HTTP协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用HTTP协议,因此握手时不容易屏蔽,能通过各种HTTP代理服务器。

  • 数据格式比较轻量,性能开销小,通信高效。

  • 更好的二进制支持,可以发送文本,也可以发送二进制数据。

  • 更强的时效性。

业务场景

实例的基本业务场景如下:

在某官方网站上,提供用户匿名在线技术交流功能。用户点击网页,即可为用户生成一个匿名,用户可以看到当前在线人的昵称信息,随后可以在群里发送消息进行交流。

Spring Boot集成WebSocket

引入依赖文件:

<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>

<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-websocketartifactId>
dependency>

<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-thymeleafartifactId>
dependency>

其中spring-boot-starter-websocket是针对websocket的依赖,另外web和thymeleaf是为了使用web功能和页面展示功能而引入的依赖。

开启WebSocket和配置

单独创建配置类WebSocketConfig:

@Configuration
@EnableWebSocket
public class WebSocketConfig {

/**
* 注入ServerEndpointExporter;
* 该Bean会自动注册使用了@ServerEndpoint注解声明的Websocket endpoint
*/
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}

}

其中通过@EnableWebSocket注解开启WebSocket服务,内部实例化ServerEndpointExporter的Bean,该Bean会自动注册使用了@ServerEndpoint注解声明的Websocket endpoint。

声明@ServerEndpoint

创建AssistantServerEndpoint类,用于提供WebSocket具体的功能实现:

/**
* 说明:
* 1、@ServerEndpoint注解中指定WebSocket协议的地址;
* 2、@OnOpen、@OnMessage、@OnClose、@OnError注解与WebSocket中监听事件对应
* @author sec
* @version 1.0
* @date 2020/2/19 10:17 AM
**/
@Slf4j
@RestController
@ServerEndpoint("/helper/{username}")
public class AssistantServerEndpoint {

/**
* 连接建立时触发
*/
@OnOpen
public void openSession(@PathParam("username") String username, Session session) {
log.info("用户{}登录", username);
String message = "欢迎用户[" + username + "] 已进入!";
// 发送登录消息给其他人
sendMessageAll(message);

// 获取当前在线人数,发给自己
String onlineInfo = WebSocketUtils.getOnlineInfo();
WebSocketUtils.sendMessage(session, onlineInfo);

// 添加自己到map中
CLIENTS.put(username, session);
}

/**
* 客户端接收服务端数据时触发
*/
@OnMessage
public void onMessage(@PathParam("username") String username, String message) {
log.info("发送消息:{}", message);
sendMessageAll("[" + username + "] : " + message);
}

/**
* 连接关闭时触发
*/
@OnClose
public void onClose(@PathParam("username") String username, Session session) {
//当前的Session移除
CLIENTS.remove(username);
// 离开消息通知所有人
sendMessageAll("[" + username + "] 已离开!");
try {
session.close();
} catch (IOException e) {
log.error("onClose error", e);
}
}

/**
* 通信发生错误时触发
*/
@OnError
public void onError(Session session, Throwable throwable) {
try {
session.close();
} catch (IOException e) {
log.error("onError Exception", e);
}
log.info("Throwable msg " + throwable.getMessage());
}

}

其中,@ServerEndpoint注解中指定WebSocket协议的地址;@OnOpen、@OnMessage、@OnClose、@OnError注解与WebSocket中监听事件对应。

openSession方法,用户建立连接时,会添加到缓存当中,并发送登录消息给其他人,同时给自己发送消息,显示当前有谁在线。

onMessage方法,提供发送消息的功能。onClose方法提供连接关闭时的业务处理。onError方法处理通信发生错误时的处理。

工具类WebSocketUtils

上述类中使用到了工具类,提供一些辅助功能:

package com.secbro.utils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.websocket.RemoteEndpoint;
import javax.websocket.Session;
import java.io.IOException;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
* @author sec
* @version 1.0
* @date 2020/2/26 9:10 AM
**/
public final class WebSocketUtils {

private static final Logger logger = LoggerFactory.getLogger(WebSocketUtils.class);

/**
* 存储WebSocket session;
* 以用户的姓名为key,WebSocket为对象为value;
*/
public static final Map CLIENTS = new ConcurrentHashMap<>();/**
* 使用连接发送数据
* @param session 用户session
* @param message 发送内容
*/public static void sendMessage(Session session, String message) {if (session == null) {return;
}final RemoteEndpoint.Basic basic = session.getBasicRemote();if (basic == null) {return;
}try {
basic.sendText(message);
} catch (IOException e) {
logger.error("sendMessage IOException ", e);
}
}public static void sendMessageAll(String message) {
CLIENTS.forEach((sessionId, session) -> sendMessage(session, message));
}/**
* 获取所有在线用户
*/public static String getOnlineInfo(){
Set userNames = CLIENTS.keySet();if(userNames.size() ==0){return "当前无人在线...";
}return CLIENTS.keySet().toString() + "在线";
}
}

辅助功能主要是缓存当前在线用户,以及调用方法发送消息给当前用户。

进入聊天Controller

提供进入聊天的入口Controller,AssistantController:

@Controller
public class AssistantController {

private AtomicInteger idProducer = new AtomicInteger();

@RequestMapping("/")
public String index(Model model) {

model.addAttribute("username","user" + idProducer.getAndIncrement());
return "index";
}

}

该Controller中,当用户进入时,创建一个匿名,并返回页面。

页面操作

在index.html页面中,展示聊天框及相关聊天信息。

<html lang="en" xmlns:th="http://www.thymeleaf.org"><head><meta charset="UTF-8"><title>技术交流群title><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.4.1/dist/css/bootstrap.min.css"><script src="http://ajax.microsoft.com/ajax/jquery/jquery-1.4.min.js">script>head><body class="container" style="width: 70%"><h4>技术交流群h4><div class="form-group"><label for="content">label><textarea id="content" class="form-control" readonly="readonly" cols="80" rows="15">textarea>div><div class="form-group"><label for="message">群发消息  label><input id="message" value="" class="form-control"/><div style="margin-top: 10px"><button id="user_exit" class="btn btn-danger">离开button><button id="toSend" class="btn btn-info">发送消息button><input id="username" th:value="${username}" style="display: none"/>div>div>body><script type="text/javascript">
$(document).ready(function () {var ws;if ("WebSocket" in window){var baseUrl = 'ws://localhost:8080/helper/';var userName = $('#username').val();
ws = new WebSocket(baseUrl + userName);// 连通之后的回调事件,建立链接
ws.onopen = function () {console.log("建立 websocket 连接...");
};// 接收后台服务端的消息
ws.onmessage = function (event) {
$('#content').append(event.data + '\n');
};// 连接关闭的回调事件
ws.onclose = function () {
$('#content').append('[' + userName + '] 已离开!');console.log("关闭 websocket 连接...");
};
} else {// 浏览器不支持 WebSocket
alert("您的浏览器不支持WebSocket!");
}// 客户端发送消息到服务器
$('#toSend').click(function () {
sendMsg();
});
$(document).keyup(function(event){// 回车键事件if(event.keyCode==13){
sendMsg();
}
});function sendMsg(){
ws.send($('#message').val());
}// 退出
$('#user_exit').click(function () {if (ws) {
ws.close();
}
});
})script>html>

聊天页面采用thymeleaf,首先获取用户名称,然后通过JQuery操作,建立WebSocket链接,并绑定一些相关事件。

最终,页面显示效果如下: 54774c92c0f764cd00e8529542fa76a8.png

技术视频

目前正在为大家打造一套关于Spring Boot的精品课程:《Spring Boot 2.x全家桶》系列视频教程,该系列视频教程预计30+章节,100+节课,20+小时的内容,涵盖市面上大多数的Spring Boot使用场景。目前录制过半,价格优惠,后期会逐步调整,可点击“阅读原文”,查看相关教程。https://edu.csdn.net/course/detail/20369

dc9b98de393664e394fc4efb5e433c3a.png

0da0805dc0c7910796b6fef6660b1368.png

未完待续,持续保持每周更新5-7节课时的节奏中……

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值