• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

Websocket 技术实践 实现在线聊天系统

武飞扬头像
Dragon Wu
帮助1

目录

一、认识Websocket

1、什么是websocket

2、websocket优势分析

3、websocket与springboot应用程序交互的图解

4、websocket与http协议对比

二、Springboot实现websocket技术的案例

1、引入依赖

2、注入IOC容器

3、websocket服务类

三、前端websocket连接

1、websocket连接的js

2、在线访问的html

四、代码设计与测试

1、失败重连

2、心跳检测

3、用户在线聊天测试 

五、案例源码 


一、认识Websocket

1、什么是websocket

WebSocket是一种在单个TCP连接上进行全双工通信的协议。

全双工:可以同时双向传输数据(B->A,A->B可以同时进行)。

推送技术是建立在客户端服务器的一种机制,就是由服务器主动将信息发往客户端的技术。就像广播电台播音。

2、websocket优势分析

过去传统的服务器与客户端的聊天通信一般有两种:

1、轮询请求:通过http的短连接的方式来进行轮询请求,不断轮询请求,对服务器压力很大。

2、socket长连接:socket长连接保证了客户端和服务器的长连接通信,但对服务器资源照成了极大的浪费。

于是诞生了websocket技术。

websocket协议的实现方式:

它是一种长连接,只能通过一次请求来初始化连接,然后所有的请求和响应都是通过这个TCP连接进行通讯,这意味着它是基于事件驱动,异步的消息机制。

学新通

3、websocket与springboot应用程序交互的图解

学新通

4、websocket与http协议对比

学新通

二、Springboot实现websocket技术的案例

1、引入依赖

  1.  
    <dependency>
  2.  
    <groupId>org.springframework.boot</groupId>
  3.  
    <artifactId>spring-boot-starter-websocket</artifactId>
  4.  
    </dependency>

2、注入IOC容器

  1.  
    @Configuration
  2.  
    public class WebSocketConfig {
  3.  
     
  4.  
    @Bean
  5.  
    public ServerEndpointExporter serverEndpointExporter(){
  6.  
    return new ServerEndpointExporter();
  7.  
    }
  8.  
    }

3、websocket服务类

  1.  
    package com.dragonwu.config.websocket;
  2.  
     
  3.  
    import com.alibaba.fastjson2.JSON;
  4.  
    import org.springframework.stereotype.Component;
  5.  
     
  6.  
    import javax.websocket.OnClose;
  7.  
    import javax.websocket.OnMessage;
  8.  
    import javax.websocket.OnOpen;
  9.  
    import javax.websocket.Session;
  10.  
    import javax.websocket.server.PathParam;
  11.  
    import javax.websocket.server.ServerEndpoint;
  12.  
    import java.io.IOException;
  13.  
    import java.util.HashMap;
  14.  
    import java.util.Map;
  15.  
     
  16.  
    /**
  17.  
    * @author Dragon Wu
  18.  
    * @since 2022-10-12 19:11
  19.  
    **/
  20.  
    //ws://localhost:port/websocket/A
  21.  
    @ServerEndpoint(value = "/websocket/{userId}")
  22.  
    @Component
  23.  
    public class WebSocketEndpoint {
  24.  
     
  25.  
    //与某个客户端的连接会话,需要通过它来给客户端发生数据
  26.  
    private Session session;
  27.  
     
  28.  
    /*
  29.  
    连接建立成功调用的方法
  30.  
    */
  31.  
    @OnOpen
  32.  
    public void onOpen(Session session, @PathParam("userId") String userId){
  33.  
    //把会话存入到连接池中
  34.  
    SessionPool.sessions.put(userId,session);
  35.  
    }
  36.  
     
  37.  
    /*
  38.  
    关闭连接
  39.  
    */
  40.  
    @OnClose
  41.  
    public void onClose(Session session) throws IOException {
  42.  
    SessionPool.close(session.getId());
  43.  
    session.close();
  44.  
    }
  45.  
     
  46.  
    /*
  47.  
    收到客户端消息后调用的方法
  48.  
    */
  49.  
    @OnMessage
  50.  
    public void onMessage(String message,Session session){
  51.  
    // 如果是心跳检测的消息,则返回pong作为心跳回应
  52.  
    if (message.equalsIgnoreCase("ping")) {
  53.  
    try {
  54.  
    Map<String, Object> params = new HashMap<String, Object>();
  55.  
    params.put("type", "pong");
  56.  
    session.getBasicRemote().sendText(JSON.toJSONString(params));
  57.  
    System.out.println("应答客户端的消息:" JSON.toJSONString(params));
  58.  
    } catch (Exception e1) {
  59.  
    e1.printStackTrace();
  60.  
    }
  61.  
    }
  62.  
    else
  63.  
    {
  64.  
    SessionPool.sendMessage(message);
  65.  
    }
  66.  
    }
  67.  
     
  68.  
    }
学新通

线程池操作类:

  1.  
    package com.dragonwu.config.websocket;
  2.  
     
  3.  
    import javax.websocket.Session;
  4.  
    import java.io.IOException;
  5.  
    import java.util.Map;
  6.  
    import java.util.concurrent.ConcurrentHashMap;
  7.  
     
  8.  
    /**
  9.  
    * @author Dragon Wu
  10.  
    * @since 2022-10-12 19:19
  11.  
    **/
  12.  
    public class SessionPool {
  13.  
     
  14.  
    //sessionId为系统随机生成的
  15.  
    public static Map<String, Session> sessions = new ConcurrentHashMap<>();
  16.  
     
  17.  
    public static void close(String sessionId) throws IOException {
  18.  
    for (String userId : SessionPool.sessions.keySet()) {
  19.  
    Session session = SessionPool.sessions.get(userId);
  20.  
    if (session.getId().equals(sessionId)) {
  21.  
    sessions.remove(userId);
  22.  
    break;
  23.  
    }
  24.  
    }
  25.  
    }
  26.  
     
  27.  
    public static void sendMessage(String sessionId, String message) {
  28.  
    sessions.get(sessionId).getAsyncRemote().sendText(message);
  29.  
    }
  30.  
     
  31.  
    public static void sendMessage(String message) {
  32.  
    for (String sessionId : SessionPool.sessions.keySet()) {
  33.  
    SessionPool.sessions.get(sessionId).getAsyncRemote().sendText(message);
  34.  
    }
  35.  
    }
  36.  
     
  37.  
    public static void sendMessage(Map<String, Object> params) {
  38.  
    //{"formUserId":userId,"toUserId":toUserId,"msg":msg}
  39.  
    String toUserId = params.get("toUserId").toString();
  40.  
    String msg = params.get("msg").toString();
  41.  
    String fromUserId = params.get("fromUserId").toString();
  42.  
    msg = "来自" fromUserId "的消息:" msg;
  43.  
    Session session = sessions.get(toUserId);
  44.  
    if (session != null) {
  45.  
    session.getAsyncRemote().sendText(msg);
  46.  
    }
  47.  
    }
  48.  
    }
学新通

学新通

三、前端websocket连接

1、websocket连接的js

  1.  
    var wsObj = null;
  2.  
    var wsUri = null;
  3.  
    var userId = -1;
  4.  
    var lockReconnect = false;
  5.  
    var wsCreateHandler = null;
  6.  
    function createWebSocket() {
  7.  
    var host = window.location.host; // 带有端口号
  8.  
    userId = GetQueryString("userId");
  9.  
    // wsUri = "ws://" host "/websocket?userId=" userId;
  10.  
    wsUri = "ws://" host "/websocket/" userId;
  11.  
     
  12.  
    try {
  13.  
    wsObj = new WebSocket(wsUri);
  14.  
    initWsEventHandle();
  15.  
    } catch (e) {
  16.  
    writeToScreen("执行关闭事件,开始重连");
  17.  
    reconnect();
  18.  
    }
  19.  
    }
  20.  
     
  21.  
    function initWsEventHandle() {
  22.  
    try {
  23.  
    wsObj.onopen = function (evt) {
  24.  
    onWsOpen(evt);
  25.  
    heartCheck.start();
  26.  
    };
  27.  
     
  28.  
    wsObj.onmessage = function (evt) {
  29.  
    onWsMessage(evt);
  30.  
    heartCheck.start();
  31.  
    };
  32.  
     
  33.  
    wsObj.onclose = function (evt) {
  34.  
    writeToScreen("执行关闭事件,开始重连");
  35.  
    onWsClose(evt);
  36.  
    reconnect();
  37.  
    };
  38.  
    wsObj.onerror = function (evt) {
  39.  
    writeToScreen("执行error事件,开始重连");
  40.  
    onWsError(evt);
  41.  
    reconnect();
  42.  
    };
  43.  
    } catch (e) {
  44.  
    writeToScreen("绑定事件没有成功");
  45.  
    reconnect();
  46.  
    }
  47.  
    }
  48.  
     
  49.  
    function onWsOpen(evt) {
  50.  
    writeToScreen("CONNECTED");
  51.  
    }
  52.  
     
  53.  
    function onWsClose(evt) {
  54.  
    writeToScreen("DISCONNECTED");
  55.  
    }
  56.  
     
  57.  
    function onWsError(evt) {
  58.  
    writeToScreen(evt.data);
  59.  
    }
  60.  
     
  61.  
    function writeToScreen(message) {
  62.  
    if(DEBUG_FLAG)
  63.  
    {
  64.  
    $("#debuggerInfo").val($("#debuggerInfo").val() "\n" message);
  65.  
    }
  66.  
    }
  67.  
     
  68.  
    function GetQueryString(name) {
  69.  
    var reg = new RegExp("(^|&)" name "=([^&]*)(&|$)", "i");
  70.  
    var r = window.location.search.substr(1).match(reg); //获取url中"?"符后的字符串并正则匹配
  71.  
    var context = "";
  72.  
    if (r != null)
  73.  
    context = r[2];
  74.  
    reg = null;
  75.  
    r = null;
  76.  
    return context == null || context == "" || context == "undefined" ? "" : context;
  77.  
    }
  78.  
     
  79.  
    //断后重连
  80.  
    function reconnect() {
  81.  
    if(lockReconnect) {
  82.  
    return;
  83.  
    };
  84.  
    writeToScreen("1秒后重连");
  85.  
    lockReconnect = true;
  86.  
    //没连接上会一直重连,设置延迟避免请求过多
  87.  
    wsCreateHandler && clearTimeout(wsCreateHandler);
  88.  
    wsCreateHandler = setTimeout(function () {
  89.  
    writeToScreen("重连..." wsUri);
  90.  
    createWebSocket();
  91.  
    lockReconnect = false;
  92.  
    writeToScreen("重连完成");
  93.  
    }, 1000);
  94.  
    }
  95.  
     
  96.  
    //心跳检测
  97.  
    var heartCheck = {
  98.  
    //15s之内如果没有收到后台的消息,则认为是连接断开了,需要再次连接
  99.  
    timeout: 15000,
  100.  
    timeoutObj: null,
  101.  
    serverTimeoutObj: null,
  102.  
    //重启
  103.  
    reset: function(){
  104.  
    clearTimeout(this.timeoutObj);
  105.  
    clearTimeout(this.serverTimeoutObj);
  106.  
    this.start();
  107.  
    },
  108.  
    //开启定时器
  109.  
    start: function(){
  110.  
    var self = this;
  111.  
    this.timeoutObj && clearTimeout(this.timeoutObj);
  112.  
    this.serverTimeoutObj && clearTimeout(this.serverTimeoutObj);
  113.  
    this.timeoutObj = setTimeout(
  114.  
    function(){
  115.  
    writeToScreen("发送ping到后台");
  116.  
    try
  117.  
    {
  118.  
    wsObj.send("ping");
  119.  
    }
  120.  
    catch(ee)
  121.  
    {
  122.  
    writeToScreen("发送ping异常");
  123.  
    }
  124.  
    //内嵌计时器
  125.  
    self.serverTimeoutObj = setTimeout(function(){
  126.  
    //如果onclose会执行reconnect,我们执行ws.close()就行了.如果直接执行reconnect 会触发onclose导致重连两次
  127.  
    writeToScreen("没有收到后台的数据,关闭连接");
  128.  
    //wsObj.close();
  129.  
    reconnect();
  130.  
    }, self.timeout);
  131.  
    },
  132.  
    this.timeout)
  133.  
     
  134.  
    },
  135.  
    }
学新通

2、在线访问的html

  1.  
    <!DOCTYPE html>
  2.  
    <html>
  3.  
     
  4.  
    <head>
  5.  
    <meta charset="UTF-8">
  6.  
    <title>Floor View</title>
  7.  
    <script src="/js/websocket.js"></script>
  8.  
    <script type="text/javascript" src="https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js"></script>
  9.  
    <script id="code">
  10.  
    var DEBUG_FLAG = true;
  11.  
    $(function()
  12.  
    {
  13.  
    //启动websocket
  14.  
    createWebSocket();
  15.  
     
  16.  
    });
  17.  
     
  18.  
    // 当有消息推送后触发下面事件
  19.  
    function onWsMessage(evt) {
  20.  
    var jsonStr = evt.data;
  21.  
    writeToScreen(jsonStr);
  22.  
    }
  23.  
     
  24.  
    function writeToScreen(message) {
  25.  
    if(DEBUG_FLAG)
  26.  
    {
  27.  
    $("#debuggerInfo").val($("#debuggerInfo").val() "\n" message);
  28.  
    }
  29.  
    }
  30.  
     
  31.  
    function sendMessageBySocket()
  32.  
    {
  33.  
    var toUserId = $("#userId").val();
  34.  
    var msg = $("#msg").val();
  35.  
    var data = {"fromUserId": userId,"toUserId": toUserId,"msg": msg};
  36.  
    wsObj.send(JSON.stringify(data));
  37.  
    }
  38.  
    </script>
  39.  
    </head>
  40.  
     
  41.  
    <body style="margin: 0px;padding: 0px;overflow: hidden; ">
  42.  
    <!-- 显示消息-->
  43.  
    <textarea id="debuggerInfo" style="width:100%;height:200px;"></textarea>
  44.  
    <!-- 发送消息-->
  45.  
    <div>用户:<input type="text" id="userId"></input></div>
  46.  
    <div>消息:<input type="text" id="msg"></input></div>
  47.  
    <div><input type="button" value="发送消息" onclick="sendMessageBySocket()"></input></div>
  48.  
    </body>
  49.  
    </html>
学新通

四、代码设计与测试

1、失败重连

在客户端和服务端连接失败是进行失败重连,代码如下:

学新通

2、心跳检测

学新通

服务器:

学新通

在客户端和服务器长时间未通信时,客户端会向服务器发一个ping,若服务器没问题则会给客户端返回一个pong的回复,以确保连接正常。

3、用户在线聊天测试 

学新通

可以看到聊天可以正常进行。

五、案例源码 

Websocket案例代码: Websocket案例代码

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhgbfghh
系列文章
更多 icon
同类精品
更多 icon
继续加载