当前位置: 首页 > article >正文

vue+websocket实现即时聊天平台

目录

1 什么是websocket

2 实现步骤

2.1 导入依赖

2.2 编写代码


1 什么是websocket

WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议。它主要用于在客户端和服务器之间建立持久的连接,允许实时数据交换。WebSocket 的设计目的是为了提高 Web 应用程序的交互性,减少延迟和带宽的使用。

  • 全双工通信:客户端和服务器可以同时发送和接收数据,而不需要等待对方完成发送。

  • 持久连接:建立一次连接后,可以保持该连接,直到主动关闭。这比传统的 HTTP 请求/响应模型更加高效。

  • 低延迟:由于不需要为每个请求建立新的连接,WebSocket 可以显著减少延迟。

  • 节省带宽:在 WebSocket 中,只有数据被发送而不需要携带大量的头部信息,这减少了带宽的消耗。

2 实现步骤

实施前提:默认在springBoot环境下实施

2.1 导入依赖

<!--WebSocket依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
    <version> 3.3.4</version>
</dependency>

2.2 编写代码

WebSocketConfig:主要实现websocket的一些配置
package com.hyh.admin.config.websocket;

import org.springframework.boot.web.servlet.ServletContextInitializer;
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;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;

/**
 * WebSocket配置
 * @author hyh
 */
@Configuration
@EnableWebSocket
public class WebSocketConfig implements ServletContextInitializer {
    /*
     *  ServerEndpointExporter 作用
     *  这个Bean会自动注册使用@ServerEndpoint注解声明的Websocket endpoint
     */
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }

    /*
     * 解除websocket对数据大小的限制
     * @param servletContext Servlet上下文
     *
     */
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        // 解除websocket对数据大小的限制
        servletContext.setInitParameter("org.apache.tomcat.websocket.textBufferSize","10240000");
        servletContext.setInitParameter("org.apache.tomcat.websocket.binaryBufferSize","10240000");
    }
}
WebSocketSingleServe:具体的实现聊天的实时代码需求
package com.hyh.admin.config.websocket;

import com.hyh.ad.common.core.domain.model.SysUser;
import com.hyh.admin.config.websocket.context.SpringBeanContext;
import com.hyh.admin.domain.Messages;
import com.hyh.admin.service.MessageService;
import com.hyh.admin.sys.service.ISysUserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * WebSocket 单聊服务端
 */
@ServerEndpoint("/singleChat/{username}")
@Component
public class WebSocketSingleServe implements InitializingBean {

    private static final Logger log = LoggerFactory.getLogger(WebSocketSingleServe.class);

    // 记录当前在线的连接
    public static final Map<String, Session> sessionMap = new ConcurrentHashMap<>();

    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(@PathParam("username") String username, Session session) {
        // 将用户的session放入map中
        session.getUserProperties().put("username", username);
        sessionMap.put(username, session);
        log.info("用户:{}",session.getUserProperties().get("username"));
        log.info("用户:{} 连接成功,session:{},总数:{}", username, session.getId(), sessionMap.size());
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose(Session session) {
        try {
            sessionMap.values().remove(session);
            log.info("连接关闭,session:{},总数:{}", session.getId(), sessionMap.size());
        } catch (Exception e) {
            log.error("连接关闭异常:{}", e.getMessage());
        }
    }

    /**
     * 收到客户端消息后调用的方法
     * @param message 客户端发送过来的消息
     */
    @OnMessage
    public void onMessage(String message, Session fromSession) {
        // 假设消息格式为 "username:file:data"
        String[] parts = message.split(":", 3);
        if (parts.length == 3) {
            String targetUsername = parts[0].trim(); // 目标用户
            String type = parts[1].trim(); // 消息类型(text/file)
            String content = parts[2].trim(); // 消息内容

            log.info("收到消息:{},类型:{},内容:{}", targetUsername, type, content);
            // 根据类型处理消息
            if ("text".equals(type)) {
                // 发送文本消息
                sendMessageToUser(targetUsername, content, type);
            } else if ("image".equals(type)) {
                // 发送文件消息
                sendFileToUser(targetUsername, content, type);
            }else if ("file".equals(type)) {
                // 发送文件消息
                sendFileToUser(targetUsername, content, "file");
            }

            // 消息持久化
            String username = (String) fromSession.getUserProperties().get("username");
            saveMessage(username, targetUsername, content, type);
        }
    }



    /*
     * 消息持久化
     */
    private void saveMessage(String sendUsername, String targetUsername, String msg, String type) {
        // 保存消息
        try {
            MessageService messageService = SpringBeanContext.getContext().getBean(MessageService.class);
            ISysUserService sysUserService = SpringBeanContext.getContext().getBean(ISysUserService.class);
            SysUser targetUser = sysUserService.selectUserByUserName(targetUsername);
            Long targetUserId = targetUser.getId();
            SysUser sendUser = sysUserService.selectUserByUserName(sendUsername);
            Long userId = sendUser.getId();

            Messages messages = new Messages();
            messages.setSenderId(userId);
            messages.setReceiverId(targetUserId);
            messages.setContent(msg);
            messages.setMessageType(type); // 保存消息类型

            messageService.addMessage(messages);
            log.info("消息持久化成功");
        } catch (Exception e) {
            log.error("消息持久化失败:{}", e.getMessage());
        }
    }

    /*
     *  发送文件给用户
     */
    private void sendFileToUser(String targetUsername, String fileContent, String type) {
        Session targetSession = sessionMap.get(targetUsername);
        if (targetSession != null) {
            try {
                targetSession.getBasicRemote().sendText(type + "|" + fileContent); // 文件发送格式
                log.info("发送文件给用户:{},发送成功", targetUsername);
            } catch (IOException e) {
                log.error("发送文件失败:{}", e.getMessage());
            }
        }
    }


    /**
     * 发生错误时调用
     */
    @OnError
    public void onError(Session session, Throwable error) {
        log.error("发生错误,session:{} ,错误信息:{}", session.getId(), error);
    }

    /**
     * 服务端发送消息给指定用户
     * @param username 目标用户
     * @param message 消息内容
     */
    public void sendMessageToUser(String username, String message, String type) {
        Session session = sessionMap.get(username);
        if (session != null && session.isOpen()) {
            try {
                session.getBasicRemote().sendText(type + "|" + message);
                log.info("发送给用户:{},内容:{}", username, message);
            } catch (IOException e) {
                log.error("发送消息失败:{}", e.getMessage());
            }
        } else {
            log.warn("用户:{} 不在线,无法发送消息", username);
        }
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        log.info("WebSocket服务端启动");
    }
}

  onopen方法主要用于连接的的方法,所有和websocket发起连接的客户端都会经过这个方法。

  onmessage方法主要用于发送消息的方法,其中定义了发送消息的格式,可以自行定义。

   前端代码:

 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>聊天界面</title>
    <style>
        body { font-family: Arial, sans-serif; }
        #messages { border: 1px solid #ccc; height: 300px; overflow-y: scroll; margin-bottom: 10px; }
        input, button { margin: 5px; }
    </style>
</head>
<body>
<h2>聊天界面</h2>
<input type="text" id="targetUser" placeholder="输入目标用户名...">
<input type="text" id="message" placeholder="输入消息...">
<button id="sendBtn">发送</button>
<div id="messages"></div>
<img src="https://c-ssl.duitang.com/uploads/item/202003/27/20200327141738_ulbvu.jpg" alt="">
<script>
    const username = prompt("请输入您的用户名:"); // 获取当前用户的用户名
    const socket = new WebSocket(`ws://127.0.0.1:8088/singleChat/${username}`);

    socket.onopen = function() {
        console.log(`${username} 已连接`);
    };

    socket.onmessage = function(event) {
        const messagesDiv = document.getElementById("messages");
        messagesDiv.innerHTML += `<p>${event.data}</p>`;
        messagesDiv.scrollTop = messagesDiv.scrollHeight; // 滚动到底部
    };

    document.getElementById("sendBtn").onclick = function() {
        const targetUser = document.getElementById("targetUser").value;
        const messageInput = document.getElementById("message").value;
        const message = `${targetUser}:text:${messageInput}`; // 格式化消息
        socket.send(message);

        // 显示自己发送的消息
        const messagesDiv = document.getElementById("messages");
        messagesDiv.innerHTML += `<p>我: ${messageInput}</p>`;
        messagesDiv.scrollTop = messagesDiv.scrollHeight; // 滚动到底部

        document.getElementById("message").value = "";  // 清空输入框
    };
</script>
</body>
</html>

  

vue的部分代码和项目完整的截图为:

 this.socket = new WebSocket(
        `ws://127.0.0.1:8088/singleChat/${localStorage.getItem("username")}`
      );

      this.socket.onopen = () => {
        console.log(localStorage.getItem("username") + " 连接成功");
      };

      // 只设置一次 onmessage 处理逻辑
      this.socket.onmessage = (event) => {
        const message = event.data; // 假设格式为 "type:content"
        const parts = message.split("|"); // 按冒号分割

        if (parts.length === 2) {
          const type = parts[0].trim(); // 消息类型
          const content = parts[1].trim(); // 消息内容

          this.contactRecord.push({
            id: Date.now(), // 使用时间戳作为消息 ID
            senderId: this.user.id, // 或者其他用户的 ID
            content: content,
            messageType: type, // 添加类型
          });

          // 进度条滚动到底部
          this.scrollToBottom();
        }
      };
    },

 

需要源码的请私信我:

谢谢各位的支持!!! 


http://www.kler.cn/a/377523.html

相关文章:

  • ffplay 实现视频流中音频的延迟
  • ffmpeg:视频字幕嵌入(GPU加速)
  • 【笔记】前后端互通中前端登录无响应
  • Redis 初学者指南
  • yolo系列各种环境配置运行
  • mark 一些攻防 prompt
  • C/C++--10--VS2008编译C语言时如何将const LineA * 里面的值赋值给另外一个结构体LineA?
  • 站群服务器对SEO优化的具体帮助是什么
  • goframe开发一个企业网站 前端界面 拆分界面7
  • Linux Qt 6安装Oracle QOCI SQL Driver插件(适用WSL)
  • 设计模式-观察者模式(代码实现、源码级别应用、使用场景)
  • R6:LSTM实现糖尿病探索与预测
  • 中药大数据(四):数据预处理+管理端的功能实现
  • linux-valgrind检测分析C/C++程序(三)
  • 4. STM32之TIM实验--输出比较(PWM输出,电机,四轴飞行器,智能车,机器人)--(实验2:PWM驱动舵机)
  • Java方法的使用
  • MP4650模块改为固定电压记录
  • 【C++】深入理解 C++ 输入输出同步机制:为什么 cin/cout 没有 scanf/printf 快?
  • Java: 遍历 Map
  • Ubuntu编译linux内核指南(适用阿里云、腾讯云等远程服务器;包括添加Android支持)
  • golang有序map
  • vue3 + ts + element-plus 二次封装 el-table
  • ✨ Midjourney中文版:创意启航,绘梦无界 ✨
  • Harmony NEXT - AlphabetIndexer实现联系人字母索引
  • 密码学简介
  • Python入门:如何掌控多线程数量