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

SpringBoot 使用WebSocket打造在线聊天室

1、WebSocket是HTML5开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。在WebSocket API中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。
2、浏览器通过 JavaScript 向服务器发出建立 WebSocket 连接的请求,连接建立以后,客户端和服务器端就可以通过 TCP 连接直接交换数据。
3、当你获取 Web Socket 连接后,你可以通过 send() 方法来向服务器发送数据,并通过 onmessage 事件来接收服务器返回的数据。

使用说明:

使用步骤:1、获取WebSocket客户端对象。

例如: var webSocket = new WebSocket(url);

使用步骤:2、获取WebSocket回调函数。

例如:webSocket.onmessage = function (event) {console.log('WebSocket收到消息:' + event.data);

事件类型WebSocket回调函数事件描述
openwebSocket.onopen当打开连接后触发
messagewebSocket.onmessage当客户端接收服务端数据时触发
errorwebSocket.onerror当通信异常时触发
closewebSocket.onclose当连接关闭时触发
使用步骤:3、发送消息给服务端

例如:webSokcet.send(jsonStr) 结合实际场景 本案例采用JSON字符串进行消息通信。


 

先引入websocket依赖

1

2

3

4

5

<!-- websocket消息推送 -->

<dependency>

    <groupId>org.springframework.boot</groupId>

    <artifactId>spring-boot-starter-websocket</artifactId>

</dependency>

添加 WebSocketConfig 配置

1

2

3

4

5

6

7

8

9

10

11

12

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.web.socket.server.standard.ServerEndpointExporter;

@Configuration

public class WebSocketConfig {

    @Bean

    public ServerEndpointExporter serverEndpointExporter() {

        return new ServerEndpointExporter();

    }

实体bean接收客户端发过来的信息

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

@Data

public class SocketMsg {

    /**

     * 聊天类型 0 群聊 1 单聊

     **/

    private int type;

    /**

     * 发送者

     **/

    private String sendOutUser;

    /**

     * 接受者

     **/

    private String receiveUser;

    /**

     * 消息

     **/

    private String msg;

}

WebSocketUtil

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

import cn.hutool.json.JSONUtil;

import org.springframework.stereotype.Component;

import javax.websocket.*;

import javax.websocket.server.PathParam;

import javax.websocket.server.ServerEndpoint;

import java.util.concurrent.ConcurrentHashMap;

import java.util.concurrent.CopyOnWriteArraySet;

/**

 * WebSocket 连接测试

 */

@Component

@ServerEndpoint("/web-socket/{userName}")

public class WebSocketUtil {

    private String userName;

    private Session session;

    /** 固定前缀  */

    private static final String USER_NAME_PREFIX = "user_name_";

    /**

     * 用来存放每个客户端对应的MyWebSocket对象。

     **/

    private static CopyOnWriteArraySet<WebSocketUtil> webSocketSet = new CopyOnWriteArraySet<>();

    /**

     * 存放Session集合,方便推送消息 (javax.websocket.Session)

     */

    private static ConcurrentHashMap<String, Session> sessionMap = new ConcurrentHashMap<>();

    /**

     * 私聊:向指定客户端推送消息

     */

    public synchronized static void privateMessage(SocketMsg socketMsg) {

        //接收消息的用户

        Session receiveUser = sessionMap.get(USER_NAME_PREFIX + socketMsg.getReceiveUser());

        //发送给接收者

        if(receiveUser != null){

            //发送给接收者

            System.out.println(socketMsg.getSendOutUser()+" 向 "+socketMsg.getReceiveUser()+" 发送了一条消息:"+socketMsg.getMsg());

            receiveUser.getAsyncRemote().sendText(socketMsg.getSendOutUser()+":"+socketMsg.getMsg());

        }else{

            //发送消息的用户

            System.out.println(socketMsg.getSendOutUser()+" 私聊的用户 "+socketMsg.getReceiveUser()+" 不在线或者输入的用户名不对");

            Session sendOutUser = sessionMap.get(USER_NAME_PREFIX + socketMsg.getSendOutUser());

            //将系统提示推送给发送者

            sendOutUser.getAsyncRemote().sendText("系统消息:对方不在线或者您输入的用户名不对");

        }

    }

    /**

     * 群聊:公开聊天记录

     * @param userName 发送者的用户名称(当前用户)

     * @param message 发送的消息

     * @param flag 用来标识 是否要将消息推送给 当前用户

     */

    public synchronized static void publicMessage(String userName,String message,boolean flag) {

        for (WebSocketUtil item : webSocketSet) {

            Session session = item.session;

            if (flag){

                session.getAsyncRemote().sendText(message);

            }else {

                //获取发送这条消息的用户

                Session currentUser = sessionMap.get(USER_NAME_PREFIX + userName);

                //消息不用推送到发送者的客户端

                if (!session.getId().equals(currentUser.getId())){

                    session.getAsyncRemote().sendText(message);

                }

            }

        }

        System.out.println("公共频道接收了一条消息:"+message);

    }

    /**

     * 监听:连接成功

     * @param session

     * @param userName 连接的用户名

     */

    @OnOpen

    public void onOpen(Session session, @PathParam("userName") String userName) {

        this.userName = userName;

        this.session = session;

        sessionMap.put(USER_NAME_PREFIX + userName, session);

        webSocketSet.add(this);

        //在线数加1

        String tips = userName+" 加入聊天室。当前聊天室人数为" + webSocketSet.size();

        System.out.println(tips);

        publicMessage(userName,tips,true);

    }

    /**

     * 监听:收到客户端发送的消息

     * @param message 发送的信息(json格式,里面是 SocketMsg 的信息)

     */

    @OnMessage

    public void onMessage(String message) {

        if (JSONUtil.isTypeJSONObject(message)) {

            SocketMsg socketMsg = JSONUtil.toBean(message, SocketMsg.class);

            if(socketMsg.getType() == 1){

                //单聊,需要找到发送者和接受者

                privateMessage(socketMsg);

            }else{

                //群发消息

                publicMessage(socketMsg.getSendOutUser(),socketMsg.getSendOutUser()+": "+socketMsg.getMsg(),false);

            }

        }

    }

    /**

     * 监听: 连接关闭

     */

    @OnClose

    public void onClose() {

        if (sessionMap.containsKey(USER_NAME_PREFIX + userName)) {

            //连接关闭后,将此websocket从set中删除

            sessionMap.remove(USER_NAME_PREFIX + userName);

            webSocketSet.remove(this);

        }

        String tips = userName+" 退出聊天室。当前聊天室人数为" + webSocketSet.size();

        System.out.println(tips);

        publicMessage(userName,tips,true);

    }

    /**

     * 监听:发生异常

     * @param error

     */

    @OnError

    public void onError(Throwable error) {

        System.out.println("userName为:" + userName + ",发生错误:" + error.getMessage());

        error.printStackTrace();

    }

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

<!DOCTYPE html>

<html lang="en">

    <head>

        <meta charset="UTF-8">

        <meta http-equiv="X-UA-Compatible" content="IE=edge">

        <meta name="viewport" content="width=device-width, initial-scale=1.0">

        <title>聊天室</title>

        <script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>

        <style type="text/css">

            input{

                width: 150px;

                height: 30px;

                line-height: 25px;

                padding: 5px 10px;

                border-radius: 5px;

                border: 2px solid;

                font-size: 16px;

            }

            #msg{

                width: 300px;

            }

            button{

                width: 80px;

                height: 44px;

                padding: 5px 20px;

                border-radius: 5px;

            }

        </style>

    </head>

    <body>

        聊天室<br/><br/>

        <input type="text" id="sendOutUser" placeholder="自己的用户名">

        <button onclick="connectWebSocket()">上线</button>

        <button onclick="closeWebSocket()">下线</button>

        <br/><br>

        <input type="text" id="msg" placeholder="要发送的信息"/>

        <input type="text" id="receiveUser" placeholder="接收人的用户名"/>

        <button onclick="send()">发送</button>

        <br><br>

        <hr>

        <div id="msgList"></div>

        <script type="text/javascript">

            var websocket = null;

            //连接WebSocket

            function connectWebSocket() {

                var sendOutUser = document.getElementById("sendOutUser").value;

                if (sendOutUser === "") {

                    alert("请输入用户名");

                    return;

                }

                //判断当前浏览器是否支持websocket

                if ('WebSocket' in window) {

                    websocket = new WebSocket("ws://localhost:7070/web-socket/"+document.getElementById("sendOutUser").value);

                } else {

                    alert('当前浏览器 not support websocket')

                }

                //连接发生错误的回调方法

                websocket.onerror = function () {

                    alert("连接发生错误");

                };

                //连接成功建立的回调方法

                websocket.onopen = function () {

                    var sendOutUser = document.getElementById("sendOutUser")

                    sendOutUser.readOnly = true

                    sendOutUser.style.backgroundColor='#ddd'

                }

                //接收到消息的回调方法

                websocket.onmessage = function (event) {

                    console.log(event.data)

                    innerdiv("",event.data)

                }

                //连接关闭的回调方法

                websocket.onclose = function () {

                    innerdiv("","websocket连接关闭");

                }

                //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。

                window.onbeforeunload = function () {

                    closewebsocket();

                }

            }

            //关闭连接

            function closeWebSocket() {

                websocket.close();

            }

            //发送消息

            function send() {

                var m = new Map(); // 空Map

                var sendOutId = document.getElementById("sendOutUser")  //发送者

                var msg = document.getElementById("msg").value  //发送消息

                if (msg === "") {

                    alert("请输入消息");

                    return;

                }

                var receiveUser = document.getElementById("receiveUser").value //接收者

                m.set("sendOutUser",sendOutUser.value);

                m.set("msg",msg)

                // 接收者为空时,type为群聊,否则为私聊

                if (receiveUser === "") {

                    m.set("type",0)

                }else{

                    m.set("receiveUser",receiveUser)

                    m.set("type",1)

                }

                json = mapToJson(m)

                websocket.send(json)

                innerdiv("我",msg)

            }

            //map转换为json

            function  mapToJson(map) {

                var obj= Object.create(null);

                for (var[k,v] of map) {

                    obj[k] = v;

                }

                return JSON.stringify(obj);

            }

            //显示聊天记录到页面

            function innerdiv(id,txt){

                var msgList = document.getElementById("msgList")

                if (id === "") {

                    msgList.innerHTML += "<div>" + txt + "</div><br>"

                }else{

                    msgList.innerHTML += "<div>"+ id +": "+txt+ "</div><br>"

                }

            }

        </script>

    </body>

</html>

相关文章:

  • Ubuntu配置Yolov8环境并训练自己的数据集
  • Power Automate-创建审批流
  • GetPrivateProfileSection使用
  • IP-guard WebServer RCE漏洞复现
  • JavaEE初阶学习:Linux 基本使用和 web 程序部署
  • 十进制转换成2进制
  • 建设大型综合运维平台,对接集成多厂商网管系统
  • 线程池创建、执行、销毁的原理解析
  • Python 解决tkinter的Menu菜单command参数与bind方法共用触发事件
  • 如何设置静态IP
  • 【云栖2023】王峰:开源大数据平台3.0技术解读
  • RedisTemplate 使用 pipeline 时需要注意的问题
  • 在opencv OpenCV中打开相机摄像头,用分水岭算法实时实现图像的分割与提取
  • 策略模式~
  • 本地浏览器全局翻译 demo 以火狐firefox为例【免费-简单】
  • [PHP内核探索]PHP中的哈希表
  • 【vuex入门系列02】mutation接收单个参数和多个参数
  • android百种动画侧滑库、步骤视图、TextView效果、社交、搜房、K线图等源码
  • Java 9 被无情抛弃,Java 8 直接升级到 Java 10!!
  • JavaScript工作原理(五):深入了解WebSockets,HTTP/2和SSE,以及如何选择
  • Java新版本的开发已正式进入轨道,版本号18.3
  • Linux链接文件
  • vue自定义指令实现v-tap插件
  • 简单数学运算程序(不定期更新)
  • 如何胜任知名企业的商业数据分析师?
  • 项目管理碎碎念系列之一:干系人管理
  • 《天龙八部3D》Unity技术方案揭秘
  • LevelDB 入门 —— 全面了解 LevelDB 的功能特性
  • 进程与线程(三)——进程/线程间通信
  • 容器镜像
  • 新年再起“裁员潮”,“钢铁侠”马斯克要一举裁掉SpaceX 600余名员工 ...
  • # Python csv、xlsx、json、二进制(MP3) 文件读写基本使用
  • #Spring-boot高级
  • (windows2012共享文件夹和防火墙设置
  • (第9篇)大数据的的超级应用——数据挖掘-推荐系统
  • (多级缓存)缓存同步
  • (二十三)Flask之高频面试点
  • (个人笔记质量不佳)SQL 左连接、右连接、内连接的区别
  • (力扣记录)1448. 统计二叉树中好节点的数目
  • (三) prometheus + grafana + alertmanager 配置Redis监控
  • (十二)devops持续集成开发——jenkins的全局工具配置之sonar qube环境安装及配置
  • (五)关系数据库标准语言SQL
  • (转)fock函数详解
  • (转)jdk与jre的区别
  • (转)Oracle 9i 数据库设计指引全集(1)
  • (转)如何上传第三方jar包至Maven私服让maven项目可以使用第三方jar包
  • (轉貼) UML中文FAQ (OO) (UML)
  • .net core webapi 部署iis_一键部署VS插件:让.NET开发者更幸福
  • .NET Framework与.NET Framework SDK有什么不同?
  • @Async注解的坑,小心
  • @Not - Empty-Null-Blank
  • [ 云计算 | AWS ] AI 编程助手新势力 Amazon CodeWhisperer:优势功能及实用技巧
  • [120_移动开发Android]008_android开发之Pull操作xml文件
  • [383] 赎金信 js
  • [Android Pro] AndroidX重构和映射