大家都晓得应用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
JAVA | byte | int8 | -128 ~ 127 |
GOLANG | byte | uint8 | 0 ~ 255 |