600字范文,内容丰富有趣,生活中的好帮手!
600字范文 > SpringBoot集成WebSocket实现简单的多人聊天室

SpringBoot集成WebSocket实现简单的多人聊天室

时间:2020-08-12 11:28:17

相关推荐

SpringBoot集成WebSocket实现简单的多人聊天室

1.什么是WebSocket?

WebSocket协议是由HTML5定义的,基于TCP协议实现的一种网络协议,通过该协议服务器可以主动向客户端发送信息;

WebSocket 协议在诞生,成为W3C国际标准;

我们已经有了 HTTP 协议,为什么出现一个websocket协议?

http协议是短连接,因为请求之后,都会关闭连接,下次重新请求数据,需要再次打开链接;

WebSocket协议是一种长连接,只需要通过一次请求来初始化连接,然后所有的请求和响应都是通过这个TCP连接进行通讯;

所以HTTP协议通信只能是客户端向服务器发出请求,服务器返回响应结果,HTTP

协议做不到服务器主动向客户端推送信息,而websocket能实现服务器和客户端全双工通信;

何谓全双工

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

基本实现原理

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

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

传统Web推送实现

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

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

这种形式缺点很明显,浏览器需要不断的向服务器发出HTTP请求,而HTTP请求包含较长的头部,有效信息相对较少,反复的无效请求占用了大量的带宽和

CPU 资源,造成很大的浪费,所以,WebSocket 应运而生;

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

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

WebSocket的特点

全双工通信,客户端和服务器可以双向平等通信;

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

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

可以发送文本,也可以发送二进制数据;

通信具有更强的实时性;

协议标识符是ws,服务器地址举个例子就是:ws:///some/path

而http协议的地址则是: http://…

websocket业务场景

WebSocket聊天室;

股票实时价格显示等应用;

即时通讯、游戏、可视化大屏展示等领域;

企业内部管理通讯等功能,主要通讯协议websocket;

web网页聊天、客服系统实现;

系统提醒、用户上下线提醒、客户端同步,实时数据更新,多屏幕同步,用户在线状态,消息通知,扫描二维码登录/二维码支付,弹幕、各类信息提醒,在线选座,实时监控大屏等等;

2.Java中的WebSocket API

在Java EE 7中Java语言开始支持websocket协议,Java EE 7中定义了一套Websocket

API规范,也就是一系列接口,没有实现,位于包javax.websocket下,包含客户端API和服务端API,WebSocket的Java

API 只是规范,具体实现需要web容器(比如tomcat就实现了Java websocket api)、Java EE服务器或者框架提供;

1、Tomcat:java中的websocket实现,需要tomcat 7.0.47+以上才支持Java EE7;

2、Spring的websocket,需要Spring 4.x,所以springboot也可以用;

2.1 WebSocket开发中的相关注解及API方法

@ServerEndpoint("/websocket/{uid}")

申明这是一个websocket服务;

需要指定访问该服务的地址,在地址中可以指定参数,需要通过{}进行占位;

@OnOpen

用法:public void onOpen(Session session, @PathParam(“uid”) String uid)

throws IOException{}

该方法将在建立连接后执行,会传入session对象,就是客户端与服务端建立的长连接通道,通过@PathParam获取url中声明的参数;

@OnClose

用法:public void onClose() {}

该方法是在连接关闭后执行;

@OnMessage

用法:public void onMessage(String message, Session session) throws

IOException {}

该方法用于接收客户端发送的消息;

message:发来的消息数据;

session:会话对象(也是长连接通道);

发送消息到客户端;

用法:session.getBasicRemote().sendText(“hello,websocket.”);

通过session进行消息发送;

2.2 前端技术对WebSocket的支持

Websocket是html5规范,主流浏览器都支持;(某些老浏览器不支持)

jQuery、vueJS、React JS、angularjs等都可以支持webscoket对象;

底层是javascript支持的一个webscoket的js对象,通过这个对象可以建立websocket的连接:ws://localhost:8080/websocket/12345

3.多人聊天室的实现源码

3.1 pom文件中添加相关依赖

<dependencies><!-- SpringBoot框架web项目起步依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- SpringBoot框架websocket起步依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency><!-- SpringBoot框架内嵌Tomcat对jsp的解析依赖 --><dependency><groupId>org.apache.tomcat.embed</groupId><artifactId>tomcat-embed-jasper</artifactId></dependency><!-- lombok依赖 --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.12</version></dependency></dependencies><build><!-- SpringBoot框架编译打包插件 --><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins><!-- src/main/webapp下的jsp页面编译到META-INF/resources下才能访问 --><resources><resource><directory>src/main/webapp</directory><targetPath>META-INF/resources</targetPath><includes><include>*.*</include></includes></resource></resources></build>

3.2 在核心配置文件中配置视图解析器

#配置视图解析器spring.mvc.view.prefix=/spring.mvc.view.suffix=.jsp

3.3 加入相关静态资源文件

3.4 编写控制层controller

这其中只有一个请求,/chat,这个请求会跳转到我们的index.jsp页面。

import org.springframework.stereotype.Controller;import org.springframework.ui.Model;import org.springframework.web.bind.annotation.RequestMapping;import java.util.concurrent.atomic.AtomicInteger;/****/@Controllerpublic class ChatController {//声明原子变量类,确保服务端和客户端之间操作的原子性和可见性private AtomicInteger atomicInteger=new AtomicInteger();@RequestMapping("/chat")public String chat(Model model) {model.addAttribute("username","user" + atomicInteger.getAndIncrement());return "index";}}

3.5 写一个配置类,开启SpringBoot对WebSocket的支持

import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.web.socket.config.annotation.EnableWebSocket;import org.springframework.web.socket.server.standard.ServerEndpointExporter;/****/@EnableWebSocket //开启SpringBoot对WebSocket的支持@Configuration //声明该类是一个配置类public class ChatConfig {/*** 配置ServerEndpointExporter的bean* 该Bean会自动注册使用了@ServerEndpoint注解声明的Websocket endpoint* @return*/@Beanpublic ServerEndpointExporter serverEndpoint() {return new ServerEndpointExporter();}}

6.6 写一个工具类

这个工具类封装了大量静态方法,供外界调用。

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;/*** 聊天室功能实现的一个工具类*/public class ChatUtils {//定义日志对象private static final Logger logger= LoggerFactory.getLogger(ChatUtils.class);//定义map集合,确保数据共享和安全,这里使用ConcurrentHashMap//用户名为key,session信息为valuepublic static final Map<String, Session> 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) {e.printStackTrace();logger.error("sendMessage IOException",e);}}/*** 发送消息给所有人* @param message*/public static void sendMessageAll(String message) {CLIENTS.forEach((sessionId,session) -> sendMessage(session,message));}/*** 获取所有的在线用户*/public static String getOnlineInfo() {Set<String> userNames=CLIENTS.keySet();if (userNames.size() == 0) {return "当前无人在线......";}return userNames.toString() + "在线";}}

3.7 WebSocket的核心类

import lombok.extern.slf4j.Slf4j;import org.ponent;import javax.websocket.*;import javax.websocket.server.PathParam;import javax.websocket.server.ServerEndpoint;import java.io.IOException;/*** @ServerEndpoint注解中指定WebSocket协议的地址* @OnOpen、@OnMessage、@OnClose、@OnError注解与WebSocket中监听事件对应*/@Slf4j //生成一些日志代码@Component@ServerEndpoint("/websocket/{username}")public class ChatServerEndpoint {/*** 连接建立时触发*/@OnOpenpublic void onOpen(@PathParam("username") String username, Session session) {log.info("用户{}登录",username);String message= "用户[" + username + "]已进入聊天室!";//将该用户登录的消息发送给其他人ChatUtils.sendMessageAll(message);//将自己的信息添加到map集合中ChatUtils.CLIENTS.put(username,session);//获取当前的在线人数,发给自己查看String onlineInfo=ChatUtils.getOnlineInfo();ChatUtils.sendMessage(session,onlineInfo);}/*** 客户端接收服务端发来的数据时触发*/@OnMessagepublic void onMessage(@PathParam("username") String username,String message) {log.info("发送消息:{}, {}",username,message);//广播,把消息同步给其他客户端ChatUtils.sendMessageAll("[" + username + "]: " + message);}/*** 连接关闭时触发*/@OnClosepublic void onClose(@PathParam("username") String username,Session session) {//从当前的map集合中移除该用户ChatUtils.CLIENTS.remove(username);//将该用户离线的消息通知给其他人ChatUtils.sendMessageAll("[" + username + "]已离线!");try {//关闭WebSocket下的该Seesion会话session.close();log.info("{} 已离线......",username);} catch (IOException e) {e.printStackTrace();log.error("onClose error",e);}}/*** 聊天通信发生错误时触发*/@OnErrorpublic void onError(Session session,Throwable throwable) {try {//关闭WebSocket下的该Seesion会话session.close();} catch (IOException e) {e.printStackTrace();log.error("onError Exception",e);}log.info("Throwable msg " + throwable.getMessage());}}

3.8 最后是我们的jsp页面

这里简单的说一下,当我们发起controller中对应的请求之后,会跳转到这个页面,待页面加载完之后(就是执行完<script>标签之前的代码),然后走script脚本内容,下面就是ajax+jQuery了,首先是获取了websocket的请求地址,然后就是根据情况来触发

onopen、onmessage、onclose、onerror这些事件了。其实也不难理解。

<%@ page contentType="text/html;charset=utf-8" language="java" %><html><head><title>SpringBoot + WebSocket + JSP</title><link rel="stylesheet" href="${pageContext.request.contextPath}/css/bootstrap.min.css"><script src="${pageContext.request.contextPath}/js/jquery.min.js"></script><script src="${pageContext.request.contextPath}/js/bootstrap.min.js"></script></head><body style="margin: 45px"><h4>张起灵在线聊天室</h4><div class="form-group"><label for="content"></label><textarea id="content" readonly="readonly" cols="80" rows="15"></textarea></div><div class="form-group" style="margin-top: 8px"><textarea id="message" cols="80" rows="5" placeholder="请输入消息"></textarea><div style="margin-top: 10px"><button id="toSend" class="btn btn-info">发送</button><button id="toExit" class="btn btn-danger">离线</button><input id="username" value="${username}" style="display: none"></div></div><script type="text/javascript">$(function () {var ws;//如果浏览器支持WebSocketif ("WebSocket" in window) {var baseUrl='ws://localhost:8080/websocket/';var username=$('#username').val();ws=new WebSocket(baseUrl + username);//建立连接之后,触发事件ws.onopen=function () {console.log("建立 websocket 连接......");};//接收后台服务端的消息,触发事件ws.onmessage=function (event) {$('#content').append(event.data + '\n\n');console.log("接收到服务端发送的消息......" + event.data + '\n');};//连接关闭时,触发事件ws.onclose=function () {$('#content').append('[' + username + ']已离线');console.log("关闭 websocket 连接......");};//发生错误时,触发事件ws.onerror=function (event) {console.log("websocket发生错误......" + event + '\n');};} else {//如果浏览器不支持WebSocketalert("很抱歉,您的浏览器不支持WebSocket!!!");}//发送按钮触发的行为,客户端发送消息到服务器$('#toSend').click(function () {sendMsg();});//支持回车键发送消息$(document).keyup(function (event) {if (event.keyCode == 13) {sendMsg();}});//发送消息的函数function sendMsg() {ws.send($('#message').val());$('#message').val("");}//离线按钮触发的行为$('#toExit').click(function () {if (ws) {ws.close();}})})</script></body></html>

3.9 测试结果

我这里开了三个用户,一个user0,一个user1,一个user2。

分别在浏览器中访问三次请求地址就可以开启三个用户的聊天室了。

然后我们关闭user2的窗口,或者点击离线按钮。

最后依次点击user1和user0的离线按钮,因为我们之前代码中用到了Slf4j,所以我们此时回到IDEA中查看控制台打印的日志信息。

原文链接:/weixin_43823808/article/details/118066147

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。