在项目开发的时候利用基于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%。对小数据字符串压缩比例不明显,有时反而比未压缩前大一点点。。不过影响不大。
相关推荐
这是一个java web项目集成了netty websocket的完整代码。java web项目作为服务器端和客户端进行数据通信。但是常常存在提示Max frame length of 65536 has been exceeded问题。初始化握手对象时指定了...
综上所述,Spring Boot与Netty的结合使用,能够构建出强大的实时消息推送系统,满足现代Web应用中对于数据实时同步的需求。通过理解并熟练掌握上述知识点,开发者可以创建出高效、可靠的通信平台。
这使得实时推送、游戏、协作编辑等应用成为可能。Spring Boot提供了一种方便的方式来集成WebSocket,但其底层实现可能不够高效,这时Netty就派上用场了。 Netty是一个高性能、异步事件驱动的网络应用框架,适用于...
3. **Netty服务器**:创建一个Netty服务器,配置WebSocket服务器通道处理器,处理WebSocket的连接请求和数据帧。 4. **Spring与Netty的集成**:通过Spring的`WebSocketMessageBrokerConfigurer`接口,将Spring的...
在本文中,我们将深入探讨如何利用 Netty 和 WebSocket 实现心跳检测和断线重连机制。 首先,我们需要理解 WebSocket 协议。WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议,它为客户端和服务器提供了低...
WebSocket是一种在单个TCP连接上进行全双工通信的协议,允许服务器主动推送数据给客户端,无需频繁的轮询。在Spring Boot中,我们可以利用WebSocket实现即时通讯,用户之间的消息可以实时传递,提高用户体验。Stomp...
总的来说,这个项目结合了WebSocket、Netty和SpringBoot的优势,提供了一个高效、便捷的消息推送解决方案。开发者可以在此基础上根据实际需求进行定制和扩展,例如增加身份验证、权限控制、消息加密等功能,以满足...
WebSocket是一种在客户端和服务器之间建立长连接的协议,它提供了双向通信的能力,使得服务器可以主动向客户端推送数据。Netty是Java领域一个高性能、异步事件驱动的网络应用框架,常用于开发高并发、低延迟的网络...
3. **Netty中的WebSocket**: Netty提供了WebSocketServer和WebSocketClient两个处理器,它们实现了WebSocket协议的握手和数据帧处理。开发者可以通过继承这两个处理器,自定义自己的业务逻辑。 4. **WebSocket握手*...
通过 JMX,我们可以对 Netty 服务器的性能进行监控,例如并发连接数、处理速率等,以确保在高并发情况下系统仍能稳定运行。 5. **配置与启动**:Netty 服务器和客户端通常需要配置线程池、缓冲区大小、心跳机制等。...
WebSocket是一种在客户端和服务器之间建立持久连接的协议,它提供了双向通信能力,使得服务器和客户端可以实时、高效地交换数据。在开发WebSocket服务时,我们可能会遇到数据包大小的限制问题,这会影响到大文件或者...
WebSocket是一种在客户端和服务器之间建立长连接的协议,它允许双向通信,即服务器可以主动向客户端推送数据。在Web应用中,WebSocket极大地提高了实时性,广泛应用于在线聊天、股票更新、游戏、远程控制等场景。而...
而WebSocket是一种在单个TCP连接上进行全双工通信的协议,常用于实现服务器主动向客户端推送数据的需求。 描述中提到了几个关键点: 1. **服务端主动推送消息**:在传统的HTTP协议中,服务器通常只能响应客户端的...
在聊天室应用中,WebSocket尤其适合,因为它能实现消息的即时推送,无需频繁的轮询请求。 **Netty与WebSocket结合** 在Netty中,WebSocket的处理可以通过`WebSocketServerProtocolHandler`来实现,这个处理器负责...
"基于Netty5.0的高级案例NettyWebsocket" 这个标题揭示了我们将会探讨一个使用Netty 5.0框架实现的WebSocket服务器的高级应用场景。Netty是一个高性能、异步事件驱动的网络应用程序框架,主要用于快速开发可维护的高...
WebSocket是Web交互的一种高效协议,它允许服务器与客户端进行全双工通信,即双方可以同时发送数据,极大地提升了实时性。在Java世界中,Netty框架因其高效的性能和易用性,成为了实现WebSocket服务器的首选工具。本...
支持WebSocket的浏览器通过webSocket协议发送请求给我们编写的webSocket服务器,服务器对请求消息进行判断,如果是合法的webSocket请求,则获取请求消息体,并在后面追加字符串:“欢迎使用Netty WebSocket 服务,...
在本示例中,"简易版netty websocket通讯demo 聊天" 提供了一个基础的 WebSocket 协议通信的实现,用于构建聊天应用。WebSocket 是一种在客户端和服务器之间建立持久连接的协议,它允许双方进行全双工通信,即数据...
WebSocket 协议则是一种在单个TCP连接上进行全双工通信的协议,它允许服务器和客户端实时交换数据,常用于实现聊天、实时推送通知等场景。 在"Netty实现WebSocket例子"中,我们将探讨如何使用Netty来搭建WebSocket...
WebSocket是一种在客户端和服务器之间建立持久连接的协议,它允许双方进行全双工通信,即数据可以在任意方向流动,而无需反复发起请求。在Web开发中,WebSocket为实时交互提供了强大的支持,比如在线聊天、股票实时...