精华内容
下载资源
问答
  • 网络协议、socket、webSocket

    万次阅读 多人点赞 2019-07-31 19:20:28
    一、网络协议 网络协议为计算机网络中进行数据交换而建立的规则、标准或约定的集合。 1、OSI七层协议 OSI是一个开放性的通信系统互连参考模型,他是一个定义得非常好的协议规范。OSI模型有7层结构,从上到下分别...

    一、网络协议

    网络协议为计算机网络中进行数据交换而建立的规则、标准或约定的集合。

    1、OSI七层协议

    OSI是一个开放性的通信系统互连参考模型,他是一个定义得非常好的协议规范。OSI模型有7层结构,从上到下分别是 7 应用层 6 表示层 5 会话层 4 传输层 3 网络层 2 数据链路层 1 物理层。

    下面的图表显示不同的协议在最初OSI模型中的位置:

    7 应用层 例如HTTP、SMTP、SNMP、FTP、Telnet、SIP、SSH、NFS、RTSP、XMPP、Whois、ENRP

    6 表示层 例如XDR、ASN.1、SMB、AFP、NCP

    5 会话层 例如ASAP、TLS、SSH、ISO 8327 / CCITT X.225、RPC、NetBIOS、ASP、Winsock、BSD sockets

    4 传输层 例如TCP、UDP、RTP、SCTP、SPX、ATP、IL

    3 网络层 例如IP、ICMP、IGMP、IPX、BGP、OSPF、RIP、IGRP、EIGRP、ARP、RARP、 X.25

    2 数据链路层 例如以太网、令牌环、HDLC、帧中继、ISDN、ATM、IEEE 802.11、FDDI、PPP

    1 物理层 例如线路、无线电、光纤、信鸽

    2、TCP/IP协议组

    TCP/IP(Transmission Control Protocol/Internet Protocol)即传输控制协议/网间协议,定义了主机如何连入因特网及数据如何在它们之间传输的标准,从字面意思来看TCP/IP是TCP和IP协议的合称,但实际上TCP/IP协议是指因特网整个TCP/IP协议族。不同于ISO模型的七个分层,TCP/IP协议参考模型把所有的TCP/IP系列协议归类到四个抽象层中。

    应用层:TFTP,HTTP,SNMP,FTP,SMTP,DNS,Telnet 等等

    传输层:TCP,UDP

    网络层:IP,ICMP,OSPF,EIGRP,IGMP

    数据链路层:SLIP,CSLIP,PPP,MTU

    3、OSI七层和TCP/IP四层的关系

    3.1 OSI引入了服务、接口、协议、分层的概念,TCP/IP借鉴了OSI的这些概念建立TCP/IP模型。

    3.2 OSI先有模型,后有协议,先有标准,后进行实践;而TCP/IP则相反,先有协议和应用再提出了模型,且是参照的OSI模型。

    3.3 OSI是一种理论下的模型,而TCP/IP已被广泛使用,成为网络互联事实上的标准。

    3.4 OSI的应用层、表示层、会话层可以算到TCP/IP的应用层里。

    4、总结

    每一抽象层建立在低一层提供的服务上,并且为高一层提供服务。

    通过上述介绍,就可以搞清楚例如HTTP协议和TCP协议的区别之类的问题了。TPC协议是一种传输层协议,主要解决数据如何在网络中传输,而HTTP协议是应用层协议,主要解决如何包装数据。关于TCP和HTTP协议的关系,网上有一段比较容易理解的介绍:“我们在传输数据时,可以直接使用(传输层)TCP协议,但是那样的话,如果没有应用层,便无法识别数据内容,如果想要使传输的数据有意义,则必须使用到应用层协议,应用层协议有很多,比如HTTP、FTP、TELNET等,也可以自己定义应用层协议。WEB使用HTTP协议作为应用层协议,以封装HTTP文本信息,然后使用TCP作为传输层协议将它发到网络上。”

    TCP(Transmission Control Protocol,传输控制协议)是基于连接的协议,也就是说,在正式收发数据前,必须和对方建立可靠的连接。一个TCP连接必须要经过三次“对话”才能建立起来,其中的过程非常复杂,我们这里只做简单、形象的介绍,你只要做到能够理解这个过程即可。我们来看看这三次对话的简单过程:主机A向主机B发出连接请求数据包:“我想给你发数据,可以吗?”,这是第一次对话;主机B向主机A发送同意连接和要求同步(同步就是两台主机一个在发送,一个在接收,协调工作)的数据包:“可以,你什么时候发?”,这是第二次对话;主机A再发出一个数据包确认主机B的要求同步:“我现在就发,你接着吧!”,这是第三次对话。三次“对话”的目的是使数据包的发送和接收同步,经过三次“对话”之后,主机A才向主机B正式发送数据。

    UDP(User Data Protocol,用户数据报协议)是与TCP相对应的协议。它是面向非连接的协议,它不与对方建立连接,而是直接就把数据包发送过去!UDP适用于一次只传送少量数据、对可靠性要求不高的应用环境。比如,我们经常使用“ping”命令来测试两台主机之间TCP/IP通信是否正常,其实“ping”命令的原理就是向对方主机发送UDP数据包,然后对方主机确认收到数据包,如果数据包是否到达的消息及时反馈回来,那么网络就是通的。例如,在默认状态下,一次“ping”操作发送4个数据包(如图2所示)。大家可以看到,发送的数据包数量是4包,收到的也是4包(因为对方主机收到后会发回一个确认收到的数据包)。这充分说明了UDP协议是面向非连接的协议,没有建立连接的过程。正因为UDP协议没有连接的过程,所以它的通信效果高;但也正因为如此,它的可靠性不如TCP协议高。QQ登陆采用TCP协议和HTTP协议,你和好友之间发送消息时主要采用UDP协议发消息,因此有时会出现收不到消息的情况。

     

    二、socket

    我们经常把socket翻译为套接字,socket是在应用层和传输层之间的一个抽象层,它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用以实现进程在网络中通信。socket是一组接口,在设计模式中,socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在socket接口后面,对用户来说,一组简单的接口就是全部,让socket去组织数据,以符合指定的协议。

     

    三、webSocket

    1、简介

    WebSocket协议是基于TCP的一种新的网络协议,和http协议一样属于应用层协议,它实现了浏览器与服务器全双工(full-duplex)通信,也就是允许服务器主动发送信息给客户端。我在实现二维码扫描登录时曾使用过,有了它就不需要通过轮询或者建立长连接的方式来使客户端实时获取扫码状态,因为当扫码后,服务器端可以主动发送消息通知客户端。

    2、webSocket和http的区别

    http链接分为短链接和长链接,短链接是每次请求都要重新建立TCP链接,TCP又要三次握手才能建立,然后发送自己的信息。即每一个request对应一个response。长链接是在一定的期限内保持TCP连接不断开。客户端与服务器通信,必须要由客户端发起然后服务器返回结果。客户端是主动的,服务器是被动的。

    简单的说,WebSocket协议之前,双工通信是通过多个http链接轮询来实现的,这导致了效率低下。WebSocket解决了这个问题,他实现了多路复用,他是全双工通信。在webSocket协议下客服端和浏览器可以同时发送信息。建立了WebSocket之后服务器不必在浏览器发送request请求之后才能发送信息到浏览器。这时的服务器已有主动权想什么时候发就可以什么时候发送信息到客户端,而且信息当中不必再带有head的部分信息了。与http的长链接通信来比,这种方式不仅能降低服务器的压力,而且信息当中也减少了部分多余的信息。

    3、webSocket和socket的区别

    就像Java和JavaScript,并没有什么太大的关系,但又不能说完全没关系。可以这么说:

    • 命名方面,Socket是一个深入人心的概念,WebSocket借用了这一概念;
    • 使用方面,完全两个东西。

    总之,可以把WebSocket想象成HTTP,HTTP和Socket什么关系,WebSocket和Socket就是什么关系。

    最后附上一张有意思的图片:

     

    展开全文
  • Springboot整合WebSocket常见问题解决 1. 启动失败报错 现象 java.lang.IllegalStateException: Failed to register @ServerEndpoint class: class com.xxx.WebSocketServer$$EnhancerBySpringCGLIB$$62689f33 ...

    Springboot整合WebSocket常见问题解决

    1. 启动失败报错

    • 现象
    java.lang.IllegalStateException: Failed to register @ServerEndpoint class: class com.xxx.WebSocketServer$$EnhancerBySpringCGLIB$$62689f33
     
    javax.websocket.DeploymentException: Cannot deploy POJO class [com.xxx.WebSocketServer$$EnhancerBySpringCGLIB$$62689f33] as it is not annotated with @ServerEndpoint
    
    • 问题原因
    	由于程序使用AOP切面,切面范围包括了@ServerEndpoint注解所在的类
    
    • 解决方案
    	可参考如下链接博主的分析,非常详细
    	参考链接:https://blog.csdn.net/qq_15807785/article/details/83547978
    
    2. 客户端无法和WebSocket服务端建立连接
    • 现象
    	通过ws://localhost:port/ws/name无法和服务端进行建立连接
    
    • 问题原因
    	可能存在两个原因:
    	1. springboot程序配置了server.servlet.context-path
    	2. 使用shiro等权限控制框架对请求进行了拦截
    
    • 解决方案
    	1. ws://localhost:port/ws/name地址中加入server.servlet.context-path配置的前缀
    	2. 在shiro权限框架中将websocke使用地址忽略权限校验
    
    展开全文
  • 小程序WebSocket 常见问题:(本文已解决的) 1.自动断开链接,重连但是只能存在两个WebSocket问题。 ---1兼容情况:1.1 正常聊天过一段时间 WebSocket 自动断开后重新链接,并且保存之前的聊天记录 ---1...

    微信小程序开发交流qq群   173683895

       承接微信小程序开发。扫码加微信。

    小程序 WebSocket 常见问题:本文已解决的

    1.自动断开链接,重连但是只能存在两个 WebSocket 的问题。

      ---1兼容情况:1.1 正常聊天过一段时间 WebSocket 自动断开后重新链接,并且保存之前的聊天记录

      ---1兼容情况:1.2 在用户黑屏但是没退出小程序过一段时间时 WebSocket 自动断开链接后重新链接,并且保存之前的聊天记录

      ---1兼容情况:1.3 在聊天室的页面,点击右上角返回按钮,页面会自动执行卸载,这个时候 WebSocket 是没有销毁的,再次进入时会同时存在两个WebSocket,第三次进入就会报错了(只能同时存在两个 WebSocket )。

    解决方案:因为需要兼容的情况比较多,解决方案可详见代码。具体思路就是,添加一个自动重连的开关。(必须WebSocket 销毁以后才能新建WebSocket )根据情况判断是否重连 WebSocket 。

    2. 录音成功,但是发送给后端接收不到语音文件。

    解决办法:在已录制完指定帧大小的事件回调函数中, 使用 wx.arrayBufferToBase64(res.frameBuffer),把得到的arrayBuffer 转为 Base64 再传给后端,同时设置 signType: 'BASE64'。

    3.录音传输给后端时,后端BASE64 解码失败的问题。

    解决方案:与后端确认 录音文件的采样率、编码码率、音频格式、帧大小是否一致。

    推荐设置:

        var recorder = wx.getRecorderManager();
        const options = {
          duration: 10000, //指定录音的时长,单位 ms
          sampleRate: 16000, //采样率
          numberOfChannels: 1, //录音通道数
          encodeBitRate: 24000, //编码码率
          format: 'mp3', //音频格式,有效值 aac/mp3
          frameSize: 12, //指定帧大小,单位 KB
        }
        recorder.start(options) //开始录音

    4.如何自动把页面聚焦在最新的聊天信息

    效果图:

    解决方案:在聊天信息的list 赋值成功后执行一次公共页面聚焦的方法:

      // 公共聚焦方法,方法比较笨,但是过度效果平滑流畅
      bottom: function() {
        var that = this;
        this.setData({
          scrollTop: 100000
        })
      },
    
    //调用示例:
          this.setData({
            allContentList: that.data.allContentList,
          })
          this.bottom();

    5.用户输入空格并发送,这个时候聊天冒泡框样式肯定会变形,因为没有空格行高。

    解决方案: css 设置冒泡框最低高度  ----  min-height: 80rpx;(数值根据需求自定义)

    6.聊天冒泡框的小三角形怎么实现?三角形还想要给它边框,又如何实现?

    效果图:

    实现步骤:设置文字冒泡框的样式-在文字冒泡框内新建一个盒子设置相对定位,里面放一个em和一个span标签,设置绝对定位,利用边框设置透明色,示例代码:

    <view class='new_txt_ai'>
      <view class='arrow'>
         <em></em>
         <span></span>
      </view>
      <view class='ai_content'>
        121(一百二十一)是 120与122之间的一个自然数。它也是奇数、合数、平方数
      </view>
    </view>
    .new_txt_ai {
      width: 460rpx;
      border-radius: 7rpx;
      left: 20rpx;
      background-color: #fff;
      position: relative;
      border: 1px solid #d0d0d0;
      float: left;
    }
    
    .new_txt_ai .arrow {
      position: relative;
      width: 40rpx;
      left: -30rpx;
    }
    
    .new_txt_ai .arrow em {
      position: absolute;
      border-style: solid;
      border-width: 15rpx;
      top: 20rpx;
      border-color: transparent #d0d0d0 transparent transparent;
    }
    
    .new_txt_ai .arrow span {
      position: absolute;
      top: 20rpx;
      border-style: solid;
      border-width: 15rpx;
      border-color: transparent #fff transparent transparent;
      left: 2rpx;
    }
    
    .ai_content {
      word-break: break-all;
      padding: 17rpx 30rpx 17rpx 30rpx;
    }

    还有很多常见问题就不多啰嗦了,直接上代码吧。因业务原因,发送图片和录音功能的代码暂时注释,注释打开可用。

    聊天室实现效果图:

     

    全部相关代码,代码逻辑比较多但是思路清晰,可塑性较强。提供借鉴参考。

    js源码

    // pages/index/to_news/to_news.js  
    var app = getApp();
    var util = require("../../utils/util.js");
    var socketOpen = false;
    var uuid = '',
      time_ = "1";
    var recorder = wx.getRecorderManager();
    const innerAudioContext = wx.createInnerAudioContext() //获取播放对象
    var frameBuffer_Data, session, SocketTask, string_base64, open_num = 0, submitTo_string,
      onUnload_num = 0,
      autoRestart, onHide_s = false;
    Page({
      data: {
        listCustmerServiceBanner: [],
        indicatorDots: false,
        autoplay: false,
        interval: 5000,
        duration: 1000,
        user_input_text: '', //用户输入文字
        inputValue: '',
        time: '',
        returnValue: '',
        if_send: false,
        add: true,
        cross: false,
        // is_my: true, text: '12432'
        allContentList: [{}, {
          is_ai: []
        }],
        num: 0
      },
      // 页面加载
      onLoad: function(e) {
        autoRestart = true; //是否重启
        console.log('onLoad')
        if (e && e.ofOperatorType) {
          this.setData({
            ofOperatorType: e.ofOperatorType
          })
        } else {
          this.setData({
            ofOperatorType: 2
          })
        }
        // if (onUnload_num < 1) {
        this.webSocket_open()
        // }
      },
      onShow: function(e) {
        onHide_s = false
      },
    
      onHide: function() {
        autoRestart = false;
        onHide_s = true
        console.log('onHide')
      },
      onUnload: function() {
        onUnload_num++;
        autoRestart = false;
        console.log('onUnload')
        this.close();
      },
      // 页面加载完成
      onReady: function() {
        var that = this;
        this.on_recorder();
        this.bottom()
      },
      // 创建websocket
      webSocket_open: function () {
        var that = this;
        console.log('开始创建')
        // 创建Socket
        SocketTask = wx.connectSocket({
          url: app.webS_url,
          header: {
            'content-type': 'application/json'
          },
          method: 'post',
          success: function(res) {
            console.log('WebSocket连接创建', res)
          },
          fail: function(err) {
            wx.showToast({
              title: '网络异常!',
            })
            console.log(err)
          },
        })
        that.initSocket();
      },
    
      // 提交文字
      submitTo: function(e) {
        submitTo_string =false
        console.log('提交文字')
        console.log("SocketTask", SocketTask)
        let that = this;
        if (that.data.inputValue == "") {
          return;
        }
        var data = {
          cmd: 1,
          type: 1,
          signType: 'BASE64',
          session: session,
          body: that.data.inputValue,
        }
        console.log('提交文字data:', socketOpen, data)
        if (socketOpen) {
          // 如果打开了socket就发送数据给服务器
          sendSocketMessage(data)
          if (session != undefined && session != null) {
            this.data.allContentList.push({
              is_my: true,
              text: this.data.inputValue
            });
            this.setData({
              allContentList: this.data.allContentList,
              if_send: false,
              inputValue: ''
            })
    
          }
          that.bottom()
        } else {
          submitTo_string=true;
          this.webSocket_open()
        }
      },
    
      // socket监听事件
      initSocket: function () {
        var that = this;
        console.log("aaa", SocketTask)
        SocketTask.onOpen(res => {
          socketOpen = true;
          open_num++
          if (session == undefined || session == null) {
            // repositoryType = 1 联通 2 移动 3 电信
            // ofType	int		进入客服小程序类型 1, 小程序跳转 2,搜索
            //  ofOperatorType	int	否	运营商类型1,移动 2,联通 3,电信
            //  wy_appid	String	否	小程序appid
            if (app.appid) {
              var data = {
                cmd: 2,
                ofType: 1,
                wy_appid: app.appid
              }
            } else {
              var data = {
                cmd: 2,
                ofType: 2,
                ofOperatorType: that.data.ofOperatorType
                // ofOperatorType: 1
              }
            }
            sendSocketMessage(data)
          }
          console.log('监听 WebSocket 连接打开事件。', res)
        })
        SocketTask.onClose(onClose => {
          console.log('监听 WebSocket 连接关闭事件。', onClose)
          session = null;
          SocketTask = false;
          socketOpen = false;
          // if (!autoRestart && onHide_s) {
          //   this.webSocket_open()
          // }
          // if (autoRestart) {
          //   this.webSocket_open()
          // }
        })
        SocketTask.onError(onError => {
          console.log('监听 WebSocket 错误。错误信息', onError)
          session = null;
        })
        SocketTask.onMessage(onMessage => {
          var onMessage_data = JSON.parse(onMessage.data);
          console.log("onMessage:", onMessage_data)
          // if (onMessage_data == 'session为空') {
          //   if (submitTo_string) {
          //     console.log('submitTo_string2222222222')
          //     that.submitTo()
          //   }
          //   return;
          // }
          if (onMessage_data.minipTitle) {
            wx.setTopBarText({
              text: onMessage_data.minipTitle,
            })
          }
          let is_ai_arr = onMessage_data.body;
          // 登录。默认发送一条消息给用户展示,不展示已解决未解决
          if (onMessage_data.cmd == 3) {
            that.session_pro = new Promise(function (resolve) {
              session = onMessage_data.session;
              if (submitTo_string) {
                console.log('submitTo_string11111111')
                that.submitTo()
              }
              resolve(session)
            })
            var messageTime = util.formatTime(onMessage_data.messageTime);
            // if (open_num < 2){
    
            if (is_ai_arr.length == 1) {
              that.data.allContentList.push({
                is_ai: is_ai_arr,
                solve_show: false,
                show_answer: true,
                messageTime: messageTime
              });
            } else {
                console.log('is_ai_arr:', is_ai_arr)
                that.data.allContentList.push({
                  is_ai: is_ai_arr,
                  show_answer: false,
                  solve_show: false,
                  messageTime: messageTime
                });
              }
            // }
            this.setData({
              listCustmerServiceBanner: onMessage_data.listCustmerServiceBanner,
              staffServicePhone: onMessage_data.staffServicePhone,
              allContentList: that.data.allContentList
            })
          } else {
            // 正常接收消息
            uuid = onMessage_data.messageRecordUuid;
            var messageTime;
            time_ = onMessage_data.messageTime;
            if (time_ + 1000 * 60 * 10 > onMessage_data.messageTime) {
              messageTime = 0;
            } else {
              messageTime = util.formatTime(onMessage_data.messageTime);
            }
            let arr_list = that.data.allContentList
            if (is_ai_arr.length == 1) {
              arr_list.push({
                show_answer: true,
                is_ai: is_ai_arr,
                messageTime: messageTime,
                solve_show: true,
                no_problem: false,
                yse_problem: false
              });
            } else {
              arr_list.push({
                show_answer: false,
                is_ai: is_ai_arr,
                messageTime: messageTime,
                solve_show: true,
                no_problem: false,
                yse_problem: false
              });
            }
            that.setData({
              allContentList: arr_list
            })
          }
          that.bottom();
        })
      },
      // 点击轮播图
      swiper_item_click: function (e) {
        var id = e.target.id
        console.log(id);
        var item_banners = this.data.listCustmerServiceBanner[id];
        var page = item_banners.page;
        // 类型1、自己小程序、2、其它小程序 3、H5
        switch (item_banners.type) {
          case 1:
            wx.navigateTo({
              url: page,
            })
            break;
          case 2:
            wx.navigateToMiniProgram({
              appId: item_banners.appid,
              path: page,
              extraData: {},
              envVersion: 'release',
              success(res) {
                // 打开成功
              }
            })
            break;
          case 3:
            wx.navigateTo({
              url: web + '?url=' + page,
            })
            break;
        }
      },
      // 关闭
      close: function (e) {
        if (SocketTask) {
          SocketTask.close(function (close) {
            console.log('关闭 WebSocket 连接。', close)
          })
        }
      },
      // 解决问题
      is_problem: function(e) {
        console.log('e.target.id', e.currentTarget.dataset.id)
        console.log('item', e.currentTarget.dataset.item)
        var id = e.currentTarget.dataset.id;
        var item = e.currentTarget.dataset.item;
        // id=1 已解决  0 未解决
        var yse_problem = this.data.allContentList[item].yse_problem;
        var no_problem = this.data.allContentList[item].no_problem;
        if (yse_problem || no_problem) {
          console.log(12)
          return
        } else {
          if (id == 1) {
            this.setData({
              ['allContentList[' + item + '].yse_problem']: true
            })
          } else if (id == 0) {
            this.setData({
              ['allContentList[' + item + '].no_problem']: true
            })
          }
          console.log(this.data.allContentList[item].yse_problem, this.data.allContentList[item].no_problem)
          this.bottom();
        }
        var url = app.httpUrl + '/v1/userFeedbackResult.do'
        var data = {
          'session': app.http_session,
          'type': id,
          'uuid': uuid
        }
        console.log('userFeedbackResult提交的数据:', data)
        util.request(url, 'POST', data, '', function(res) {
          console.log('userFeedbackResult返回的数据:', res.data)
    
        }, function(err) {
          console.log(err)
        })
      },
      // 跳转小程
      minip: function(e) {
        console.log(e)
        wx.navigateToMiniProgram({
          appId: e.target.dataset.appid,
          path: e.target.dataset.path,
          extraData: {},
          envVersion: 'develop',
          success(res) {
            // 打开成功
          }
        })
      },
      // 跳转WEB
      link: function(e) {
        console.log(e.target.id)
        wx.navigateTo({
          url: '../web/web?link=' + e.target.id,
        })
      },
      // 点击加号
      add_icon_click: function(e) {
        console.log(e.target.id)
        // e.target.id == 1 点击加号   ==2  点击 X
        if (e.target.id == 2) {
          this.setData({
            add: true,
            cross: false,
            input_bottom: 0
          })
        } else if (e.target.id == 1) {
          this.setData({
            add: false,
            cross: true,
            input_bottom: 240
          })
        }
      },
      // 自动添加问题答案
      add_question: function(e) {
        var that = this;
        let answer = e.currentTarget.dataset.answer;
        let messageTime = e.currentTarget.dataset.messagetime;
        let question = e.currentTarget.dataset.question;
        console.log('question:', question, 'answer:', answer, 'messageTime', messageTime);
        this.data.allContentList.push({
          is_my: true,
          text: question
        });
        this.setData({
          allContentList: this.data.allContentList,
          if_send: false,
          inputValue: ''
        })
        that.bottom();
        setTimeout(function() {
          that.data.allContentList.push({
            is_ai: [{
              answer: answer,
              type: 1
            }],
            solve_show: true,
            show_answer: true,
            messageTime: false,
            text: question
          });
          that.setData({
            allContentList: that.data.allContentList,
          })
          that.bottom();
        }, 1000)
      },
      // 拨打电话
      phone_click: function() {
        var that = this;
        wx.showModal({
          title: '',
          content: '是否拨打' + that.data.staffServicePhone + '人工客服电话',
          success: function(res) {
            if (res.confirm) {
              wx.makePhoneCall({
                phoneNumber: that.data.staffServicePhone //仅为示例,并非真实的电话号码  
              })
            } else if (res.cancel) {
              console.log('用户点击取消')
            }
          }
        })
      },
      // 输入框
      bindKeyInput: function(e) {
        console.log(e.detail.value)
        if (e.detail.value == "") {
          this.setData({
            if_send: false,
            inputValue: e.detail.value
          })
        } else {
          this.setData({
            if_send: true,
            inputValue: e.detail.value
          })
        }
      },
      // 获取到焦点
      focus: function(e) {
        var that = this;
        console.log(e.detail.height)
        this.setData({
          focus: true,
          add: true,
          cross: false,
          input_bottom: e.detail.height
        })
      },
      // 失去焦点
      no_focus: function(e) {
        if (this.data.cross) {
          this.setData({
            focus: false,
            input_bottom: 240,
          })
        } else {
          this.setData({
            focus: false,
            input_bottom: 0
          })
        }
      },
      // 获取hei的id节点然后屏幕焦点调转到这个节点  
      bottom: function() {
        var that = this;
        this.setData({
          scrollTop: 100000
        })
      },
      hide_bg: function() {
        this.setData({
          block: false
        })
      },
      // 点击录音事件
      my_audio_click: function(e) {
        console.log('my_audio_click执行了', e)
        var index = e.currentTarget.dataset.id;
        console.log('url地址', this.data.allContentList[index].audio);
        innerAudioContext.src = this.data.allContentList[index].audio
        innerAudioContext.seek(0);
        innerAudioContext.play();
      },
      // 手指点击录音
      voice_ing_start: function() {
        var that = this;
        this.setData({
          voice_ing_start_date: new Date().getTime(), //记录开始点击的时间
        })
        const options = {
          duration: 10000, //指定录音的时长,单位 ms
          sampleRate: 16000, //采样率
          numberOfChannels: 1, //录音通道数
          encodeBitRate: 24000, //编码码率
          format: 'mp3', //音频格式,有效值 aac/mp3
          frameSize: 12, //指定帧大小,单位 KB
        }
        recorder.start(options) //开始录音
    
        this.animation = wx.createAnimation({
          duration: 1200,
        }) //播放按钮动画
        that.animation.scale(0.8, 0.8); //还原
        that.setData({
    
          spreakingAnimation: that.animation.export()
        })
      },
      // 录音监听事件
      on_recorder: function() {
        var that = this;
        recorder.onStart((res) => {
          console.log('开始录音');
        })
        recorder.onStop((res) => {
          console.log('停止录音,临时路径', res.tempFilePath);
          // _tempFilePath = res.tempFilePath;
          var x = new Date().getTime() - this.data.voice_ing_start_date
          if (x > 1000) {
            that.data.allContentList.push({
              is_my: true,
              audio: res.tempFilePath,
              length: x / 1000 * 30
            });
            that.setData({
              allContentList: that.data.allContentList
            })
          }
        })
        recorder.onFrameRecorded((res) => {
          var x = new Date().getTime() - this.data.voice_ing_start_date
          if (x > 1000) {
            console.log('onFrameRecorded  res.frameBuffer', res.frameBuffer);
            string_base64 = wx.arrayBufferToBase64(res.frameBuffer)
    
            // console.log('string_base64--', wx.arrayBufferToBase64(string_base64))
            if (res.isLastFrame) {
              that.session_pro.then(function(session) {
                var data = {
                  audioType: 3,
                  cmd: 1,
                  type: 2,
                  signType: 'BASE64',
                  session: session,
                  body: string_base64,
                }
                console.log('that.data.allContentList', that.data.allContentList)
                sendSocketMessage(data)
              })
              // 进行下一步操作
            } else {
              that.session_pro.then(function(session) {
                var data = {
                  cmd: 1,
                  audioType: 2,
                  type: 2,
                  signType: 'BASE64',
                  session: session,
                  body: string_base64
                }
                console.log('录音上传的data:', data)
                sendSocketMessage(data)
              })
            }
          }
        })
      },
      // 手指松开录音
      voice_ing_end: function() {
        var that = this;
        that.setData({
          voice_icon_click: false,
          animationData: {}
        })
        this.animation = "";
        var x = new Date().getTime() - this.data.voice_ing_start_date
        if (x < 1000) {
          console.log('录音停止,说话小于1秒!')
          wx.showModal({
            title: '提示',
            content: '说话要大于1秒!',
          })
          recorder.stop();
        } else {
          // 录音停止,开始上传
          recorder.stop();
        }
      },
      // 点击语音图片
      voice_icon_click: function() {
        this.setData({
          voice_icon_click: !this.data.voice_icon_click
        })
      },
    })
    //通过 WebSocket 连接发送数据,需要先 wx.connectSocket,并在 wx.onSocketOpen 回调之后才能发送。
    function sendSocketMessage(msg) {
      var that = this;
      if (app.http_session != "") {
        msg.http_session = app.http_session
        console.log('通过 WebSocket 连接发送数据', JSON.stringify(msg))
        SocketTask.send({
          data: JSON.stringify(msg)
        }, function(res) {
          console.log('已发送', res)
        })
      } else {
        app.promise.then(function(http_session) {
          msg.http_session = http_session;
          console.log('通过 WebSocket 连接发送数据', JSON.stringify(msg));
          SocketTask.send({
            data: JSON.stringify(msg)
          }, function(res) {
            console.log('已发送', res);
          })
    
        })
    
      }
    
    }

    wxml源码

    <!-- <button bindtap='close'>关闭</button>
    <button bindtap='open'>打开</button> -->
    <!-- <swiper indicator-dots="{{indicatorDots}}" autoplay="{{autoplay}}" interval="{{interval}}" duration="{{duration}}">
      <block wx:for="{{listCustmerServiceBanner}}" wx:key=''>
        <swiper-item>
          <image src="{{item.picUrl}}" bindtap='swiper_item_click' id='{{index}}' class="slide-image" />
        </swiper-item>
      </block>
    </swiper> -->
    <view class='page_bg' wx:if='{{block}}' bindtap='hide_bg' />
    <view class='btn_bg' wx:if='{{block}}'>
      <view wx:for="{{link_list}}" wx:key='index'>
        <button class="sp_tit" id='{{index}}' bindtap='list_item'>查看详情 {{item}} </button>
      </view>
    </view>
    <scroll-view class="history" scroll-y="true" scroll-with-animation scroll-top="{{scrollTop}}">
    
      <block wx:key="{{index}}" wx:for="{{allContentList}}">
        <block wx:if="{{item.is_my}}">
          <view class='my_right new_txt'>
            <view class='time' wx:if='{{item.messageTime&&item.messageTime!=0}}'>
              {{item.messageTime}}
            </view>
            <view class='p_r page_r' style='margin-right: 25rpx;' wx:if='{{item.text}}'>
              <view class='new_txt'>
                <view class='new_txt_my'>
                  <view class='arrow'>
                    <em></em>
                    <span></span>
                  </view>
                  <text decode="true">{{item.text}}</text>
                </view>
              </view>
              <open-data class='new_img' type="userAvatarUrl"></open-data>
            </view>
            <view class='p_r page_r' style='margin-right: 25rpx;' wx:if='{{item.audio}}' bindtap='my_audio_click' data-id='{{index}}'>
              <view class='new_txt_my_2' style=' width:{{item.length}}px'>
                <image class='my_audio' src='/images/yuyin_icon.png'></image>
              </view>
              <span class='_span'></span>
              <open-data class='new_img' type="userAvatarUrl"></open-data>
            </view>
          </view>
        </block>
        <!-- <view class='you_left' id='id_{{allContentList.length}}'> -->
        <block wx:if="{{item.is_ai&&item.is_ai!=''}}">
          <view class='you_left' style='width:100%;' id='id_{{allContentList.length}}' wx:key="{{index}}">
            <view class='time' wx:if='{{item.messageTime}}'>
              {{item.messageTime}}
            </view>
            <view class='p_r' style='margin-left: 20rpx;'>
              <image class='new_img' src='/images/top_img.png'></image>
              <view class='new_txt'>
                <view class='new_txt_ai'>
                  <view class='arrow'>
                    <em></em>
                    <span></span>
                  </view>
                  <!-- {{item.text}} -->
                  <view class='ai_content'>
                    <block wx:for='{{item.is_ai}}' wx:for-item='itt' wx:for-index='indexi'  wx:key=''>
                      <text wx:if='{{itt.type=="1"&&item.show_answer}}' decode="true" >{{itt.answer}}</text>
                      <block wx:if='{{itt.type=="1"&&!item.show_answer}}'>
                      <text decode="true" wx:if='{{indexi==0}}'>{{itt.answer}}</text>
                      <view  decode="true" style='color:#0000EE' bindtap='add_question' data-messagetime='{{itt.messageTime}}' data-question='{{itt.question}}' data-answer='{{itt.answer}}'>· {{itt.question}}?</view>
                      </block>
                      <text wx:if='{{item.type=="2"}}' decode="true" style='color:#0000EE' bindtap='link' id='{{item.link}}'>{{item.text}}</text>
                      <image wx:if='{{item.type=="3"}}' style='width:{{item.w}}rpx;height:{{item.h}}rpx;' src='{{item.src}}'></image>
                      <text wx:if='{{item.type=="10"}}' decode="true" data-path='{{item.path}}' data-appid='{{item.appId}}' bindtap='minip'>{{item.text}}</text>
                    </block>
                  </view>
                  <!-- <view class='is_ai_btn' wx:if='{{item.solve_show&&item.is_ai[0].answer!="我不明白"}}'>
                    <view bindtap='is_problem' data-id='1' data-item='{{index}}' style=' {{item.yse_problem?"color: red;":""}}'>
    
                      <image src='{{item.yse_problem?"/images/in_zan.png":"/images/zan.png"}}' /> 已解决
                    </view>
                    <view bindtap='is_problem' data-id='0' data-item='{{index}}' class='two' style=' {{item.no_problem?"color: #00B1FF;":""}}'>
                      <image src='{{item.no_problem?"/images/in_zan_no.png":"/images/zan_no.png"}}' /> 未解决
                    </view>
                  </view> -->
                  <view class='yes_problem_log' wx:if="{{item.yse_problem&&item.solve_show}}" style=''>感谢您的反馈,我们会再接再厉!</view>
                  <view class='yes_problem_log' style='color:#32CF3C' wx:if="{{item.no_problem&&item.solve_show}}" bindtap='phone_click'>拨打人工客服</view>
    
                </view>
              </view>
            </view>
          </view>
        </block>
      </block>
    </scroll-view>
    <!-- 遮罩 -->
    <view class='zezhao' wx:if='{{cross}}' bindtap='add_icon_click' id='2'></view>
    <!-- 输入框 -->
    <view class='{{cross?"in_voice_icon":""}}'>
      <view class="sendmessage" wx:if='{{!cross}}' style='bottom:{{input_bottom}}px'>
        <input type="text" style='{{focus?"border-bottom: 1px solid #88DD4B;":""}}' adjust-position='{{false}}' cursor-spacing='5' bindinput="bindKeyInput" value='{{inputValue}}' focus='{{focus}}' bindblur='no_focus' bindfocus="focus" confirm-type="done" placeholder="请输入您要咨询的问题"/>
        <button wx:if='{{if_send&&inputValue!=""}}' bindtap="submitTo" class='user_input_text'>发送</button>
        <image class='add_icon' bindtap='add_icon_click' id='1' wx:if='{{add&&!if_send&&inputValue==""}}' src='/images/jia_img.png'></image>
        <image class='add_icon' bindtap='add_icon_click' id='2' wx:if='{{cross}}' src='/images/audio/cross37.png'></image>
    
      </view>
      <view wx:if='{{cross}}' class='item' bindtap='phone_click'>
        <image class='img' src='/images/yuyin_icon.png'></image>
        <view class='text'>人工客服</view>
      </view>
    </view>
    
    <!-- <view class='zezhao' wx:if='{{add_icon_click}}' bindtap='add_icon_click'></view> -->
    <!-- <view class='in_voice_icon'>
      <view class="sendmessage_2">
        <input type="text" bindinput="bindKeyInput" adjust-position='{{false}}' value='{{inputValue}}' focus='{{focus}}' bindfocus="focus" confirm-type="done" placeholder="" />
        <image class='add_icon' bindtap='add_icon_click' src='/images/audio/cross37.png'></image>
      </view>
      <view class='item' bindtap='phone_click'>
        <image class='img' src='/images/yuyin_icon.png'></image>
        <view class='text'>人工客服</view>
      </view>
    </view> -->

    wxss源码

    page {
      background-color: #f2f2f2;
      height: 100%;
      padding: 0 auto;
      margin: 0 auto;
    }
    
    swiper {
      height: 180rpx;
    }
    
    swiper swiper-item .slide-image {
      width: 100%;
      height: 180rpx;
    }
    
    .jia_img {
      height: 80rpx;
      width: 90rpx;
    }
    
    .time {
      text-align: center;
      padding: 5rpx 20rpx 5rpx 20rpx;
      border-radius: 10rpx;
      display: block;
      height: 38rpx;
      line-height: 38rpx;
      position: relative;
      margin: 0 auto;
      margin-bottom: 20rpx;
      width: 90rpx;
      color: white;
      font-size: 26rpx;
      background-color: #dedede;
    }
    
    .tab {
      bottom: 120rpx;
    }
    
    .tab_1 {
      position: fixed;
      bottom: 50rpx;
      width: 200rpx;
      font-size: 26rpx;
      left: 50%;
      margin-left: -45rpx;
      height: 100rpx;
    }
    
    .tab_2 {
      right: 30rpx;
      position: fixed;
    }
    
    /* 聊天 */
    
    .my_right {
      float: right;
      margin-top: 30rpx;
      position: relative;
    }
    
    .my_audio {
      height: 60rpx;
      width: 60rpx;
      z-index: 2;
      position: relative;
      top: 10rpx;
      left: 20rpx;
    }
    
    .you_left {
      margin-top: 30rpx;
      float: left;
      position: relative;
      padding-left: 5rpx;
    }
    
    .new_img {
      width: 85rpx;
      height: 85rpx;
      overflow: hidden;
    }
    
    .page_r {
      float: right;
    }
    
    .new_txt {
      min-width: 380rpx;
      width: 460rpx;
      word-break: break-all;
    }
    
    .new_txt_my {
      border-radius: 7rpx;
      background: #9fe75a;
      position: relative;
      right: 30rpx;
      min-height: 50rpx;
      padding: 17rpx 30rpx 17rpx 30rpx;
      float: right;
      border: 1px solid #d0d0d0;
    }
    
    .new_txt_my .arrow {
      position: absolute;
      z-index: 2;
      width: 40rpx;
      right: -38rpx;
    }
    
    .new_txt_my .arrow em {
      position: absolute;
      border-style: solid;
      border-width: 15rpx;
      border-color: transparent transparent transparent #d0d0d0;
      top: 1rpx;
    }
    
    .new_txt_my .arrow span {
      position: absolute;
      top: 5rpx;
      border-style: solid;
      border-width: 15rpx;
      border-color: transparent transparent transparent #9fe75a;
    }
    
    .new_txt_my_2 {
      word-break: break-all;
      border-radius: 7rpx;
      background: #9fe75a;
      min-width: 330rpx;
      max-width: 530rpx;
      padding: 17rpx 30rpx 17rpx 30rpx;
      float: right;
    }
    
    .new_txt_ai {
      width: 460rpx;
      border-radius: 7rpx;
      left: 20rpx;
      background-color: #fff;
      position: relative;
      border: 1px solid #d0d0d0;
      float: left;
    }
    
    .new_txt_ai .arrow {
      position: relative;
      width: 40rpx;
      left: -30rpx;
    }
    
    .new_txt_ai .arrow em {
      position: absolute;
      border-style: solid;
      border-width: 15rpx;
      top: 20rpx;
      border-color: transparent #d0d0d0 transparent transparent;
    }
    
    .new_txt_ai .arrow span {
      position: absolute;
      top: 20rpx;
      border-style: solid;
      border-width: 15rpx;
      border-color: transparent #fff transparent transparent;
      left: 2rpx;
    }
    
    .ai_content {
      word-break: break-all;
      padding: 17rpx 30rpx 17rpx 30rpx;
    }
    
    .sanjiao {
      top: 25rpx;
      position: relative;
      width: 0px;
      height: 0px;
      border-width: 15rpx;
      border-style: solid;
    }
    
    .my {
      border-color: transparent transparent transparent #9fe75a;
    }
    
    .you {
      border-color: transparent #fff transparent transparent;
    }
    
    ._span {
      border-color: #fff transparent transparent;
      top: -17px;
    }
    
    .is_ai_btn {
      border-radius: 0 0 7px 7px;
      border-top: 1px solid #d0d0d0;
      background: white;
      position: relative;
      bottom: 0;
      left: 0;
      width: 100%;
      height: 80rpx;
      line-height: 80rpx;
      display: flex;
      flex-direction: row;
      text-align: center;
    }
    
    .is_ai_btn view {
      width: 50%;
    }
    
    .is_ai_btn image {
      width: 32rpx;
      position: relative;
      top: 4rpx;
      height: 32rpx;
    }
    
    .is_ai_btn .two {
      border-left: 1px solid #d0d0d0;
    }
    
    .yes_problem_log {
      border-top: 1px solid #d0d0d0;
      height: 80rpx;
      text-align: center;
      line-height: 80rpx;
    }
    
    .voice_icon {
      width: 60rpx;
      height: 60rpx;
      margin: 0 auto;
      padding: 10rpx 10rpx 10rpx 10rpx;
    }
    
    .add_icon {
      width: 70rpx;
      height: 70rpx;
      margin: 0 auto;
      padding: 20rpx 10rpx 10rpx 15rpx;
    }
    
    .voice_ing {
      width: 90%;
      height: 75rpx;
      line-height: 85rpx;
      text-align: center;
      border-radius: 15rpx;
      border: 1px solid #d0d0d0;
    }
    
    .zezhao {
      height: 100%;
      position: absolute;
      top: 0;
      left: 0;
      z-index: 2;
      width: 100%;
      background: rgba(0, 0, 0, 0.5);
    }
    
    .in_voice_icon {
      z-index: 3;
      left: 0;
      bottom: 0;
      width: 100%;
      position: absolute;
      height: 500rpx;
      background: #f8f8f8;
    }
    
    .in_voice_icon .item {
      position: relative;
      left: 50%;
      margin-left: -60rpx;
      margin-top: 180rpx;
      text-align: center;
      width: 120rpx;
    }
    
    .in_voice_icon .img {
      width: 120rpx;
      height: 120rpx;
      border-radius: 15rpx;
    }
    
    .in_voice_icon .text {
      font-size: 32rpx;
      margin-top: 20rpx;
      background: white;
      width: 200rpx;
      margin-left: -40rpx;
      border-radius: 15rpx;
      height: 80rpx;
      line-height: 80rpx;
    }
    
    .sendmessage {
      width: 100%;
      z-index: 2;
      display: flex;
      position: fixed;
      bottom: 0px;
      background-color: #f8f8f8;
      flex-direction: row;
      height: 100rpx;
    }
    
    .sendmessage input {
      width: 78%;
      height: 80rpx;
      line-height: 80rpx;
      font-size: 28rpx;
      margin-top: 10rpx;
      margin-left: 20rpx;
      border-bottom: 1px solid #d0d0d0;
      padding-left: 20rpx;
    }
    
    .sendmessage button {
      border: 1px solid white;
      width: 18%;
      height: 80rpx;
      background: #0c0;
      color: white;
      line-height: 80rpx;
      margin-top: 10rpx;
      font-size: 28rpx;
    }
    
    .hei {
      height: 20rpx;
    }
    
    .history {
      /* height: 73%; */
      height: 88%;
      display: flex;
      font-size: 14px;
      line-height: 50rpx;
      position: relative;
      top: 20rpx;
    }
    
    .icno_kf {
      position: fixed;
      bottom: 160rpx;
      margin: 0 auto;
      text-align: center;
      left: 50%;
      margin-left: -40rpx;
      width: 100rpx;
      height: 100rpx;
      border-radius: 50%;
    }
    

    引用的util文件源码:

    
    // 手机号码验证
    function isUnicoms(mobileNo) {
        //移动:134(0 - 8) 、135、136、137、138、139、147、150、151、152、157、158、159、178、182、183、184、187、188、198 
        //联通:130、131、132、145、155、156、175、176、185、186、166
        //电信:133、153、173、177、180、181、189、199 
      // 1,移动 2,联通 3,电信
      var move = /^((134)|(135)|(136)|(137)|(138)|(139)|(147)|(150)|(151)|(152)|(157)|(158)|(159)|(178)|(182)|(183)|(184)|(187)|(188)|(198))\d{8}$/g;
      var link = /^((130)|(131)|(132)|(155)|(156)|(145)|(185)|(186)|(176)|(175)|(170)|(171)|(166))\d{8}$/g;
      var telecom = /^((133)|(153)|(173)|(177)|(180)|(181)|(189)|(199))\d{8}$/g;
      if (move.test(mobileNo)) {
        return '1';
      } else if (link.test(mobileNo)) {
        return '2';
      } else if (telecom.test(mobileNo)) {
        return '3';
      } else {
        return '非三网号段';
      }
    }
    // 网络请求
    function request(url, method, data, message, _success, _fail) {
      wx.showNavigationBarLoading()
      if (message != "") {
        wx.showLoading({
          title: message
        })
      }
      wx.request({
        url: url,
        data: data,
        header: {
          'content-type': 'application/x-www-form-urlencoded'
        },
        method: method,
        success: function (res) {
          _success(res)
          wx.hideNavigationBarLoading()
          if (message != "") {
            wx.hideLoading()
          }
        },
        fail: function (err) {
          if (err) {
            _fail(err)
          }
          wx.hideNavigationBarLoading()
          if (message != "") {
            wx.hideLoading()
          }
        },
      })
    }
    
    //上传语音
    function up_audio(url, audioSrc,name, data, _succ, _fail) {
      const uploadTask = wx.uploadFile({
          url: url, //仅为示例,非真实的接口地址
          filePath: audioSrc,
          name: name,
          formData: data,
          header: {
            "content-type": "multipart/form-data"
          },
          success: function (res) {
            _succ(res)
          },fail:function(err){
            _fail(err)
          }
        })
        uploadTask.onProgressUpdate((res) => {
          console.log('audio上传进度', res.progress)
          console.log('audio已经上传的数据长度', res.totalBytesSent)
          console.log('audio预期需要上传的数据总长度', res.totalBytesExpectedToSend)
        })
    }
    
    function formatTime(unixtime) {
      var dateTime = new Date(parseInt(unixtime))
      var year = dateTime.getFullYear();
      var month = dateTime.getMonth() + 1;
      var day = dateTime.getDate();
      var hour = dateTime.getHours();
      var minute = dateTime.getMinutes();
      var second = dateTime.getSeconds();
      var now = new Date();
      var now_new = Date.parse(now.toDateString());  //typescript转换写法
      var milliseconds = now_new - dateTime;
      var timeSpanStr =  hour + ':' + minute;
      // var timeSpanStr = year + '-' + month + '-' + day + ' ' + hour + ':' + minute;
      return timeSpanStr;
    }
    module.exports = {
      request: request,
      isUnicoms: isUnicoms,
      formatTime:formatTime,
      up_audio: up_audio
    }

    引用的app.js

    //app.js
    var util = require('utils/util.js');
    App({
      onLaunch: function () {
        var that = this;
        that.http_session = '';
        return that.promise = new Promise(function (resolve) {
          // that.webS_url = 'ws://192.168.199.147:7041';//填你请求的地址
          // that.httpUrl = 'http://192.168.199.147:7051'//填你请求的测试
          wx.login({
            success: function (res) {
              var data = {
                code: res.code
              }
              if (res.code) {
                //发起网络请求
                var url = that.httpUrl + '/v1/user/login.do';
                util.request(url, 'POST', data, '', function (res) {
                  console.log(res);
                  that.http_session = res.data.body;
                  resolve(that.http_session);
                }, function (err) {
                  console.log(err);
                })
              } else {
                console.log('登录失败!' + res.errMsg)
              }
            }
          });
        })
      },
      // 提交formid
      form_id_bg: function (formId) {
        console.log('form_id_bg执行了')
        let url = this.httpUrl + '/v1/formid/saveFormid.do';
        this.promise.then(function (http_session) {
          let data = {
            session: http_session,
            // minipid: '10000',
            formId: formId
          }
          util.request(url, 'post', data, '', function (res) {
          })
        })
      },
      onShow: function (even) {
        var e;
        // if (even.referrerInfo.extraData && even.referrerInfo.extraData.foo) {
        //   e = even.referrerInfo.extraData.foo
        // }
        if (e && e.appid) {
          this.appid = e.appid;
        }
      }
    })

    群聊的链接:https://blog.csdn.net/qq_35713752/article/details/78688311

    展开全文
  • SpringBoot整合websocket踩的坑 最近做一个项目需要为前端大屏推送信息,使用的是SpringBoot,需在项目中整合websocket实时推送信息,且只用有连接的时候再推送。 第一步:在SpringBoot中整合进入websocket 先是pom....

    SpringBoot整合websocket踩的坑

    最近做一个项目需要为前端大屏推送信息,使用的是SpringBoot,需在项目中整合websocket实时推送信息,且只用有连接的时候再推送。

    第一步:在SpringBoot中整合进入websocket

    先是pom.xml添加依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-websocket</artifactId>
    </dependency>
    

    无需再配置文件中添加任何配置,可直接使用项目端口。

    第二步:websocket配置类

    添加websocket配置类 WebSocketConfig.java

    /**
     * @Component 
     * @since jdk1.8
     */
    @Component
    public class WebSocketConfig {
    
        @Bean
        public ServerEndpointExporter serverEndpointExporter(){
            return new ServerEndpointExporter();
        }
    }
    

    第三步:各节点监听发放类

    MonitorWebSocket.java

    /**
     * 
     * 此处在本地启动和Linux服务器部署启动会有冲突,最后详细说
     *
     */
    @Component
    @ServerEndpoint(value = "/websocket/monitor/{userId}")
    public class MonitorWebSocket  {
    
        private static final Logger logger = LoggerFactory.getLogger(MonitorWebSocket.class);
        /**静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。*/
        private static int onlineCount = 0;
        /**concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。*/
        private static ConcurrentHashMap<String, MonitorWebSocket> webSocketMap = new ConcurrentHashMap<>();
        /**与某个客户端的连接会话,需要通过它来给客户端发送数据*/
        private Session session;
        /**接收userId*/
        private String userId="";
    
        /**
         * 连接建立成功调用的方法*/
        @OnOpen
        public void onOpen(Session session, @PathParam("userId") String userId) {
            this.session = session;
            this.userId=userId;
            if(webSocketMap.containsKey(userId)){
                webSocketMap.remove(userId);
                webSocketMap.put(userId,this);
                //加入set中
            }else{
                webSocketMap.put(userId,this);
                //加入set中
                addOnlineCount();
                //在线数加1
            }
    
            logger.info("/websocket/monitor/用户连接:"+userId+",当前在线人数为:" + getOnlineCount());
    
            try {
                sendMessage(JSONObject.toJSONString(R.ok("连接成功")));
            } catch (IOException e) {
                logger.error("用户:{},网络异常!!!!!!:{}",userId,e.getMessage());
            }
        }
    
        /**
         * 连接关闭调用的方法
         */
        @OnClose
        public void onClose() {
            if(webSocketMap.containsKey(userId)){
                webSocketMap.remove(userId);
                //从set中删除
                subOnlineCount();
            }
            logger.info("用户退出:{},当前在线人数为:{}", userId, getOnlineCount());
        }
    
        /**
         * 收到客户端消息后调用的方法
         *
         * @param message 客户端发送过来的消息*/
        @OnMessage
        public void onMessage(String message, Session session) {
            System.out.println("用户消息:"+userId+",报文:"+message);
            //可以群发消息
            //消息保存到数据库、redis
            if(StringUtils.isNotBlank(message)){
                try {
                    //解析发送的报文
                    JSONObject jsonObject = JSONObject.parseObject(message);
                    //追加发送人(防止串改)
                    jsonObject.put("fromUserId",this.userId);
                    String toUserId=jsonObject.getString("toUserId");
                    //传送给对应toUserId用户的websocket
                    if(StringUtils.isNotBlank(toUserId)&&webSocketMap.containsKey(toUserId)){
                        webSocketMap.get(toUserId).sendMessage(jsonObject.toJSONString());
                    }else{
                        logger.info("请求的userId:"+toUserId+"不在该服务器上");
                        //否则不在这个服务器上,发送到mysql或者redis
                    }
                }catch (Exception e){
                    logger.error(e.getMessage());
                }
            }
        }
    
        /**
         * session会话错误
         * @param session
         * @param error
         */
        @OnError
        public void onError(Session session, Throwable error) {
            logger.error("用户错误:{},原因:{}", this.userId, error.getMessage());
        }
    
        /**
         * 实现服务器主动推送
         */
        public void sendMessage(String message) throws IOException {
            this.session.getBasicRemote().sendText(message);
        }
    
    
        /**
         * 发送自定义消息
         * */
        public static void sendInfo(String message,@PathParam("userId") String userId) throws IOException {
            logger.info("发送消息到:{},报文:{}", userId, message);
            if(StringUtils.isNotBlank(userId)&&webSocketMap.containsKey(userId)){
                webSocketMap.get(userId).sendMessage(message);
            }else{
                logger.error("用户{},不在线!", userId);
            }
        }
    
    
        /**
         * 给所有用户连接推送消息
         * @author cd
         * @date 2020/3/10
         * @param message
         * @return
         */
        public static void sendMessageAll(String message) throws IOException {
    
            for (MonitorWebSocket item : webSocketMap.values()) {
                item.session.getAsyncRemote().sendText(message);
            }
    
        }
    
        public static synchronized int getOnlineCount() {
            return onlineCount;
        }
    
        public static synchronized void addOnlineCount() {
            MonitorWebSocket.onlineCount++;
        }
    
        public static synchronized void subOnlineCount() {
            MonitorWebSocket.onlineCount--;
        }
    
    }
    

    第四步:前端页面写法

    // 建立websocket连接,进入页面后台推送数据
    var websocket = null;
    var host = document.location.host;
    // 获得一个随机数作为连接的唯一标识, 此处是封装的一个获取指定位数的随机数方法
    var username = RndNum(10); 
    //判断当前浏览器是否支持WebSocket
    if (websocket != null) {
        websocket.close();
        websocket = null;
    }
    if ('WebSocket' in window) {
        websocket = new WebSocket('ws://' + host + '/websocket/monitor/' + username);
    } else {
        alert('当前浏览器 Not support websocket')
    }
    //连接成功建立的回调方法
    websocket.onopen = function () {
        console.log("websocket通道连接成功!")
    }
    //连接发生错误的回调方法
    websocket.onerror = function () {
        console.log("websocket连接发生错误!")
    };
    
    //接收到消息的回调方法
    websocket.onmessage = function (event) {
        setMessageInnerHTML(event.data);
    }
    //连接关闭的回调方法
    websocket.onclose = function () {
        console.log("websocket连接关闭!")
    }
    //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
    window.onbeforeunload = function () {
        websocket.close();
    }
    //将消息显示在网页上
    function setMessageInnerHTML(msg) {
        
        // document.getElementById('message').innerHTML += innerHTML + '<br/>';
    
    }
    

    以下是踩的坑
    按照上面的写法程序在本地启动、测试,不会有任何问题,但是当把项目部署到Linux服务器的tomcat 中时启动会报错

    java.lang.IllegalStateException: Failed to register @ServerEndpoint class:
    

    为了解决这个问题本人在网上查找了大量的资料,基本的解决办法就是说


    MonitorWebSocket类中不需要在@ServerEndpoint之前使用@Component注解,去掉后就可以在服务器正常启动
    还有一种是说添加jar包的,这种本人也试过,服务器确实可以正常启动

    以上两种解决方法都可以解决服务器启动报错问题,但是这种写法在本地启动项目的时候前端无法建立连接,还是不能推送数据,至今未找到如何使本地和服务器同时满足不需要修改代码的解决方案,最后只能在部署到服务器时将MonitorWebSocket.java类中的@Component注解去掉,若哪位大神有更好的解决办法留言私信我,谢谢

    展开全文
  • WebSocket网络协议

    2020-12-31 16:45:35
    文章目录**WebSocket****WebSocket协议****服务器** WebSocket WebSocket是HTML5新增的协议,它的目的是在浏览器和服务器之间建立一个不受限的双向通信的通道,比如说,服务器可以在任意时刻发送消息给浏览器。 为...
  • 1. WebSocket特性介绍WebSocket是HTML5开始提供的一种浏览器与服务器间进行全双工通讯的网络技术。WebSocket通信协议于2011年被IETF定为标准RFC 6455,WebSocket API也被W3C定为标准,主流的浏览器都已经支持...
  • 最近做了一个简陋的聊天室,可以发送文字,图片和语音数据,这些数据通过一个websocket对象...我想到的解决办法是对文字,图片和音频数据分别建立一个websocket对象,至少可以保证一般情况下文字信息不会堵塞。 ...
  • websocket

    2021-10-23 21:38:20
    我们知道,在网络中的两个应用程序(进程)需要全双工相互通信(全双工即双方可同时向对方发送消息),需要用到的就是socket,它能够提供端对端通信,对于程序员来讲,他只需要在某个应用程序的一端(暂且称之为...
  • websocket上传参数中文乱码问题解决

    万次阅读 2016-04-25 13:38:10
    websocket上传参数中文乱码问题解决最近在做毕业设计的时候用到websocket,遇到的一个问题,就是websocket上传参数时会中文乱码,下面是我遇到的问题描述和解决方法:websocket = new WebSocket("ws://localhost:...
  • 就可以采用websocket的长链接的形式,实时有服务端或者客户端推送数据,已达到数据的实时展示。 2、websocket简介 SpringBoot官方推荐的基于STOMP实现,STOMP:即Simple Text Orientated Messaging Protocol,它...
  • WebSocket客户端连接不上和掉线的问题以及解决方案
  • websocket报404问题

    2018-10-15 18:08:06
    1、在浏览器中测试ok,截图: ... 2、在java代码中连接报404 ...在网上找了很多说报404的,但基本都是修改服务端或者tomcat下的jar包,这些解决方案不合适我遇到的问题,万能的csdn大神,请帮我看下。
  • 如题,Struts Sping MyBatis框架,增加WebSocket功能ws请求出现404问题,可能是什么原因导致的? [img=https://img-bbs.csdn.net/upload/201703/19/1489910889_25892.png][/img]
  • IE8、IE9 WebSocket解决方案

    万次阅读 2018-08-24 12:05:00
    在项目中有需要兼容IE客户端websocket使用的需求,百度了一番发现目前很多都采用github上开源项目https://github.com/gimite/web-socket-js的实现。 项目配置: 服务器jetty9.2 ...这个问题其实在websoc...
  • 小程序WebSocket 常见问题:(本文已解决的) 1.自动断开链接,重连但是只能存在两个WebSocket问题。 ---1兼容情况:1.1 正常聊天过一段时间 WebSocket 自动断开后重新链接,并且保存之前的聊天记录 ---1兼容...
  • vue 中,运用WebSocket通信,解决前端轮询问题和多用户数据共享问题需求描述:最初实现方式:问题描述问题解决方式WebSocket 简介WebSocket 规范WebSocket 握手协议WebSocket 实战( **Vue中进行使用** )参考文档: ...
  • Websocket 断线重连及心跳问题解决方案预备代码解决方案断线重连心跳 预备代码 为描述方便,先将简单的 Websocket 连接函数 ws_connect() 贴出来,ws 为 Websocket 对象: var ws; /** * 连接 websocket * @param...
  • 这里解决 Spring 下 使用Websocket的时候 出现的问题。 场景描述 想要搭建一个类似于在线聊天室的Demo。 前端使用ws://localhost:8080/.../websocket 这里的 ... 可以有很多种 1.如果你是用Spri...
  • 最近做项目时遇到了需要多用户之间通信的问题,涉及到了WebSocket握手请求,以及集群中WebSocket Session共享的问题。 期间我经过了几天的研究,总结出了几个实现分布式WebSocket集群的办法,从zuul到spring cloud ...
  • WebSocket

    2018-12-26 10:48:48
    WebSocket 是一种网络通信协议。RFC6455 定义了它的通信标准。 WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。 为什么需要 WebSocket ? 了解计算机网络协议的人,应该都知道:...
  • iOS webSocket网络 基础知识

    千次阅读 2017-06-05 09:51:39
    这一篇我们先了解一下基本知识,这样对我们后面的学习更加有帮助 。 Socket,WebSocket,Http,Tcp等这些我们已经听的耳朵有茧了,但是用得时候还是复习一下吧。 大学学习网络基础的时...
  • 最近做项目时遇到了需要多用户之间通信的问题,涉及到了WebSocket握手请求,以及集群中WebSocket Session共享的问题。 期间我经过了几天的研究,总结出了几个实现分布式WebSocket集群的办法,从zuul到spring cloud g...
  • tomcat websocket 并发问题解决(四)

    千次阅读 2018-03-05 18:48:00
    但是假定一种情况,即在最后一步 sendMessageBlock 方法内,此时连接因为网络异常关闭了,发送方法抛出了异常,那么复位的代码 stateMachine.complete(last) 就得不到执行,state 就维持在 TEXT_PARTIAL_WRITING ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 25,655
精华内容 10,262
关键字:

websocket如何解决网络问题