<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Spring Boot + websocket 自定義協議開發(fā)

          共 27339字,需瀏覽 55分鐘

           ·

          2021-04-24 20:51

          作者:myworld

          來源:SegmentFault 思否社區(qū)


          大家都知道使用socket通信都是二進制,通信框架多是使用二進制通信,高效且快速,但在前端如何編輯發(fā)送二進制,二進制數據在日常的JavaScript中很少遇到,但是當你使用WebSocket與后端進行數據交互時,就有可能會用到二進制的數據格式。

          這里我們自定義一個簡單協議 寫一個前后端websocket交互的示例

          定義協議

          前2個字節(jié) 定義消息類型(如心跳包/權限檢查包等)
          剩余字節(jié) 定義消息體

          服務端代碼

          我們在上篇 websocket入門筆記 的基礎上再次開發(fā)

          修改 MyWebsocketHandler 繼承 BinaryWebSocketHandler

          package com.ben.websocketdemo;

          import com.fasterxml.jackson.databind.util.ByteBufferBackedInputStream;
          import org.springframework.stereotype.Component;
          import org.springframework.web.socket.BinaryMessage;
          import org.springframework.web.socket.CloseStatus;
          import org.springframework.web.socket.TextMessage;
          import org.springframework.web.socket.WebSocketSession;
          import org.springframework.web.socket.handler.AbstractWebSocketHandler;
          import org.springframework.web.socket.handler.BinaryWebSocketHandler;

          import java.nio.ByteBuffer;
          import java.nio.CharBuffer;
          import java.nio.charset.Charset;
          import java.nio.charset.StandardCharsets;
          import java.util.Calendar;

          @Component
          public class MyWebsocketHandler extends BinaryWebSocketHandler {

              @Override
              public void afterConnectionEstablished(WebSocketSession session) throws Exception {
                  System.out.println("afterConnectionEstablished");
              }

          //    // 發(fā)送
          //    @Override
          //    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
          //         String msg = message.getPayload();
          //
          //        // 向客戶端發(fā)送數據
          //        session.sendMessage(new TextMessage("你好哦: " + msg));
          //    }

              // 發(fā)送二進制消息
              @Override
              protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) throws Exception {
                  ByteBuffer byteBuffer = message.getPayload();
                  short mytype = byteBuffer.getShort();

                  byte[] bytePrefix = ByteBuffer.allocate(2).putShort(mytype).array();

                  Calendar calendar= Calendar.getInstance();
                  int m = calendar.get(Calendar.MINUTE);
                  int s = calendar.get(Calendar.SECOND);
                  String time = String.format("%02d", m) + ":" + String.format("%02d", s);
                  switch (mytype){
                      case 1: // 心跳包
                          byte[] content = time.getBytes(StandardCharsets.UTF_8);
                          byte[] bytes = byteMergerAll(bytePrefix,content);
                          session.sendMessage(new BinaryMessage(bytes));
                      break;
                      default:
                          byte[] contentRecevid = new byte[byteBuffer.remaining()];
                          byteBuffer.get(contentRecevid);
                          String recevidMsg = new String(contentRecevid, StandardCharsets.UTF_8);
                          System.out.println("收到客戶端消息: " + recevidMsg);
                          String respStr = time + " 服務端已處理: " + recevidMsg;
                          byte[] respcontent1 = respStr.getBytes(StandardCharsets.UTF_8);
                          byte[] bytes1 =byteMergerAll(bytePrefix,respcontent1);
                          session.sendMessage(new BinaryMessage(bytes1));
                      break;
                  }
              }

              private static byte[] byteMergerAll(byte[]... values) {
                  int length_byte = 0;
                  for (int i = 0; i < values.length; i++) {
                      length_byte += values[i].length;
                  }
                  byte[] all_byte = new byte[length_byte];
                  int countLength = 0;
                  for (int i = 0; i < values.length; i++) {
                      byte[] b = values[i];
                      System.arraycopy(b, 0, all_byte, countLength, b.length);
                      countLength += b.length;
                  }
                  return all_byte;
              }

              @Override
              public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
                  System.out.println("afterConnectionClosed");
              }
          }

          上面 handleBinaryMessage 方法注意幾點

          • 根據我們自定義協議, byte[] 前2個字節(jié) 用于定義消息類型 我們通過 byteBuffer.getShort() 獲取 , 之后byteBuffer的position增加2 表明前2個字節(jié)已經讀取過了
          • default分支中通過 byteBuffer.get() 獲取的是byteBuffer[position,limit]的內容
          • 關于 ByteBuffer 的相關介紹 可以自行搜索下 java ByteBuffer

          js端

          新建文件 websocketdemo.html
          <!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>Document</title>
          </head>

          <body>
              <form name="publish">
                  <input type="text" name="message">
                  <input type="submit" value="send">
              </form>

              <div id="messages"></div>
          </body>
          <script>
              let websocket = new WebSocket("ws://127.0.0.1:8080/bensocket");
              websocket.binaryType = "arraybuffer";
              const protoLen = 2; //定義協議前部固定長度

              document.forms.publish.onsubmit = function () {
                  let msg = this.message.value;

                  let content = stringToBytes(msg);
                  var buffer = new ArrayBuffer(content.length + protoLen);

                  // let buffer = new ArrayBuffer(protoLen + content.byteLength);
                  let dataView = new DataView(buffer);

                  dataView.setInt16(0, 88); // 從第0個Byte位置開始,放置一個數字為1的Short類型數據(占2 Byte) 數字代表消息類型 1是心跳包 其他根據業(yè)務自定義
                  for (var i = 0; i < content.length; i++) {
                      dataView.setUint8(protoLen + i, content[i]);
                  }

                  websocket.send(buffer);
                  return false;
              }

              setInterval(() => {
                  let buffer = new ArrayBuffer(protoLen);
                  let dataView = new DataView(buffer);

                  dataView.setInt16(0, 1); // 1表示心跳包
                  websocket.send(buffer);
              }, 1000);

              websocket.onopen = function (evt) {
                  let el = document.createElement('div');
                  el.textContent = "onopend";
                  document.getElementById("messages").prepend(el);
              };
              websocket.onclose = function (evt) {
                  let el = document.createElement('div');
                  el.textContent = "onclose";
                  document.getElementById("messages").prepend(el);
              };
              websocket.onmessage = function (evt) {
                  let { data } = evt;
                  let len = data.byteLength;
                  let buffer = new ArrayBuffer(len);
                  let dataView = new DataView(data);
                  let type = dataView.getInt16(0)

                  var arr = [];
                  for (var i = protoLen; i < len; i++) {
                      arr.push(dataView.getInt8(i));
                  }
                  let content = utf8ByteToUnicodeStr(arr);

                  let el = document.createElement('div');
                  el.textContent = (type == 1 ? "heart: " : "normal: ") + content;
                  document.getElementById("messages").prepend(el);

              };
              websocket.onerror = function (evt) {
                  document.getElementById("messages").prepend("onerror");
              };

              /**
               *@description:將string轉為UTF-8格式signed char字節(jié)數組
              *
              */
              function stringToBytes(str) {
                  var bytes = new Array();
                  for (var i = 0; i < str.length; i++) {
                      var c = str.charCodeAt(i);
                      var s = parseInt(c).toString(2);
                      if (c >= parseInt('000080', 16) && c <= parseInt('0007FF', 16)) {
                          var af = '';
                          for (var j = 0; j < (11 - s.length); j++) {
                              af += '0';
                          }
                          af += s;
                          var n1 = parseInt('110' + af.substring(0, 5), 2);
                          var n2 = parseInt('110' + af.substring(5), 2);
                          if (n1 > 127) n1 -= 256;
                          if (n2 > 127) n2 -= 256;
                          bytes.push(n1);
                          bytes.push(n2);
                      } else if (c >= parseInt('000800', 16) && c <= parseInt('00FFFF', 16)) {
                          var af = '';
                          for (var j = 0; j < (16 - s.length); j++) {
                              af += '0';
                          }
                          af += s;
                          var n1 = parseInt('1110' + af.substring(0, 4), 2);
                          var n2 = parseInt('10' + af.substring(4, 10), 2);
                          var n3 = parseInt('10' + af.substring(10), 2);
                          if (n1 > 127) n1 -= 256;
                          if (n2 > 127) n2 -= 256;
                          if (n3 > 127) n3 -= 256;
                          bytes.push(n1);
                          bytes.push(n2);
                          bytes.push(n3);
                      } else if (c >= parseInt('010000', 16) && c <= parseInt('10FFFF', 16)) {
                          var af = '';
                          for (var j = 0; j < (21 - s.length); j++) {
                              af += '0';
                          }
                          af += s;
                          var n1 = parseInt('11110' + af.substring(0, 3), 2);
                          var n2 = parseInt('10' + af.substring(3, 9), 2);
                          var n3 = parseInt('10' + af.substring(9, 15), 2);
                          var n4 = parseInt('10' + af.substring(15), 2);
                          if (n1 > 127) n1 -= 256;
                          if (n2 > 127) n2 -= 256;
                          if (n3 > 127) n3 -= 256;
                          if (n4 > 127) n4 -= 256;
                          bytes.push(n1);
                          bytes.push(n2);
                          bytes.push(n3);
                          bytes.push(n4);
                      } else {
                          bytes.push(c & 0xff);
                      }
                  }
                  return bytes;
              }

              function byteToString(array) {
                  var result = "";
                  for (var i = 0; i < array.length; i++) {
                      result += String.fromCharCode(parseInt(array[i], 2));
                  }
                  return result;
              }

              function utf8ByteToUnicodeStr(utf8Bytes){
              var unicodeStr ="";
              for (var pos = 0; pos < utf8Bytes.length;){
                  var flag= utf8Bytes[pos];
                  var unicode = 0 ;
                  if ((flag >>>7) === 0 ) {
                      unicodeStr+= String.fromCharCode(utf8Bytes[pos]);
                      pos += 1;

                  } else if ((flag &0xFC) === 0xFC ){
                      unicode = (utf8Bytes[pos] & 0x3) << 30;
                      unicode |= (utf8Bytes[pos+1] & 0x3F) << 24;
                      unicode |= (utf8Bytes[pos+2] & 0x3F) << 18;
                      unicode |= (utf8Bytes[pos+3] & 0x3F) << 12;
                      unicode |= (utf8Bytes[pos+4] & 0x3F) << 6;
                      unicode |= (utf8Bytes[pos+5] & 0x3F);
                      unicodeStr+= String.fromCharCode(unicode) ;
                      pos += 6;

                  }else if ((flag &0xF8) === 0xF8 ){
                      unicode = (utf8Bytes[pos] & 0x7) << 24;
                      unicode |= (utf8Bytes[pos+1] & 0x3F) << 18;
                      unicode |= (utf8Bytes[pos+2] & 0x3F) << 12;
                      unicode |= (utf8Bytes[pos+3] & 0x3F) << 6;
                      unicode |= (utf8Bytes[pos+4] & 0x3F);
                      unicodeStr+= String.fromCharCode(unicode) ;
                      pos += 5;

                  } else if ((flag &0xF0) === 0xF0 ){
                      unicode = (utf8Bytes[pos] & 0xF) << 18;
                      unicode |= (utf8Bytes[pos+1] & 0x3F) << 12;
                      unicode |= (utf8Bytes[pos+2] & 0x3F) << 6;
                      unicode |= (utf8Bytes[pos+3] & 0x3F);
                      unicodeStr+= String.fromCharCode(unicode) ;
                      pos += 4;

                  } else if ((flag &0xE0) === 0xE0 ){
                      unicode = (utf8Bytes[pos] & 0x1F) << 12;;
                      unicode |= (utf8Bytes[pos+1] & 0x3F) << 6;
                      unicode |= (utf8Bytes[pos+2] & 0x3F);
                      unicodeStr+= String.fromCharCode(unicode) ;
                      pos += 3;

                  } else if ((flag &0xC0) === 0xC0 ){ //110
                      unicode = (utf8Bytes[pos] & 0x3F) << 6;
                      unicode |= (utf8Bytes[pos+1] & 0x3F);
                      unicodeStr+= String.fromCharCode(unicode) ;
                      pos += 2;

                  } else{
                      unicodeStr+= String.fromCharCode(utf8Bytes[pos]);
                      pos += 1;
                  }
              }
              return unicodeStr;
          }
          </script>

          </html>
          上面代碼注意幾點
          • js代碼中使用到 ArrayBuffer 和 DataView 來操作字節(jié) 具體介紹和用法 參考文章 https://www.jianshu.com/p/468...
          可能存在的疑惑點 arr.push(dataView.getInt8(i));
          java服務器 發(fā)送給客戶端的是 byte[] , java中byte數據類型是8位、有符號的,以二進制補碼表示的整數 
          java的基本數據類型
          所以 代碼是 dataView.getInt8(i) 而不是 dataView.getUint8(i)
          這一點和golang有些不一樣 根據golang的文檔描述
          The Go Programming Language Specification
          Numeric types
          uint8 the set of all unsigned 8-bit integers (0 to 255)
          byte alias for uint8

          測試



          點擊左下角閱讀原文,到 SegmentFault 思否社區(qū) 和文章作者展開更多互動和交流,掃描下方”二維碼“或在“公眾號后臺回復“ 入群 ”即可加入我們的技術交流群,收獲更多的技術文章~

          - END -


          瀏覽 144
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  青青草手机看片爱爱爱 | 操屄视频网站 | 男女操逼黄片视频 | 操逼非常好非常棒的视频 | 亚洲自拍在线观看 |