大家都晓得应用 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;
@Component
public 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 Specification
Numeric types
uint8 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 |
测试