websocket进行通讯时,可以选择采用字符串或者字节流的传输模式。但在发送与接收时,需要考虑数据的分包,即分成一个个请求与响应消息。无论是采用哪种传输模式,都不免要遇到这个问题。

采用字符串传输时,接收端可以将每次接收到的字符串拼接到一起,再检测是否出现了某一特定子串,比如连续两个换行,即可将一个长的字符串分隔成一个个的请求或响应消息。这种处理方式比较简单且有效。但这里,介绍另一种模式,即传输字节流。

首先考虑下分包的问题,一般分为消息头与消息体。出于简单目的,消息头里只存放一个消息体的长度,消息体为字节数组。

确定了数据包格式接下来可以写实现代码了,首先是连接:

var socket;
var uri = "ws://" + window.location.host + "/push"; // 示例地址
function Connect(uri) {
socket = new WebSocket(uri);
socket.binaryType = "arraybuffer";
socket.onopen = function (e) {
console.log("已连接至服务器");
};
socket.onclose = function (e) {
console.log("链接已关闭");
};
socket.onmessage = function (e) {
doReceive(e.data);
};
socket.onerror = function (e) {
console.log("出现错误");
};
}

这里将socket变量定义为公共的,因为后续的发送方法会用到这个变量。默认JavaScript里的WebSocket传输是采用字符串模式的,采用UTF-8编码,通过将binaryType属性设置为arraybuffer来使用字节流传输。

当发生onmessage事件时代表接收到数据,保存在参数e.data里。每一次接收都可能接收到一个完整的消息或部分消息,我们通过一个doReceive方法来进行消息数据包的拆分。代码如下:

var receive = [];
var length = 0;
function doReceive(buffer) {
receive = receive.concat(Array.from(new Uint8Array(buffer)));
if (receive.length < 4) {
return;
}
length = new DataView(new Uint8Array(receive).buffer).getUint32(0);
if (receive.length < length + 4) {
return;
}
var bytes = receive.slice(4, length + 4);
doSomething(bytes); receive = receive.slice(length + 4);
};

其中receive作为接收缓冲区,每次接收到数据时先将其存到该缓冲区里。之后检查其长度是否大于等于4字节,即上文定义的消息头长度。若满足条件,则将其作为Uint32值读取,代表消息体长度。之后检查缓冲区是否大于消息头加消息体长度,若满足则读取消息体,之后得到的bytes字节数组即为完整的消息体。最后,用剩余字节重置缓冲区以备下一次读取。

其中buffer参数为ArrayBuffer类型,其代表原始的字节数组,本身是无意义的。JavaScript通过一个个视图来解释这些字节。Uint8Array即是其中一种视图,它将ArrayBuffer中的字节作为8位无符号整数来对待,正好一字节对应一个uint8整数。类似的还有Uint16Array,它将ArrayBuffer中的字节作为16位无符号整数来对待,则每两位对应一个uint16整数。

而DataView是JavaScript API提供的一种视图,他将ArrayBuffer中的数据作为网络流对待,它采用大端编码,可以用它来读取或写入数据。这里我们用它读取了一个Uint32的值。

接下来是发送方法,代码如下:

function doSend(bytes) {
var buffer = new ArrayBuffer(bytes.length + 4);
var view = new DataView(buffer);
view.setUint32(0, bytes.length);
for (var i = 0; i < bytes.length; i++) {
view.setUint8(i + 4, bytes[i]);
}
socket.send(view);
};

其中参数bytes是已经编码过的字节数组,这里通过DataView视图将其存储到ArrayBuffer对象里,以备发送。按照上文约定,需先设置Uint32型的消息体长度,再设置消息体。

最后,本文按约定的消息格式来进行请求与响应消息的传输,消息体为不定长度的字节序列。其本身是无意义的。我们可以通过API提供的Uint16Array、Uint32Array等视图将其作为整数值序列,也可以自我实现其内容的解释方式。

比如参考这里将其作为采用UTF-8编码的字符串。之后可再将字符串打印或反序列化为JSON对象等。

最新文章

  1. Nginx配置(全)
  2. Android API Guides 学习笔记---Application Fundamentals(一)
  3. Action的动态调用方法
  4. nyoj_t218(Dinner)
  5. C/C++技巧
  6. C++之运算符重载(1)
  7. 前端基础系列——CSS规范(文章内容为转载)
  8. linux 内存管理——内核的shmall 和shmmax 参数
  9. CSS 布局Float 【1】
  10. 7.1 Backup and Recovery Types 备份和恢复类型
  11. HTTP Response Splitting攻击探究 &lt;转&gt;
  12. Android事件处理概述
  13. 8086的分段寻址技术学习总结(Segmented Addressing)
  14. il8n国际化
  15. QT 11 鼠标键盘事件添加
  16. OpenGL教程一
  17. 更新32位Spyder从3.0.0-&gt; 3.2.3
  18. spring整合quartz时间任务调度框架
  19. ecplice中代码使用快捷键无法格式化,使用其他方法将代码格式化的步骤
  20. Javaweb经典三层架构的演变

热门文章

  1. chrome调试微信,app中H5网页的方法!
  2. 汇编语言中 cs, ds,ss 的区别
  3. rpmlint 方便的rpm spec 以及rpm 文件检查工具
  4. 3-ESP8266 SDK开发基础入门篇--点亮一个灯
  5. shell脚本编程基础之case语句
  6. mysql ltrim() 函数
  7. eclipse为项目设置jdk
  8. 微信小程序 按钮固定在页面底部遮住页面显示内容问题
  9. hive 整合ranger
  10. Net core学习系列(五)——Net Core应用程序Startup类介绍