资讯 小学 初中 高中 语言 会计职称 学历提升 法考 计算机考试 医护考试 建工考试 教育百科
栏目分类:
子分类:
返回
空麓网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
空麓网 > 计算机考试 > 软件开发 > 后端开发 > Java

基于 SpringBoot+WebSocket 无DB实现在线聊天室(附源码)

Java 更新时间: 发布时间: 计算机考试归档 最新发布

文章目录

  • 基于 SpringBoot+WebSocket 无DB实现在线聊天室
    • 0 项目说明
      • 0.1 样例展示
      • 0.2 源码地址
    • 1 WebSocket 简介
      • 1.1 HTTP
      • 1.2 WebSocket
        • 1.2.1 WebSocket 协议
        • 1.2.2 WebSocket 交互
    • 2 使用教程
      • 2.1 客户端(浏览器)
        • 2.1.1 WebSocket 对象
        • 2.1.2 WebSocket 事件
        • 2.1.3 WebSocket 方法
      • 2.2 服务端(JAVA)
        • 2.2.1 编程式
        • 2.2.2 注解式
        • 2.2.3 服务端接收客户端数据
        • 2.2.4 服务端发送数据给客户端
    • 3 聊天室实现
      • 3.1 页面布局
        • 3.1.1 登录页面
        • 3.1.2 聊天界面
      • 3.2 实现流程
      • 3.3 WebSocket 消息格式
      • 3.4 工程创建
        • 3.4.1 创建项目
        • 3.4.2 引入静态资源文件
      • 3.5 登录功能
        • 3.5.1 HTML 代码
        • 3.5.2 Java 代码
      • 3.6 聊天功能
        • 3.6.1 Java 代码
        • 3.6.2 HTML 代码
        • 3.6.3 JS 代码
    • 4 Docker 部署服务器
      • 4.1 修改 WS URL 地址
      • 4.2 Maven 打包
      • 4.3 Dockerfile 文件
      • 4.4 上传服务器
      • 4.5 Docker 运行
      • 4.6 项目结果

基于 SpringBoot+WebSocket 无DB实现在线聊天室

0 项目说明

0.1 样例展示

0.2 源码地址

GitHub:https://github.com/ShiJieCloud/web-chat

Gitee:https://gitee.com/suitbaby/web-chat

GitCode:I’m Jie / web-chat · GitCode

1 WebSocket 简介

1.1 HTTP

常用的 HTTP 协议是一种无状态的、无连接的、单向的应用层协议。它采用了请求/响应模型。通信请求只能由客户端发起,服务端对请求做出应答处理。这种通信模型有一个弊端:HTTP 协议无法实现服务器主动向客户端发起消息。

这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。大多数 Web 应用程序将通过频繁的异步 AJAX 请求实现长轮询。轮询的效率低,非常浪费资源(因为必须不停连接,或者 HTTP 连接始终打开)。

HTTP 协议交互流程如下:

1.2 WebSocket

1.2.1 WebSocket 协议

为了实现服务器主动向客户端发起消息,WebSocket 产生了,WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。

WebSocket 协议有两部分:握手(基于 HTTP 协议)和数据传输:

  • 来自客户端的握手,形式如下:

    GET ws://localhost/chat HTTP/1.1Host:localhostUpgrade:websocketConnection:UpgradeSec-websocket-Key:dGh1IHNhbXBsZSBub25jZQ==Sec-WebSocket-Extensions:permessage-deflateSec-WebSocket-Version:13
  • 来自服务器的握手,形式如下:

    HTTP/1.1 101 switching ProtocolsUpgrade:websocketConnection:UpgradeSec-Websocket-Accept:s3pPLMBiTxaQ9kYGzzhZRbK+xOo=Sec-Websocket-Extensions:permessage-deflate

字段说明:

头名称说明
Connection:Upgrade标识该HTTP请求是一个协议升级请求
Upgrade:WebSocket协议升级为WebSocket协议
Sec-WebSocket-Version:13客户端支持WebSocket的版本
Sec-WebSocket-Key:客户端采用base64编码的24位随机字符序列,服务器接受客户端HTTP协议升级的证明。要求服务端响应一个对应加密的sec-WebSocket-Accept头信息作为应答
Sec-WebSocket-Extensions协议扩展类型

1.2.2 WebSocket 交互

WebSocket 协议客户端服务端交互流程如图:

2 使用教程

2.1 客户端(浏览器)

2.1.1 WebSocket 对象

实现 WebSocket 的 Web 浏览器将通过 WebSocket 对象公开所有必需的客户端功能(主要指支持HTML5的浏览器)。

使用以下方式创建 WebSocket 对象:

var ws = new Websocket(url);

参数 url 格式说明:ws://ip地址:端口号/资源名称,例如:let ws = new WebSocket("ws://localhost:8080/wechat")

2.1.2 WebSocket 事件

WebSocket 对象的相关事件

事件事件处理程序描述
openwebsocket对象.onopen连接建立时触发
messagewebsocket对象.onmessage客户端接收服务端数据时触发
errorwebsocket对象.onerror通信发生错误时触发
closewebsocket对象.onclose连接关闭时触发

2.1.3 WebSocket 方法

WebSocket 对象的相关方法:

方法描述
send()使用连接发送数据

2.2 服务端(JAVA)

Tomcat 的 7.0.5 版本开始支持 WebSocket,并且实现了 Java WebSocket 规范(JSR356)。Java WebSocket 应用由一系列的WebSocketEndpoints组成。Endpoint 是一个 Java 对象,代表 WebSocket 链接的一端,对于服务端,我们可以视为处理具体 WebSocket 消息的接口,类似一个 Controller 处理 HTTP 请求一样。

我们可以通过使用编程式或注解的方式定义 Endpoint。

2.2.1 编程式

第一种是编程式,即继承 javax.websocket.Endpoint 类并实现其方法。

public abstract class Endpoint {        public abstract void onOpen(Session session, EndpointConfig config);        public void onClose(Session session, CloseReason closeReason) {        // NO-OP by default    }        public void onError(Session session, Throwable throwable) {        // NO-OP by default    }}

2.2.2 注解式

第二种是注解式,即定义一个 PoJo 类,在类上添加 @ServerEndpoint 注解,标识该类为 Endpoint 实例。

Endpoint 实例会在 WebSocket 握手时创建,并在客户端与服务端链接过程中长时间有效,最后在链接关闭时结束。在 Endpoint 接口中明确定义了与其生命周期相关的方法,规范实现者确保生命周期的各个阶段调用实例的相关方法。生命周期方法如下:

方法含义描述注解
onClose当会话关闭时调用。@OnClose
onOpen当开启一个新的会话时调用,该方法是客户端与服务端握手成功后调用的方法。@onOpen
onError当连接过程中异常时调用。@OnError

2.2.3 服务端接收客户端数据

  • 编程式:通过为javax.websocket包下的Session对象添加MessageHandler消息处理器来接收消息
  • 注解式:定义Endpoint时,可以在PoJo类上添加@OnMessage注解指定接收消息的方法。

2.2.4 服务端发送数据给客户端

发送消息则由RemoteEndpoint完成,其实例由Session维护,根据使用情况,我们可以通过javax.websocket包下的Session对象的getBasicRemote()获取同步消息发送的实例,然后调用其sendXxx()方法就可以发送消息,可以通过Session.getAsyncRemote获取异步消息发送实例。

3 聊天室实现

3.1 页面布局

3.1.1 登录页面

用户输入昵称即可登录聊天室

3.1.2 聊天界面

聊天室主界面左部分为用户列表,显示聊天室所有在线用户,右部分为聊天区,包含聊天记录和消息输入框

3.2 实现流程

聊天室项目,WebSocket 工作流程图如下:

3.3 WebSocket 消息格式

  • 客户端→服务端

    {	"toName":"张三",	"message":"你好"}
  • 服务端→客户端

    • 系统消息格式

      {	"isSystem":true,  "status":true,	"fromName":null,  "message":[    "李四","王五"  ]}
    • 推送聊天消息格式

      {	"isSystem":false,  "status":false,	"fromName":"张三",  "message":"你好"}

3.4 工程创建

3.4.1 创建项目

创建一个 SpringBoot 项目,导入以下依赖:

  org.springframework.boot  spring-boot-starter-web  org.springframework.boot  spring-boot-starter-thymeleaf  org.projectlombok  lombok  true  org.springframework.boot  spring-boot-starter-websocket  3.0.0

3.4.2 引入静态资源文件

3.5 登录功能

3.5.1 HTML 代码

由于只需要用户输入昵称,所以使用最简单的 form 表单提交即可,Controller 接口为/chat,提交方式为 Post:

聊天室

我们一起聊天吧

3.5.2 Java 代码

在Controller中添加处理登录的方法,用于接收前端传来的登录请求。

@PostMapping("/chat")public String doLogin(String username,HttpSession httpSession, Model model){  //生成用户信息  String userId = UUID.randomUUID().toString().replace("-", "");  String avatarUrl = "/static/image/avatar/avatar-"+(System.currentTimeMillis()%43+1)+".png";  User user = new User(userId, username, avatarUrl);  //存储user到userMap中  UserDb.userMap.put(userId,user);  //将用户id保存到 HttpSession 中  httpSession.setAttribute("loginUserId", userId);  model.addAttribute("loginUser", user);  return "chat";}

3.6 聊天功能

3.6.1 Java 代码

  1. 创建 pojo 类 ChatEndPoint,在类上添加 @ServerEndpoint 注解,并指定 WebSocket 请求路径。在类中创建onOpen()、onMessage()、onClose()方法,并标注对应的注解,用于处理对应的WS事件:

    @Component@ServerEndpoint(value = "/wechat")public class ChatEndPoint {        @OnOpen    public void onOpen(Session session, EndpointConfig config) {           }        @OnMessage    public void onMessage(String message, Session session) {           }        @OnClose    public voidonClose(Session session) {    }}
  2. 我们需要在ChatEndPoint对象中添加HttpSession成员变量,用于取出登录逻辑中存入的用户id

    // 声明一个httpSession对象,从中获取登录用户idprivate HttpSession httpSession;
  3. 再声明一个javax.websocket包下的Session对象,通过该对象可以调用方法向指定的客户端(用户)发送消息

    private Session session;
  4. 因为每一个客户端都会对应一个ChatEndPoint对象,所以我们需要使用集合将所有客户端的ChatEndPoint对象保存起来,方便后续对指定用户单独操作

    // 用Map集合存储所有客户端对应的ChatEndPoint对象private static Map onlineUsers = new ConcurrentHashMap<>();
  5. 接下来我们需要实现onOpen()方法,在方法体中完成 WebSocket 连接建立后的需要执行的操作逻辑:

    1. 保存当前WS Session对象
    2. 保存HTTPSession 对象
    3. 将当前 ChatEndPoint 对象存储到Map集合中
    4. 遍历Map集合,调用ChatEndPoint对象的 WebSocket Session 成员变量的 sendText()方法将当前所有在线用户推送给所有客户端,如:chatEndPoint.session.getBasicRemote().sendText(message)
    @OnOpenpublic void onOpen(Session session, EndpointConfig config) {  //将局部Session对象赋值给成员session  this.session = session;  //获取httpSession对象  HttpSession httpSession = (HttpSession)config.getUserProperties().get(HttpSession.class.getName());  this.httpSession = httpSession;  String userId = (String) httpSession.getAttribute("loginUserId");  System.out.println(userId+"登录聊天室...."+HttpSession.class.getName());  //将当前 ChatEndPoint 对象存储到onlineUsers Map中  onlineUsers.put(userId, this);  //将当前在线用户的用户名推送给所有的客户端  //1.生成系统消息  String message = MessageUtils.getMessage(true, true,null, UserDb.userMap.values());  //2.调用方法推送消息  broadcastAllUsers(message);}
  6. 由于在聊天室项目中,当用户向另一个用户发送消息时,客户端才会向服务端发送消息,所以在 onMessage() 方法中,只实现转发消息的逻辑即可:

    1. 将客户端传来的JSON字符串格式的数据转为对象
    2. 获取需要转发的用户id
    3. 获取需要向用户转发的消息
    4. 通过用户id从Map集合中获取客户端对应的 ChatEndPoint 对象,调用 sendText()方法将消息发送
    //将消息转发送给指定用户@OnMessagepublic void onMessage(String message, Session session) {  try {    //将message的json格式转为message对象    ObjectMapper mapper = new ObjectMapper();    Message mess = mapper.readValue(message, Message.class);        //获取接收数据    String toUserId = mess.getToUserId();    String data = mess.getMessage();        //获取当前登录的用户    String loginUserId = (String) httpSession.getAttribute("loginUserId");        //生成推送给用户的消息格式    String resultMessage = MessageUtils.getMessage(false, false, loginUserId, data);        //使用指定用户的session对象发送消息给指定用户    onlineUsers.get(toUserId).session.getBasicRemote().sendText(resultMessage);  } catch (JsonProcessingException e) {    e.printStackTrace();  } catch (IOException e) {    e.printStackTrace();  }}
  7. 当用户关闭浏览器时,我们需要在onClose()中将用户下线的消息发送给客户端

    @OnClosepublic void onClose(Session session) {  String loginUserId = (String) httpSession.getAttribute("loginUserId");  System.out.println(loginUserId + "WS连接关闭了....");    //移除用户Session  onlineUsers.remove(loginUserId);  //移除用户昵称  UserDb.userMap.remove(loginUserId);  //通知所有客户端用户下线  Set set = new HashSet<>();  set.add(loginUserId);  //1.生成系统消息  String message = MessageUtils.getMessage(true,false, null, set);  //2.调用方法推送消息  broadcastAllUsers(message);}
  8. 服务端的主要代码已经完成,除此之外,我们还需要添加一些配置,否则我们编写的WebSocket并不会起到作用。

    我们需要将 ServerEndpointExporter 的实例添加到Spring容器中,ServerEndpointExporter实例会自动扫描所有的服务器端点,并添加所有带有 @ServerEndpoint 注解的类

    @Beanpublic ServerEndpointExporter serverEndpointExporter(){    return new ServerEndpointExporter();}

    注:如果使用的是外置的 Tomcat 容器,则不需要自己提供 ServerEndpointExporter,因为它将由 Tomcat 容器自己提供和管理。

  9. 假如我们启动项目,此时会报空异常,因为我们在处理用户登录的逻辑中,将登录用户的id存储在HttpSession中,而我们在处理WebSocket连接建立后的onOpen() 中,用EndpointConfig对象的getUserProperties()去获取存储的HttpSession对象,因为我们没有将HttpSession存储到EndpointConfig中,所以获取不到,返回Null。

    我们需要重写ServerEndpointConfig.Configurator类的modifyHandshake()方法,通过HandshakeRequest对象获取我们需要的HttpSession,存储到ServerEndpointConfig中,才能通过getUserProperties()去获取存储的HttpSession对象:

    @Configurationpublic class GetHttpSessionConfigurator extends ServerEndpointConfig.Configurator {    @Override    public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {        HttpSession httpSession = (HttpSession) request.getHttpSession();        //将httpsession对象存储到配置对象ServerEndpointConfig中        sec.getUserProperties().put(HttpSession.class.getName(),httpSession);    }}

    其中ServerEndpointConfig 接口继承了 EndpointConfig 接口,源码如下:

    public interface ServerEndpointConfig extends EndpointConfig{	······  }

3.6.2 HTML 代码

  1. 在页面上使用ul li标签显示聊天室用户列表

  2. 显示正在聊天的用户信息:用户头像、用户昵称


    基于 SpringBoot+WebSocket 无DB实现在线聊天室(附源码)
    用户昵称
  3. 聊天记录区域

  4. 信息输入区域

3.6.3 JS 代码

  1. 当点击左侧用户列表的用户头像时,显示与该用户聊天的聊天界面

    • 设置用户头像、用户昵称
    • 显示聊天界面
    • 从sessionStorage中获取到与该用户的聊天记录并显示到聊天界面
    function open_chat(toUId,toUname,toAUrl) {  //设置聊天区用户信息  toUserAvatar.attr('src',toAUrl);  toUserName.text(toUname);  //显示聊天框  $('.call-chat-body').removeClass('fade');  toUserId = toUId;  //获取历史聊天记录  let chatData = sessionStorage.getItem(toUId);  if (chatData != null) {    //将聊天记录渲染到聊天区    $('#chat-note').html(chatData);  } else {    $('#chat-note').html("");  }}

    注:聊天记录存储到sessionStorage,key为聊天用户的id,非当前登录用户的id,获取聊天记录时通过用户id获取即可

  2. 创建 WebSocket 对象,指定 URL,url 格式为:ws://ip地址:端口号/资源名称

    //创建ws对象let ws = new WebSocket("ws://localhost:8080/wechat");

    注:/wechat为服务端的@ServerEndpoint(value = "/wechat")的 value 值

  3. 绑定 WebSocket 接收服务端发送数据的 onmessage 事件。

    • 通过事件对象获取服务端发送的数据并转为JSON对象。
    • 如果是系统消息,则进一步判断是同步在线用户还是通知用户下线:
      • 同步在线用户:即遍历用户列表拼接HTML字符串,渲染到页面中。
      • 通知用户下线:则将该用户从用户列表中移除,如果有用户正和该用户聊天,则隐藏聊天界面。
    • 如果不是系统消息,则为转发用户发送的消息,首先判断当前是否正在与发消息的人聊天:
      • 如果是,则直接渲染到聊天界面中,并存储到 sessionStorage 中。
      • 如果不是,则直接存储到 sessionStorage 中。
    //接收到服务端推送的消息后触发ws.onmessage = function (e) {  //获取服务端推送的消息  let dataStr = e.data;  //将dataStr转为json对象  let res = JSON.parse(dataStr);  //判断是否是系统消息  if (res.system) {    //系统消息    //用户列表    let message = res.message;    if (res.status) {      //用户在线,拼接html      let str = "";      for (let user of message) {        let userId = user.id;        let username = user.username;        let userAvatarUrl = user.avatarUrl;        if (userId != loginUserId) {          str += "
  4. + userAvatarUrl + "' " + " + userId+"','"+username+"','"+userAvatarUrl + "')">" + username + "
  5. "; } } //添加html chat_user_list.html(str); } else { //下线用户 for (let id of message) { $("#" + id).remove() if(toUserId==id){ $('.call-chat-body').addClass('fade'); } } } } else { //不是系统消息 let fromUserId = res.fromUserId; let message = res.message; let str = "
  6. "+message+"
  7. "; //是否正在与发消息的人聊天 if(toUserId==fromUserId){ //是,显示 $('#chat-note').append(str); } //存储 let chatData = sessionStorage.getItem(fromUserId); if (chatData != null) { chatData+=str; } else { chatData=str; } sessionStorage.setItem(fromUserId,chatData); }}
  8. 点击【发送】按钮,发送消息。

    • 获取用户输入的消息内容。
    • 渲染到聊天界面中。
    • 将消息数据转为JSON格式的字符串,并调用WebSocket 的send()方法将数据发送给服务端,服务端在收到消息后会调用对应的 onMessage()方法进行处理。
    $('#send-message').click(function () {    //获取输入的内容    let data = $('#message-to-send').val();    //清空输入框内容    $('#message-to-send').val("");    let str = "
  9. " + data + "
  10. "; $('#chat-note').append(str); //推送消息给服务器 let json = {"toUserId": toUserId, "message": data} ws.send(JSON.stringify(json));})
  11. 到此,聊天室项目就结束了,使用Maven打包成jar包部署到服务器上试试吧😄😄😄。

4 Docker 部署服务器

4.1 修改 WS URL 地址

  1. 我们需要修改 JS 代码中的 WebSocket 地址,将 localhost:8080 改为服务器的公网 IP 和服务端的端口号,否则客户端无法正确链接到服务端(这里我将服务端的端口改为80端口,浏览器访问时无需在 IP 地址后添加端口号)。

    //创建ws对象let ws = new WebSocket("ws://43.143.180.243:80/wechat");

4.2 Maven 打包

  1. 在 Pom.xml 文件中添加 spring-boot-maven-plugin 插件依赖:

                org.springframework.boot      spring-boot-maven-plugin      

    否则打包运行时会出现没有主清单命令:

  2. 执行 Maven 的 install 命令会将项目打包成 jar 包:

  3. 进入jar包所在目录,使用cmd命令打开命令行,运行命令查看jar包能否在正常启动:

    java -jar chat-0.0.1-SNAPSHOT.jar

    输出以下内容,表示正常启动 :

4.3 Dockerfile 文件

创建一个名为 Dockerfile 的文件,添加以下内容:

FROM jdk1.8   ADD chat-0.0.1-SNAPSHOT.jar chat-0.0.1-SNAPSHOT.jarEXPOSE 80ENTRYPOINT ["java","-jar","chat-0.0.1-SNAPSHOT.jar"]
  • FROM jdk1.8:我们的项目需要java8环境,所以基于 jdk1.8 镜像部署,jdk1.8 镜像构建教程可以查看这篇文章:https://zsjie.blog.csdn.net/article/details/122202628。
  • ADD chat-0.0.1-SNAPSHOT.jar chat-0.0.1-SNAPSHOT.jar:类似 COPY 命令,复制文件或者目录到容器。
  • EXPOSE 80:声明容器需要使用的端口。
  • ENTRYPOINT ["java","-jar","chat-0.0.1-SNAPSHOT.jar"]:类似 CMD 指令,这些命令行参数会被当作参数送给 ENTRYPOINT 指令指定的程序。

4.4 上传服务器

使用 XShell7 远程连接服务器,创建目录:

mkdir -p /usr/local/dockers/web-chat

使用 Xftp7 将项目的 jar 包和 Dockerfile 文件上传到目录下:

4.5 Docker 运行

  1. 使用 XShell7 远程连接服务器,将目录切换到 jar 包和 Dockerfile 文件所在的目录下,执行 build 命令构建镜像:

    docker build -t='web-chat' .
  2. 查看 Docker 下的镜像:

  3. 最后,我们只需要基于 web-chat 镜像创建容器并运行即可:

    docker run -di --name=web-chat -p 80:80 web-chat
  4. 使用命令查看输出日志,项目已经成功运行了:

    docker logs web-chat

4.6 项目结果

浏览器地址栏中输入服务器的ip地址访问项目:

到此,我们的在线聊天室项目就完成了。

转载请注明:文章转载自 http://www.konglu.com/
本文地址:http://www.konglu.com/it/1096814.html
免责声明:

我们致力于保护作者版权,注重分享,被刊用文章【基于 SpringBoot+WebSocket 无DB实现在线聊天室(附源码)】因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理,本文部分文字与图片资源来自于网络,转载此文是出于传递更多信息之目的,若有来源标注错误或侵犯了您的合法权益,请立即通知我们,情况属实,我们会第一时间予以删除,并同时向您表示歉意,谢谢!

我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2023 成都空麓科技有限公司

ICP备案号:蜀ICP备2023000828号-2