Netty websocket 推送数据压缩以 js解压


       在项目开发的时候利用基于Netty 的websocket项目,有时会发现在推送过程中经常不推送了。经过研究调查发现服务器在高并发的情况下,推送的数据流量接近带宽流量峰值,会导致带宽不足无法继续推送新的数据。

      为了解决这个问题:方法1:加大带宽。(花费多点钱买带宽流量)

                                     方法2:压缩数据。(减少网络传输带宽流量)

方法1没什么好说的,给钱就可以了。

主要讲讲方法2:压缩传输数据。(我网上我搜了好久没有比较完整的处理方法和代码,特记录一下处理过程给有需要的人参考)

首先:在websocket 服务端推送数据的时候,对要传输的数据进行压缩,这里我用GZIP进行压缩并用BASE64进行编码。代码如下:

 /**
     * 发送消息
     * @param set 频道通道集合
     * @param msg 发送消息内容
     */
    public static void sendBinaryMsg(CopyOnWriteArraySet<Channel> set,String msg,String channelName){
     
     if(set==null)return;
  try {
   if(set.size()==0)return;
   String frameType = "";
   msg = ZipUtil.gzip(msg);//压缩数据
   AttributeKey<String> key = AttributeKey.valueOf(channelName+"_binary");
   Iterator<Channel> it=set.iterator();
   while(it.hasNext()){
    Channel channel=it.next();
    if(channel.isActive()){
      if(channel.attr(key)!=null){
        frameType = channel.attr(key).get();
      }
     if(frameType != null && frameType.equals("false")){
      channel.writeAndFlush(new TextWebSocketFrame(msg));
     }else{
      channel.writeAndFlush(new BinaryWebSocketFrame(Unpooled.wrappedBuffer(msg.getBytes())));
     }
    }else{
     set.remove(channel);
    }
   }
  } catch (Exception e) {
   e.printStackTrace();
  }
    }

 

此处用到 ZipUtil.java的gzip压缩:

 

 package com.world.common;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

/**
 * 解压缩字符串工具类
 * @author zhanglinbo 20160827
 *
 */
public class ZipUtil {

 /**
  *
  * 功能:使用gzip进行压缩,然后再用Base64进行编码
  * @param 待压缩字符串
  * @return 返回压缩后字符串
  * @author zhanglinbo  20160827
  */
 @SuppressWarnings("restriction")
 public static String gzip(String primStr) {
  if (primStr == null || primStr.length() == 0) {
   return primStr;
  }

  ByteArrayOutputStream out = new ByteArrayOutputStream();
  
  GZIPOutputStream gzip = null;
  try {
   gzip = new GZIPOutputStream(out);
   gzip.write(primStr.getBytes());
   
  } catch (IOException e) {
   e.printStackTrace();
  } finally {
   if (gzip != null) {
    try {
     gzip.close();
     
    } catch (IOException e) {
     e.printStackTrace();
    }
   }
  }

  return  new sun.misc.BASE64Encoder().encode(out.toByteArray());
 }

 /**
  *
  * <p>
  * Description:使用gzip进行解压缩
  * 先对压缩数据进行BASE64解码。再进行Gzip解压
  * </p>
  *
  * @param compressedStr 压缩字符串
  * @return 返回解压字符串
  */
 @SuppressWarnings("restriction")
 public static String gunzip(String compressedStr) {
  if (compressedStr == null) {
   return null;
  }

  ByteArrayOutputStream out = new ByteArrayOutputStream();
  ByteArrayInputStream in = null;
  GZIPInputStream ginzip = null;
  byte[] compressed = null;
  String decompressed = null;
  try {
   compressed = new sun.misc.BASE64Decoder().decodeBuffer(compressedStr);
   in = new ByteArrayInputStream(compressed);
   ginzip = new GZIPInputStream(in);

   byte[] buffer = new byte[1024];
   int offset = -1;
   while ((offset = ginzip.read(buffer)) != -1) {
    out.write(buffer, 0, offset);
   }
   decompressed = out.toString();
  } catch (IOException e) {
   e.printStackTrace();
  } finally {
   if (ginzip != null) {
    try {
     ginzip.close();
    } catch (IOException e) {
    }
   }
   if (in != null) {
    try {
     in.close();
    } catch (IOException e) {
    }
   }
   if (out != null) {
    try {
     out.close();
    } catch (IOException e) {
    }
   }
  }

  return decompressed;
 }

 /**
  * 使用zip进行压缩
  *
  * @param str
  *            压缩前的文本
  * @return 返回压缩后的文本
  */
 @SuppressWarnings("restriction")
 public static final String zip(String str) {
  if (str == null)
   return null;
  byte[] compressed;
  ByteArrayOutputStream out = null;
  ZipOutputStream zout = null;
  String compressedStr = null;
  try {
   out = new ByteArrayOutputStream();
   zout = new ZipOutputStream(out);
   zout.putNextEntry(new ZipEntry("0"));
   zout.write(str.getBytes());
   zout.closeEntry();
   compressed = out.toByteArray();
   compressedStr = new sun.misc.BASE64Encoder().encodeBuffer(compressed);
  } catch (IOException e) {
   compressed = null;
  } finally {
   if (zout != null) {
    try {
     zout.close();
    } catch (IOException e) {
    }
   }
   if (out != null) {
    try {
     out.close();
    } catch (IOException e) {
    }
   }
  }
  return compressedStr;
 }

 /**
  * 使用zip进行解压缩
  *
  * @param compressed
  *            压缩后的文本
  * @return 解压后的字符串
  */
 @SuppressWarnings("restriction")
 public static final String unzip(String compressedStr) {
  if (compressedStr == null) {
   return null;
  }

  ByteArrayOutputStream out = null;
  ByteArrayInputStream in = null;
  ZipInputStream zin = null;
  String decompressed = null;
  try {
   byte[] compressed = new sun.misc.BASE64Decoder().decodeBuffer(compressedStr);
   out = new ByteArrayOutputStream();
   in = new ByteArrayInputStream(compressed);
   zin = new ZipInputStream(in);
   zin.getNextEntry();
   byte[] buffer = new byte[1024];
   int offset = -1;
   while ((offset = zin.read(buffer)) != -1) {
    out.write(buffer, 0, offset);
   }
   decompressed = out.toString();
  } catch (IOException e) {
   decompressed = null;
  } finally {
   if (zin != null) {
    try {
     zin.close();
    } catch (IOException e) {
    }
   }
   if (in != null) {
    try {
     in.close();
    } catch (IOException e) {
    }
   }
   if (out != null) {
    try {
     out.close();
    } catch (IOException e) {
    }
   }
  }
  return decompressed;
 }
 
}

 

--------------------------以上是netty websocket 服务端发送数据前的数据压缩----------------------------------

在浏览器端,利用js对压缩的数据进行解压缩。这里用到了pako项目的高效js解压缩组件。项目地址为:https://github.com/nodeca/pako 实测解压速度是毫秒级。

以下是测试例子,对压缩编码过的数据进行解压,打印日志,看是否正常

<!DOCTYPE html>
<html>
<meta charset="UTF-8">
<script src="https://cdn.jsdelivr.net/pako/1.0.3/pako.min.js"></script>
<script>
'use strict';

var pako = window.pako;

// In browser

// Get some base64 encoded binary data from the server. Imagine we got this:
var b64Data     = 'H4sIAAAAAAAAAJWSwW6DMAyGX4WbpQpFcRJM4Lqde+l6mCoOjOZQbcAE6QFVffcFWBq6VkjL4ZftfPplO7nA0fQQ5RFAFEdw6nfnakxtdzbRWDmWtpyAC+x3ry/bd8hhs4EYqraxXVnZfXOyvjbCkB8OqFKUkgRxd2LkDPGJCsaLeGKVJtKrLGdcBDhVHn4QzgKX4noDjl+4plL9g87+Dkd3YcIE6QBL4WF3J5hIlqFUzln9wgnyAHN8ovK2NiJUtMoue9YktVrQkvul+ViwLKNAZ7P3fDdrdhPF0Bu76dTccnbPTILuYZlM9ALWD753ynSAJapVOOHj8qgoYqjL7tPYbVkb/x3rtjHD2/A9FqpmcJV+qD/aL5caW8H1+gMYWAQw/gIAAA==';

// Decode base64 (convert ascii to binary)
var strData     = atob(b64Data);

// Convert binary string to character-number array
var charData    = strData.split('').map(function(x){return x.charCodeAt(0);});

// Turn number array into byte-array
var binData     = new Uint8Array(charData);

// Pako magic
var data        = pako.inflate(binData);

// Convert gunzipped byteArray back to ascii string:
var strData     = String.fromCharCode.apply(null, new Uint16Array(data));

// Output to console
console.log(strData);

</script>
</html>
<body>
Sending objects to server, run server code to see result.
</body>


 在项目中实际应用:

在监听websocket 消息方法中进行对数据解压:此处还判断了服务端返回的是二进制数据还是文本数据。

通过  ungzip 方法进行解压,并在回调函数中进行业务逻辑的后续处理。之所以要用回调函数,因为在等解压完成后,才能进行后续的操作,否则返回的数据可能是空的。

 //websocket 接收消息处理

    $this.socket.onmessage = function(result) {

    var json = null;

    if (result.data instanceof Blob) {    

        var blob = result.data;            

        //js中的blob没有没有直接读出其数据的方法,通过FileReader来读取相关数据      

        var reader = new FileReader();            

        reader.readAsText(blob);          

        //  当读取操作成功完成时调用.        

        reader.onload = function(evt){       

        if(evt.target.readyState == FileReader.DONE){ 

        var beford = lenght = evt.target.result.length;

        ungzip(evt.target.result,function(result){

                                                console.log("解压前:"+beford+" 解压后:"+result.length);

             

                                                if(result.indexOf("(")!=0){

          json = eval("("+result+")");

          }else{

          json = eval(result);

          }

         

          //处理业务逻辑

          $this.dealMessage(json);

        });

        

        }

        }

  }else{

   ungzip(result.data,function(result){

//console.log("解压后:"+result);

      if(result.indexOf("(")!=0){

      json = eval("("+result+")");

      }else{

      json = eval(result);

      }

     //处理业务逻辑

     $this.dealMessage(json);

  });

 

      }

        };

 

 js 解压方法: 这里使用了seajs 的模块化处理引入pako.如不用seajs,直接在页面代码引入pako.min.js即可。

 

 /**

 * 解压缩字符串

 * @param zipData 经过 gzip压缩和base64编码的字符串

 * @param callback 回调函数 用解压缩后的数据进行处理后续操作

 * @author zhanglinbo 20160827

 */

function ungzip(zipData,callback){

var unZipData = "";//解压缩后数据

//引入pako模块进行数据的解压缩

seajs.use(["module_pako"],function(pako){

try{

// Decode base64 (convert ascii to binary)

var strData     = atob(zipData);

// Convert binary string to character-number array

var charData    = strData.split('').map(function(x){return x.charCodeAt(0);});

// Turn number array into byte-array

var binData     = new Uint8Array(charData);

// Pako magic

var data        = pako.inflate(binData);

// Convert gunzipped byteArray back to ascii string:

unZipData     = String.fromCharCode.apply(null, new Uint16Array(data));

}catch(e){

unZipData = zipData;//解压出现异常,说明数据未压缩。用原有数据进行操作

}

//利用回调进行处理后续操作

if($.isFunction(callback)){

callback(unZipData);

}

});

}

 

通过对数据的压缩传输和页面的解压,可以极大的剩下带宽。实测在数据长度大于2000以上的情况下压缩比例可以达到60-70%。对小数据字符串压缩比例不明显,有时反而比未压缩前大一点点。。不过影响不大。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值