解決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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章