解决webSocket中传输base64图片过大时的过慢问题

1、背景

公司项目有个需求,将发生的事件使用webSocket推送到前端(包括一张高清图),要求1秒之内在web上显示,且不能失真。

方案1:首先是将图片转换成base64,作为字符串推送给前端,但是推送过来的信息量太大,导致接收信息延时。

方案2:改为推送文件路径,但是web请求图片会有0.6-0.7毫秒的时间,一旦推送过多,会更慢,且web加载图片时会有短暂的闪烁。

2、解决方案

将字符串信息压缩后传给web,但是java对于字符串压缩量不大,因此通过node来进行处理。使用开源插件pako。

将node作为服务端,java后台(客户端)通过socket将字符串发给node,node将压缩后的图片发送给java后台(也可以直接通过node服务器发给web,当前项目重构量比较大,未使用)。再发给web去解压。

3、代码

node服务端代码

/**
 * js压缩函数
 * @type {{}}
 */
const pako = require('pako');
/**
 * 导入websocket
 */
const websocket = require('./websocket.js');

var net = require('net');
var HOST = '127.0.0.1';
var PORT = 11111;

/**
 * 压缩函数
 * @param str
 * @returns {void | number | * | Deflate.result}
 */
const gzip = function (str){
    const _str = str || args[0] || '';
    return pako.deflate(_str, { to: 'string' });
};


net.createServer(function(socket) {
    console.log('connection: ' + socket.remoteAddress + ':' + socket.remotePort);
    /**
     * 连接后 接收消息
     */
    let __data = '';
    const BEGIN = '0x420x450x470x490x4E';
    const END = '0x450x4E0x44';

    const setData = function(data) {
        // endsWidth 无用
        if (data.includes(END)) {
            const _data = gzip(__data.replace(BEGIN, '').replace(END, ''));
            // 测试代码
            // websocket.connections.forEach(function(conn) {
            //     conn.sendText(_data);
            // });
            socket.write(_data + '\n');
            __data = '';
        }
    };

    socket.on('data', function(data) {
        data = data.toString();
        const len = data.length;
        if (data.startsWith(BEGIN)) {
            __data = data;
        } else {
            __data += data;
        }
        setData(data);
    });

    /**
     * 监听关闭状态
     */
    socket.on('close', function(data) {
        console.log('close: ' + socket.remoteAddress + ' ' + socket.remotePort);
    });
}).listen(PORT, HOST);

console.log('Server listening on ' + HOST +':'+ PORT);

node websocket测试代码

const ws = require('nodejs-websocket')

const AllUserData = [];

// Scream server example: 'hi' -> 'HI!!!'
const connection = function (conn) {
    conn.on('text', function (str) {
        AllUserData.push({
            'id':str,
            'ws':conn
        });
        conn.sendText(str.toUpperCase()+'!!!')
    });
    conn.on('close', function (code, reason) {
        console.log('Connection closed')
        // 当用户退出的时候捕捉到退出的用户
        for (let i in AllUserData) {
            if (AllUserData[i].ws == conn) {
                console.log(AllUserData[i])
            }
        }
    });
    conn.on('error', function(code) {
        // 某些情况如果客户端多次触发连接关闭,会导致connection.close()出现异常,这里try/catch一下
        try {
            conn.close()
        } catch (error) {
            console.log('close异常', error)
        }
        console.log('异常关闭', code)
    });
}
const server = ws.createServer(connection).listen(8001);

module.exports = server;

// console.log(server.connections);

java客户端代码

import java.io.*;
import java.net.Socket;

public class Pako {
    private Socket socket = null; // Socket 对象
    private PrintWriter printWriter = null; // 发送事件对象
    private BufferedReader bufferedReader = null; // 接收事件对象
    public Pako() throws IOException {
        socket = new Socket("127.0.0.1", 11111);
        printWriter = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())), true);
        bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
    }

    /**
     * @param str
     * @return
     * @throws IOException
     */
    public String getPakoDataBySocket(String str) throws IOException {
        // 事件发送
        this.sendMsg(str);
        // 事件接收
        return this.getMsg();
    }

    /**
     * 关闭连接
     * @throws IOException
     */
    public void closeSocket() throws IOException {
        socket.close();
    }

    /**
     * 发送事件
     * @param str
     * @throws IOException
     */
    public void sendMsg(String str) throws IOException {
        printWriter.println(str);
        printWriter.flush();
    }

    /**
     * 接收事件
     * @throws IOException
     * @return
     */
    private String getMsg() throws IOException {
        return bufferedReader.readLine();
    }
}

java客户端测试代码

import java.io.IOException;
import java.util.Date;

public class Test {

    public static Pako pako;
    static {
        try {
            pako = new Pako();
        } catch (IOException e) {
        }
    }

    public Test() throws IOException {
    }

    public static void main(String[] args) throws IOException {
        Test _test = new Test();
        _test.test();
    }

    public void test() throws IOException {
        String b64Data = "H4sIAAAAAAAAAJ3UMQ7CMAwF0KugP2ewEzdpcxXUAbWAOiHUMqCqdyeVQAobfGXIYL8hP5ZXnEdkeNEk6vUgXTbLonC4zMjHFY/5Wm511ekdTsOCLKVp2rlIKOA2jTuBot//cr7BhobEwsbAloY8kDGyqoQ5H/oHsdwQ21cCmaspCz0L2jcYOgLHhNGw4TT1yVmBpuS9PZHWY35siqnxvimEvpE9FY4peQhfbhO0FDnuFqWAEAAA=end";
        for (int j = 0; j < 5; j++) {
            try{
                String _str = "";
                for (int i = 0; i < 1000; i++) {
                    _str += b64Data;
                }
                _str = "0x420x450x470x490x4E"  + _str + "0x450x4E0x44";
                System.out.println(new Date().getTime());
                String str = pako.getPakoDataBySocket(_str);
                Thread thread = Thread.currentThread();
                thread.sleep(1);//暂停1毫秒后程序继续执行1
            }catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

        }
        System.out.println(new Date().getTime());
    }

}

web客户端测试代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>django-websocket</title>
    <script src="http://code.jquery.com/jquery-1.11.1.min.js"></script>
    <script src="./node_modules/pako/dist/pako.min.js"></script>
    <script type="text/javascript">//<![CDATA[
    $(function () {
        function cnn() {
            if (window.s) {
                window.s.close()
            }
            /*创建socket连接*/
            var socket = new WebSocket("ws://127.0.0.1:8001");
            socket.onopen = function () {
                console.log('WebSocket open');//成功连接上Websocket
            };
            socket.onmessage = function (e) {

                const str =  pako.inflate(e.data, { to: 'string' });
                console.log(new Date().getTime())
                console.log(str.length);//打印出服务端返回过来的数据
                $('#messagecontainer').prepend('<p>' + str + '</p>');
            };
            // Call onopen directly if socket is already open
            if (socket.readyState == WebSocket.OPEN) socket.onopen();
            window.s = socket;
        }
        cnn();
        $('#connect_websocket').click(function () {
            cnn();
        });
        $('#send_message').click(function () {
            //如果未连接到websocket
            if (!window.s) {
                alert("websocket未连接.");
            } else {
                window.s.send($('#message').val());//通过websocket发送数据
            }
        });
        $('#close_websocket').click(function () {
            if (window.s) {
                window.s.close();//关闭websocket
                console.log('websocket已关闭');
            }
        });
    });
    //]]></script>
</head>
<body>
<br>
<input type="text" id="message" value="user1"/>
<button type="button" id="connect_websocket">连接 websocket</button>
<button type="button" id="send_message">发送 message</button>
<button type="button" id="close_websocket">关闭 websocket</button>
<h1>Received Messages</h1>
<div id="messagecontainer">
</div>
</body>
</html>

4、耗时(24万字符长度,包含解压缩大概100毫秒)目前未找到socket一次传输大批量数据的方法

发送事件:0  发送时间:1571033967852
发送事件:1  发送时间:1571033967939
发送事件:2  发送时间:1571033967989
发送事件:3  发送时间:1571033968013
发送事件:4  发送时间:1571033968053

 

  • 2
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值