折腾一下WebSocket的ArrayBuffer传输方式

  前言
  
  之前写WebSocket都是基于文本传输的,后来准备升级项目,于是打算尝试一下arraybuffer传输方式,由于是第一次使用javascript处理字符串转arraybuffer,不过真的是一把辛酸泪啊,特此记录。
  
  项目背景
  
  还是之前写的(基于Tio通讯框架的SpringBootLayIM项目)[https://github.com/fanpan26/SpringBootLayIM].
  
  开发过程还原
  
  1.要将客户端的传输方式改为arraybuffer。
  
  var ws = new WebSocket(tool.www.ysyl157.com options.server + '?access_token=' + token);
  
  ws.binaryType = 'arraybuffer';
  
  2.我们就要用到框架中的 IWsMsgHandler.onBytes(WsRequest wsRequest, byte[] bytes, ChannelContext channelContext)方法接收客户端的消息
  
  /**
  
  * 字节传输
  
  * */
  
  public Object onBytes(WsRequest wsRequest, byte[www.xgll521.com] bytes, ChannelContext channelContext) throws Exception {
  
  System.out.println("接收到的消息为:"www.michenggw.com +new String(bytes));
  
  return null;
  
  }
  
  3.在ws.send之前要先将发送的内容转换。
  
  //
  
  var buff = new TextEncoder().encode(str);
  
  很好,这三个步骤做完之后,我们试一下。可以看到,打印结果正常
  
  接收到的消息为:{"username":"雍正王","avatar":"/static/images/demo/huangshang.jpg","id":203328,"type":"friend","content":"22"}
  
  到此为止呢,还是没有任何问题的。按照我之前的思路就是将消息反序列化成对象实例,然后进行相应的逻辑操作。不过呢,后来突发奇想,本来我传递过来的消息就是想原封不动的发送给对方,也就是说程序不需要执行反序列化的过程。那可不可以这样,如下图:
  
  没错就是这样子,然后我打印了一下new TextEncoder().encode(str);返回的内容如下:
  
  可以看到,它是一个 Uint8Array,于是我就看看能否重新构造一个数组,用伪代码实现就是酱紫:
  
  var newBuff = new ArrayBuffer(4+1+bodyArray.buffer.byteLength)
  
  再查资料发现ArrayBuffer得依赖DataView来进行赋值操作,那不就简单啦。先把接收人ID(UInt32)和 消息类型 (UInt8) 写入,如下:
  
  var view = new DataView(dataBuff.buffer);
  
  view.setInt32(0, targetId);
  
  view.setInt8(4, 1);
  
  那剩下的body怎么写进去呢?按照我想的,应该是有个 setContent(arr,offset:number)方法,可是查了一下,没有。于是乎再查文档,原来Unit8Array有个遍历的方法,那就用这个方法试试吧。
  
  //从索引第五个开始写
  
  dataBuff.forEach(function (value, index) {
  
  view.setInt8(5+index,value);
  
  });
  
  为了验证我这个思路(呜呜,这个思路还是研究了好久,浪费了很多时间。。。其实中间由于不熟悉DataView和ArrayBuffer导致做了很多尝试的工作,而且都失败了,要么就是报错,要么就是覆盖了消息体)的正确性,我们将后台代码改一下:
  
  byte[] targetIdBytes = Arrays.copyOf(bytes,4);
  
  byte[] contents = Arrays.copyOfRange(bytes,5,bytes.length);
  
  System.out.println("消息体:"+new String(contents));
  
  int targetId = ConvertUtil.byteArrayToInt(targetIdBytes);
  
  System.out.println("接收人:"+targetId);
  
  System.out.println("消息类型"+bytes[4]);
  
  运行正常。
  
  可是,总觉得在遍历复制一遍有点繁琐。那既然setInt32,setUInt8这些方法是覆盖数组里的值的,那我可不可以这样写呢?使用占位符的方式,也就是说,在不影响消息体的情况下,在转化成byte数组之后,前五位是占位数据,后边才是正确的消息,那么我重写前五位的内容也不会受到什么影响了,而且不用遍历赋值了。说干就干,改成代码如下:
  
  var str = placeholder + JSON.stringify(d);
  
  var buff = new TextEncoder().encode(str);
  
  return buff;
  
  问题就在于这个placeholder的值是什么呢?其实我们使用小写字母代替就可以了。比如 'abcde',看一下转换的结果:
  
  不出我所料,那这样的话,就没问题啦,直接覆盖前五位就可以了。客户端完整代码如下:
  
  //根据layim提供的data数据,进行解析
  
  var mine = data.mine,
  
  to = data.to,
  
  id = mine.id,
  
  group = to.type === 'group';
  
  if (group) {
  
  id = to.id;
  
  }
  
  //构造消息
  
  var msg = {
  
  username: mine.username
  
  , avatar: mine.avatar
  
  , id: id
  
  , type: to.type
  
  , content: mine.content
  
  }, targetId = to.id
  
  var dataBuff = this.encode(msg);
  
  var view1 = new DataView(dataBuff.buffer);
  
  view1.setInt32(0, targetId);
  
  view1.setInt8(4, group ? msgType.chatGroup : msgType.chatFriend);
  
  return view1.buffer;
  
  运行一下,结果没问题,大功告成!
  
  总结
  
  从问题的发出到解决,虽然从博客上来看没有什么难度,但是自己在做的时候,搜了很多资料,尝试了很多次也没有结果,原因是自己自动脑补了一些对象的操作API,基础知识还是很重要的啊,滚回去学习。虽然最终的思路不一定是最好的,或者还有一些其他的问题,但是多尝试一下还是有很多意外的收获。那么问题来了。反序列化和数组的拷贝哪个效率更高一些呢?
  
  //这个好还是
  
  byte[] targetIdBytes = Arrays.copyOf(bytes,4);
  
  byte[] contents = Arrays.copyOfRange(bytes,5,bytes.length);
  
  //这个好呢?
  
  Json.toBean(new String(contents))

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值