大家都晓得应用socket通信都是二进制,通信框架多是应用二进制通信,高效且疾速,但在前端如何编辑发送二进制,二进制数据在日常的JavaScript中很少遇到,然而当你应用WebSocket与后端进行数据交互时,就有可能会用到二进制的数据格式。

这里咱们自定义一个简略协定 写一个前后端websocket交互的示例

定义协定

前2个字节 定义音讯类型(如心跳包/权限查看包等)
残余字节 定义音讯体

服务端代码

咱们在上篇 websocket入门笔记 的根底上再次开发

批改 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;@Componentpublic class MyWebsocketHandler extends BinaryWebSocketHandler {    @Override    public void afterConnectionEstablished(WebSocketSession session) throws Exception {        System.out.println("afterConnectionEstablished");    }//    // 发送//    @Override//    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {//         String msg = message.getPayload();////        // 向客户端发送数据//        session.sendMessage(new TextMessage("你好哦: " + msg));//    }    // 发送二进制音讯    @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个字节 用于定义音讯类型 咱们通过 byteBuffer.getShort() 获取 , 之后byteBuffer的position减少2 表明前2个字节曾经读取过了
  • 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是心跳包 其余依据业务自定义        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字节数组    *    */    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 来操作字节 具体介绍和用法 参考文章 https://www.jianshu.com/p/468...

可能存在的纳闷点 arr.push(dataView.getInt8(i));
java服务器 发送给客户端的是 byte[] , java中byte数据类型是8位、有符号的,以二进制补码示意的整数 java的根本数据类型

所以 代码是 dataView.getInt8(i) 而不是 dataView.getUint8(i)

这一点和golang有些不一样 依据golang的文档形容

The Go Programming Language SpecificationNumeric typesuint8 the set of all unsigned 8-bit integers (0 to 255)byte alias for uint8
JAVAbyteint8-128 ~ 127
GOLANGbyteuint80 ~ 255

测试