java websocket实现聊天室 附源码

news2024/9/24 1:27:40

目录

1.Socket基础知识

2.socket代码实现

2.1 引入依赖

2.2 配置websocket

2.3 websocket的使用

2.4 webSocket服务端模块

2.5 前端代码

3.测试发送消息

4.websocket源码地址


1.Socket基础知识

Socket(套接字)用于描述IP地址和端口,是通信链的句柄,应用程序可以通过Socket向网络发出请求或者应答网络请求。

Socket是支持TCP/IP协议的网络通信的基本操作单元,是对网络通信过程中端点的抽象表示,包含了进行网络通信所必需的5种信息:连接所使用的协议、本地主机的IP地址、本地进程的协议端口、远地主机的IP地址以及远地进程的协议端口。
 

2.socket代码实现

2.1 引入依赖

我这里使用了swagger,方便调试

        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!--websocket-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>
        <!--springboot-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!--swagger包-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-boot-starter</artifactId>
            <version>3.0.0</version>
        </dependency>
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-spring-boot-starter</artifactId>
            <version>3.0.3</version>
        </dependency>

2.2 配置websocket

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

/**
 * @author yt
 * @create 2023/4/20 13:45
 */
@Configuration
public class WebSocketConfig {
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

2.3 websocket的使用

前端传一个用户id,将用户id和对应的session进行绑定,一对一就是客户端根据对应的用户id将消息发送给对应的session

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * https://blog.csdn.net/weixin_56079712/article/details/121602008
 *
 * @author yt
 * @create 2023/4/20 13:46
 */

@ServerEndpoint("/websocket/{userId}")
@Component
public class WebSocketServer {
    private static final Logger logger = LoggerFactory.getLogger(WebSocketServer.class);


    /**
     * 记录当前在线连接数
     */
    private static AtomicInteger onlineCount = new AtomicInteger(0);


    /**
     * 用户id  对应的session
     */
    private static Map<String, Session> userIdToSession = new ConcurrentHashMap<>();
    /**
     * session  对应的用户id
     */
    private static Map<Session, String> SessionTouserId = new ConcurrentHashMap<>();


    /**
     * 创建一个数组用来存放所有需要向客户端发送消息的窗口号
     */
    private static List<String> list = new ArrayList();

    public static List<String> getList() {
        return list;
    }


    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("userId") String userId) {
        // 在线数加1
        onlineCount.incrementAndGet();
        userIdToSession.put(userId, session);
        SessionTouserId.put(session, userId);
        list.add(userId);
        String msg = userId + "连接成功";
        sendMessage(msg, session);
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose(Session session) {
        //在线连接-1
        onlineCount.decrementAndGet();
        String userId = SessionTouserId.get(session);
        list.remove(userId);
        logger.info(userId + "断开连接,当前连接数为 " + onlineCount);
    }

    /**
     * 收到客户端消息后调用的方法
     *
     * @param message 客户端发送过来的消息
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        logger.info("客户端发送消息为" + message);
        sendMessage(message, session);
    }

    /**
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error) {
        System.out.println(" 发生错误 ");
        error.printStackTrace();
    }

    /**
     * 服务端发送消息给客户端
     */
    public void sendMessage(String message, Session session) {
        try {
            logger.info("服务端给客户端[{}]发送消息[{}]", session.getId(), message);
            session.getBasicRemote().sendText(message);
        } catch (Exception e) {
            logger.error("服务端发送消息给客户端失败:{}", e);
        }
    }

    public void sendOneMessage(String message, String userId) {
        try {
            Session session = userIdToSession.get(userId);
            if (session != null && session.isOpen()) {
                logger.info("服务端给客户端[{}]发送消息[{}]", session.getId(), message);
                session.getBasicRemote().sendText(message);
            }
        } catch (Exception e) {
            logger.error("服务端发送消息给客户端失败:{}", e);
        }
    }

    public void sendAllMessage(String message) {
        try {
            if (list.size() < 1) {
                logger.info("当前在线人数为0,发送消息为" + message);
                return;
            }
            for (String userId : list) {
                Session session = userIdToSession.get(userId);
                if (session != null && session.isOpen()) {
                    logger.info("服务端给客户端[{}]发送消息[{}]", session.getId(), message);
                    session.getBasicRemote().sendText(message);
                }
            }

        } catch (Exception e) {
            logger.error("服务端发送消息给客户端失败:{}", e);
        }
    }

}

2.4 webSocket服务端模块

import com.yt.websocket.websocket.WebSocketServer;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@Api(tags = "webSocket服务端模块")
@RestController
@RequestMapping("/test")
public class SendMessageController {

    @Autowired
    private WebSocketServer webSocketServer;

    @ApiOperation(value = "群发消息")
    @GetMapping("/sendMsg/{msg}")
    public String sendAllMsg(@PathVariable("msg") String msg) {
        webSocketServer.sendAllMessage(msg);
        return "群发消息【" + msg + "】发送成功";
    }

    @ApiOperation(value = "给单个用户发送消息")
    @GetMapping("/sendOneMsg/{msg}/{userId}")
    public String sendOneMsg(@PathVariable("msg") String msg, @PathVariable("userId") String userId) {
        webSocketServer.sendOneMessage(msg, userId);
        return "消息【" + msg + "】给 " + userId + "发送成功";
    }

    @ApiOperation(value = "获取在线用户")
    @GetMapping("/get/userId")
    public List getUserId() {
        return WebSocketServer.getList();
    }
}

2.5 前端代码

<!DOCTYPE html>
<meta charset="utf-8"/>
<title>WebSocket 测试 乔丹</title>
<body>
<h2>WebSocket 测试 乔丹</h2>
<HEADER class="header">
    <a class="back" ></a>
    <h3 class="tit">服务端:</h3>
</HEADER>
<div id="message">

</div>

<HEADER class="header1">
    <a class="back" ></a>
    <h3 class="tit">客户端:</h3>
</HEADER>

<div id="footer">
    <input id="text" class="my-input" type="text" />
    <button onclick="send()" >发&nbsp;送</button>
</div>

<div id="footer1">
    <br/>
    <button onclick="closeWebSocket()" >关闭websocket连接</button>
    <button onclick="openWebSocket()" >建立websocket连接</button>
</div>


<script language="javascript" type="text/javascript">
    var websocket = null;
    //判断当前浏览器是否支持WebSocket,是则创建WebSocket
    if ('WebSocket' in window) {
        console.log("浏览器支持Websocket");
        websocket = new WebSocket("ws://localhost:9091/websocket/乔丹");
    } else {
        alert('当前浏览器 Not support websocket')
    }

    //连接发生错误的回调方法
    websocket.onerror = function () {
        console.log("WebSocket连接发生错误");
        setMessageInnerHTML("WebSocket连接发生错误");
    };
    //连接成功建立的回调方法
    websocket.onopen = function () {
//	setMessageInnerHTML("WebSocket连接成功");
        console.log("WebSocket连接成功");
    }
    //接收到消息的回调方法
    websocket.onmessage = function (event) {
        if (event.data) {
            setMessageInnerHTML(event.data);
        }
        console.log(event.data);
    }
    //连接关闭的回调方法
    websocket.onclose = function () {
        console.log("WebSocket连接关闭");
    }

    //关闭WebSocket连接
    function closeWebSocket() {
        websocket.close();
    }

    //发送消息
    function send() {
        var message = document.getElementById('text').value;
        websocket.send(message);
    }


    // 建立连接的方法
    function openWebSocket() {
        websocket = new WebSocket("ws://localhost:8888/websocket/乔丹");
        websocket.onopen = function () {
//	setMessageInnerHTML("WebSocket连接成功");
            console.log("WebSocket连接成功");
        }
    }


    //将消息显示在网页上

    function setMessageInnerHTML(innerHTML) {

        document.getElementById('message').innerHTML += innerHTML + '<br/>';

    }


    //如果websocket连接还没断开就关闭了窗口,后台server端会抛异常。
    //所以增加监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接
    window.onbeforeunload = function () {
        closeWebSocket();
    }

</script>
</body>
<div id="output"></div>
</html>

3.测试发送消息

用户端:

服务端:

用户端成功接收到消息

 

4.websocket源码地址

https://gitee.com/yutao0730/web-socket-chat-room.git

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/464610.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

4年测试工作经验,跳槽之后面试20余家公司的总结

先说一下自己的个人情况&#xff0c;普通二本计算机专业毕业&#xff0c;懂python&#xff0c;会写脚本&#xff0c;会selenium&#xff0c;会性能&#xff0c;然而离职后到今天都没有收到一份offer&#xff01;一直在待业中&#xff0c;从离职第一天就开始准备简历&#xff0c…

【Vue 基础】尚品汇项目-02-路由组件的搭建

项目路由说明&#xff1a; 前端的路由&#xff1a;Key-Value键值对 Key&#xff1a;URL&#xff08;地址栏中的路径&#xff09; Value&#xff1a;相应的路由组件 作用&#xff1a;设定访问路径&#xff0c;并将路径和组件映射起来&#xff08;就是用于局部刷新页面&#xff0…

Vue+Openlayers+proj4实现坐标系转换

场景 Vue中使用Openlayers加载Geoserver发布的TileWMS&#xff1a; Vue中使用Openlayers加载Geoserver发布的TileWMS_霸道流氓气质的博客-CSDN博客 在上面的基础上实现不同坐标系坐标数据的转换。 Openlayers中默认的坐标系是EPSG:900913 EPSG:900913等效于EPSG:3857 可在…

kafka集群压测与优化

影响kafka集群性能的因数有多个&#xff0c;网络带宽、cpu、内存、磁盘读写速度、副本数、分区数、broker数量、内存缓存等因素都会影响kafka集群的性能 1.优化kafka集群配置 server.properties配置文件优化 num.network.threads4 num.io.threads4 socket.send.buffer.bytes…

提升供应链运营效率:企业如何规范化供应商关系?

在现代企业运营中&#xff0c;采购和供应链管理已成为至关重要的环节。企业尤其需要管理好自身供应商&#xff0c;才能够获得优质的原材料和零部件&#xff0c;并确保生产和销售的正常进行。本文将从供应商的筛选、双方合作的流程管理、团队建设等方面&#xff0c;为大家介绍如…

腾讯云COS+SpringBOot实现文件上传下载功能

文章目录 第一步&#xff1a;在.yml文件中配置对应秘钥内容第二步&#xff1a;完成COSConfig类编写第三步&#xff1a;编写Controller类Bug提示&#xff1a; 最近一直在做一个项目&#xff0c;需要支持视频&#xff0c;音频&#xff0c;图片的上传&#xff0c;前面介绍的都是把…

新一代边缘计算盒子,英码科技边缘计算盒子SY-E160

SY-E160 是英码科技推出的新一代智能工作站&#xff0c;内部集成了 4 核强悍处理器 A551.5 GHz&#xff0c;其内置的算力核拥有 16Tops 超强算力。SY-E160 工作站采用低功耗技术设计&#xff0c;支持 宽温度环境工作&#xff0c;可以灵活部署于各种 AI 场景中。 SY-E160 深元 A…

在SaleSmartly管理客户互动的 3 个好处

交互是关系的小组成部分。随着时间的推移&#xff0c;互动的质量决定了人们对这段关系的投入程度&#xff0c;同样的动态也适用于客户和品牌。在跨境电商业务中&#xff0c;每一次互动都是建立信任或失望的机会。 对于许多公司来说&#xff0c;客户互动的主要领域是客户服务功能…

机器学习 - Seaborn 练习, 常见功能查阅

机器学习记录 Seaborn Seaborn 是一个基于 Matplotlib 的 Python 可视化库,提供了一些内置数据集以及进行统计数据可视化和模型现场的 API。 sns.get_dataset_names() 方法会返回一个字符串列表,包含所有内置数据集的名称 练习 Seaborn 依赖Matplotlib, NumPy, SciPy, Pan…

python装不上库的心得

如果在相同的环境下别人能安装上&#xff0c;但你安装不上&#xff0c;可以考虑下面几点 目录 1 升级pip 2 有的包不用刻意装&#xff0c;它跟着别的就一起装了 3 缺少外部依赖 4 有的库用conda安装要方便一点 5 导入名不一定是包名 6 编译安装 7 安装包时&…

西门子S7-1500与FANUC机器人进行EtherNetIP通信的具体方法示例

西门子S7-1500与FANUC机器人进行EtherNetIP通信的具体方法示例 具体方法可参考以下内容: 以下示例中TIA博途的版本为V17,本例中PLC做主站,机器人做从站 一、 西门子PLC一侧的组态设置和编程 首先,我们需要到下载所需的EtherNetIP通信库文件,大家可自行百度获取或者从以下链…

Kafka的核心概念

一、消息&#xff08;Record&#xff09; 消息是 Kafka 中最基本的数据单元。消息由一串字节构成&#xff0c;其中主要由 key 和 value 构成&#xff0c;key 和 value 也都是 byte 数组。消息的真正有效负载是 value 部分的数据。为了提高网络和存储的利用率&#xff0c;生产者…

TCP流量控制与拥塞控制

什么是流量控制 一条TCP连接的每一侧主机都为该连接设置了接收缓存。当该TCP连接接收到正确的、有序的报文段&#xff0c;就会将数据放入接收缓存。相关联的应用会从缓存中读取数据。 如果发送者发送数据过快、过多&#xff0c;而接收方的应用程序从缓冲区读取的速度较慢&…

【C++】二叉搜索树经典OJ题目

文章目录 根据二叉树创建字符串二叉树的层序遍历二叉树的层序遍历II二叉树的最近公共祖先二叉搜索树与双向链表从前序与中序遍历序列构造二叉树从中序与后序遍历序列构造二叉树二叉树的前序遍历(非递归)二叉树的中序遍历(非递归)二叉树的后序遍历(非递归) 根据二叉树创建字符串…

捷报连连丨小匠物联SILA第六届“智光杯”荣获两项跨界大奖

2023年4月26日&#xff0c;SILA第六届“智光杯”跨界奖项名单公布。 喜讯传来&#xff0c;小匠物联荣获SILA第六届“智光杯”跨界奖项-全屋智能及商用系统优秀新供应链奖、智能照明新锐优秀新供应链奖。 “智光杯”“智光杯”由上海浦东智能照明联合会&#xff08;SILA&#xf…

【校招VIP】简历上项目名称看起来不重复,是安全相关项目,但是为什么简历通过率还是低?

在简历指导的直播里面&#xff0c;我看了一个新的项目。 这是个信息安全方向的一个项目&#xff0c;之前倒是没有看过。 所以项目的介绍本身是看不出它的重复度的。 但是一往下看 12345的要点&#xff0c;就发现这又是一个烂大街的。 项目本身的逻辑是没有写的。 然后又是所…

【Linux脚本篇】shell变量的使用

目录 &#x1f341;shell变量替换 &#x1f341;定义变量 &#x1f341;shell变量运算 &#x1f342;整数运算 &#x1f342;小数运算 &#x1f990;博客主页&#xff1a;大虾好吃吗的博客 &#x1f990;专栏地址&#xff1a;Linux从入门到精通 shell变量替换 ${变量#匹配规则}…

设计模式 -- 访问者模式

前言 月是一轮明镜,晶莹剔透,代表着一张白纸(啥也不懂) 央是一片海洋,海乃百川,代表着一块海绵(吸纳万物) 泽是一柄利剑,千锤百炼,代表着千百锤炼(输入输出) 月央泽,学习的一种过程,从白纸->吸收各种知识->不断输入输出变成自己的内容 希望大家一起坚持这个过程,也同…

SpringCloud入门实战(七)-Hystrix服务降级

&#x1f4dd; 学技术、更要掌握学习的方法&#xff0c;一起学习&#xff0c;让进步发生 &#x1f469;&#x1f3fb; 作者&#xff1a;一只IT攻城狮 。 &#x1f490;学习建议&#xff1a;1、养成习惯&#xff0c;学习java的任何一个技术&#xff0c;都可以先去官网先看看&…

三翼鸟:传统品牌只盯局部,智慧品牌谋划全局

“当今企业之间的竞争&#xff0c;不是产品之间的竞争&#xff0c;而是商业模式之间的竞争。”很多人都听过现代管理学之父德鲁克的这句话&#xff0c;但又有多少人真正理解了它&#xff1f; 以当下的语境去看&#xff0c;这里其实就是“自利”和“共荣”的区别。前者&#xf…