在HTTP响应中发送大型比特流的有效方式

Efficient way of sending a large bit stream in an HTTP response

本文关键字:有效 方式 大型 响应 HTTP      更新时间:2023-09-26

我需要通过连线将表示数字的大字符串发送到用Java Script编写的客户端。理论上,它是0s1s的流,比如:011101011101101...,但它可以非常非常大(长度为数百万比特)。当然,我的目标是尽量减少必要的处理和发送开销。我考虑过更改该字符串的基数,使其使用十六进制或更大的基数,这将大大减少必须发送的数据量。JavaScript内置了用于转换不同编号系统的函数,因此看起来是可行的。然而,支持的最大基数只有36。我的计算表明,当具有5000万比特的流并使用36基数时,仍然需要发送1,388,888字符——太多了。

我的问题是,你知道有什么方法可以帮助我实现目标吗?一些限制:

  • 解决方案必须适用于任意长度的流
  • 比特流可以大到50mln比特
  • 对于长度约为1-10mln的比特,应保证良好的性能。对于较大的流,它应该仍然有效,但不必线性扩展
  • 我会更加强调优化必须发送的数据量,而不一定是减少CPU开销

您可以这样做(编写演示时没有测试方法,但它"应该可以工作™",这里有一把小提琴,适合那些想复制/剪切/粘贴的人:http://fiddle.jshell.net/sywz3aym/2/
请注意,小提琴不能演奏,我本来打算写一个回应者,但恐怕现在不行。

在javascript输入区域的底部,我有一个关于asp.net响应程序外观的评论部分,如果你使用visualstudio,它会使用一个"Generic Handler(.ashx)"文件;如果你使用任何其他语言,你必须使用那里的等效选项。你需要一个请求响应程序,你可以自定义它来返回二进制数据,你需要将内容类型设置为"应用程序/八位字节流"(八位字节用于那些不知道="8组"的人,例如一个字节:)

这是javascript+注释,就像一堵华丽的文本墙:

$(document).ready(function () {
    var url = 'your handler URL';
    var oReq = new XMLHttpRequest();
    oReq.open('GET', url, true);
    oReq.responseType = 'arraybuffer';
    oReq.onload = function (oEvent) {
        var buffer = oReq.response;
        var yourData = ExtractData(buffer);
    }
    oReq.send(null);
});
function ExtractData(buffer) {
    var dataReader = {
        dataView: new DataView(buffer),
        readPtr: 0,
        littleEndian: (function () {
            var buffer = new ArrayBuffer(2);
            new DataView(buffer).setInt16(0, 256, true);
            return new Int16Array(buffer)[0] === 256;
        })(),
        setReadPtrOffset: function (byteIndex) {
            this.readPtr = byteIndex;
        },
        nextInt8: function () {
            var data = this.dataView.getInt8(this.readPtr);
            this.readPtr += 1; // Sizeof int
            return data;
        },
        nextUint8: function () {
            var data = this.dataView.getUint8(this.readPtr);
            this.readPtr += 1; // Sizeof int8
            return data;
        },
        nextInt32: function () {
            var data = this.dataView.getInt32(this.readPtr, this.littleEndian);
            this.readPtr += 4; // Sizeof int32
            return data;
        },
        nextUint32: function () {
            var data = this.dataView.getUint32(this.readPtr, this.littleEndian);
            this.readPtr += 4; // Sizeof uint32
            return data;
        },
        nextFloat32: function () {
            var data = this.dataView.getFloat32(this.readPtr, this.littleEndian);
            this.readPtr += 4; // Sizeof float
            return data;
        },
        nextFloat64: function () {
            var data = this.dataView.getFloat64(this.readPtr, this.littleEndian);
            this.readPtr += 8; // Sizeof double
            return data;
        },
        nextUTF8String: function (length) {
            var data = String.fromCharCode.apply(null, new Uint8Array(this.dataView.buffer, this.readPtr, length));
            this.readPtr += length; // Sizeof int
            return data;
        },
    }
    var numberOfInt32ToRead = dataReader.nextInt32(); // First data could be, for example, the number of ints to read.
    for(var i = 0; i < numberOfInt32ToRead; i++){
        var someInt = dataReader.nextInt32();
        // doStuffWithInt(someInt);
    }
}
/*
Serverside code looks kind of like this (asp.net/c#):
    public class YourVeryNiceDataRequestHandler : IHttpHandler
    {
        public void ProcessRequest(HttpContext context)
        {
            context.Response.ContentType = "application/octet-stream"; // <- very important
            List<int> data = SomeMethodWithGivesYouData();
            context.Response.BinaryWrite(BitConverter.GetBytes((int)data.Count)); // Explicit type casting only to help me get an overview of the data being sent, in this case it has no functional meaning other then to help me debug
            foreach(int i in data)
            {
                context.Response.BinaryWrite(BitConverter.GetBytes((int)i));
            }
            // You could send structs as well, either with the help of a binary formatter, or you can do like i did here and use BitConverter.GetBytes, be carefull when sending strings/chars since they are by default (usually) in a widechar format (1 char = 2 bytes), since i only use english for this i can convert it to a UTF8 char (1 byte):                         context.Response.BinaryWrite(System.Text.Encoding.UTF8.GetBytes(new char[] { (motion.Phase == Phases.Concentric ? 'C' : 'E') })); // Phase (as single char)
        }
    }
*/

我希望这能直接帮助你,或者引导你朝着正确的方向前进
请注意,这在很大程度上取决于您的服务器使用的数据类型,并且可能不属于您的"REST"类别,但是您确实声明要针对流大小进行优化,此afaik是实现这一点的最佳方法,添加数据压缩除外
Javascript类型的数组"模仿"c风格的数据类型,如c、c++、c#、java等中使用的数据类型

免责声明:
我没有在数据>1MB的情况下尝试过这种方法,但它确实将从服务器发送到客户端的数据总大小从2MB减少到了10-100~KB,而且对于我发送/读取的数据量,执行时间"无法测量",除了额外请求的往返时间,因为这是在浏览器中加载页面后请求的

最后一句警告:任何手动的位序列化/反序列化都应该小心进行,因为忘记在客户端或服务器端更改了某些内容很容易出错,如果在两端读写的字节太多/太少,就会产生不可调试的垃圾,这就是为什么我在服务器端代码中添加显式类型转换,这样,我可以同时打开服务器代码和客户端代码,并将readInt32与Write((int)…)匹配
这不是一项有趣的工作,但它使事情变得非常紧凑和快速(通常我会追求可读性,但有些任务只需要比可读代码工作得更快)

然而,打字阵列不可能在每个浏览器中都使用,但犬科动物表示,85%的互联网都可以使用它们:http://caniuse.com/#feat=typedarrays