【问题标题】:Difference between polling using ajax request and websockets使用 ajax 请求和 websockets 轮询的区别
【发布时间】:2018-10-11 23:38:27
【问题描述】:

我正在尝试将我的烧瓶应用程序上的输出从我的服务器连续流式传输到客户端(webapp)。我可以通过打开一个 ajax 请求并像这样打开它来成功地流式输出:

//Open up a stream
let url = '/stream';
xhr.open('GET', url);
xhr.send(fd);

//Display it like this
data = xhr.responseText;
$(".myTextArea").val(data);

我可以通过这样做来关闭这个 ajax 请求(但我让它保持打开状态):

xhr.readyState == XMLHttpRequest.DONE
//do stuff

但我也可以使用 websockets (flask_socketio) 不断轮询服务器输出流到客户端。

这两种轮询方法在性能方面是否相同?

这是我从远程服务器收集 AJAX 流的方式:

let streamTimer;
function stream(){
    let xhr;
    let streamData;
    let lastStreamData;
    xhr = new XMLHttpRequest();
    const url = '/stream';
    xhr.open('GET', url);
    xhr.send();
    let streamTimer = setInterval(function() {
        streamData = xhr.responseText;
        if (lastStreamData !== streamData) {
            console.log(streamData)
            lastStreamData = streamData;
        }
    }, 1000);
}

我的 ajax 调用我的烧瓶路由 stream(),它基本上使用 -f 选项跟踪日志,这样它将保持连接打开。如果日志有输出,那么它会将其发送回我的 ajax 调用并显示在网页上。

def stream():
    def generate():
        if request.method == "POST":
            hostname = request.data.decode('utf-8')

            # ssh set up
            client = set_up_client()

            # Connect to hostname
            client.connect(hostname, username=USERNAME,
                           password=PASSWORD)

            cmd = ('tail -f -n0 /home/server.log')

            stdin, stdout, stderr = client.exec_command(cmd)

            for line in iter(lambda: stdout.readline(2048), ""):
                print(line, end="")
                yield line
                if re.search(r'keyword1|keyword2', line):
                    print('change detected')
                    yield 'change detected\n'

    return Response(stream_with_context(generate()), mimetype='text/html')

【问题讨论】:

    标签: ajax python-3.x flask websocket socket.io


    【解决方案1】:

    这两种轮询方法在性能方面是否相同?

    不,它们不一样。您可能不会每 X 毫秒向服务器发送一个请求,但是您正在每 X 毫秒轮询一次 XMLHttpRequest#responseText getter,无论您如何看待它,这都是开销。

    好消息是您根本不需要时间间隔,因为每次从服务器发送数据时都会调用XMLHttpRequest#onprogress 事件处理程序。只需将间隔的内容移动到请求的 onprogress 事件处理程序中,就可以开始了。

    let data;
    const request = new XMLHttpRequest();
    request.open('GET', '/stream');
    request.onprogress = function() {
        if(data === request.response) return;
        data = request.response;
        console.log(data);
    };
    request.send();
    

    本例中没有轮询。客户端通过相应地更新目标元素来简单地处理progress 事件。如果有一段时间没有收到更新,我们不会每秒运行一次间隔来检查,所以不会造成任何伤害。

    this和socket的性能一样吗?我对此没有答案,但我愿意打赌它非常接近。足够接近以至于我会说“相当相似”。

    现在,我看到此方法与套接字之间的一个明显区别是套接字支持双向通信,而我无法使用相同的请求将发送数据多次复制到服务器。有可能,但我还没想通。


    进一步抽象

    您可以通过实现一个扩展 XMLHttpRequest 的类来抽象它,如下所示:

    class ChunkedResponseRequest extends XMLHttpRequest {
        constructor(...args) {
            super(...args);
            this.data = null;
            this.addEventListener('progress', _ => {
                if(this.data === this.response) return;
                const chunk = this.data === null ? this.response 
                                                 : this.response.substr(this.data.length);
                this.data = this.response;
                this.dispatchEvent(new MessageEvent('message', { data: chunk }));
            });
        }
    }
    

    那么你可以这样使用它:

    const request = new ChunkedResponseRequest();
    request.addEventListener('message', function({ data }) {
        console.log(data);
    });
    request.open('GET', '/stream');
    request.send();
    

    消息事件的data 属性的值将是服务器的最后响应。


    分块响应的实现

    对于那些想使用 Node.js 重现这种行为的人(我个人不使用 Python 或 Flask),这里有一个服务器的快速示例,它以相隔一秒的无限数量的块进行响应:

    const http = require('http')
    const requestHandler = (request, response) => {
        response.setHeader('Content-Type', 'text/html; charset=UTF-8');
        response.setHeader('Access-Control-Allow-Origin', '*'); // so we can access locally
        response.setHeader('Transfer-Encoding', 'chunked');
        (function send(i, delay) {
            setTimeout(_ => {
                response.write(i + '\r\n');
                send(i + 1, 1000);
            }, delay)
        })(0, 0);
    }
    const server = http.createServer(requestHandler)
    server.listen(3000, (error) => error && console.log(error));
    

    确保将客户端代码中XMLHttpRequest 的URL 设置为http://localhost:3000,其中3000 是服务器正在侦听的端口。显然,如果您已经在端口 3000 上进行了监听,那么您将需要选择一个未使用的端口。

    【讨论】:

    • 你太棒了!
    • 我尽力了:)
    【解决方案2】:

    对于 ajax 轮询,您必须不断地访问服务器才能获取数据,但对于 Web 套接字,您可以通过两种方式通信服务器也可以与您连接。 如果有东西,它将直接向您发送数据。

    【讨论】:

    • Ajax 轮询对我有用,但我打开了一个 ajax 连接并且服务器不断发回数据。我的连接只是在做一个tail -f mylog.log。 python 生成器将其发送回网页。我想知道这样做是否有缺点?
    • ajax 一请求一响应
    • 不完全是,查看我更新的帖子。我的 AJAX 可以有一个请求,但如果前一个响应不同,则会继续获得多个响应。我的问题是,这与套接字有何不同?
    • 如果我们谈论的是传统的 AJAX 轮询,这个答案是正确的,但我们谈论的是从客户端到服务器的单个请求,该请求以分块响应进行响应。 “轮询”是客户端每秒检查一次新数据的响应值。
    猜你喜欢
    • 2012-07-07
    • 2018-01-03
    • 1970-01-01
    • 2015-07-26
    • 1970-01-01
    • 2012-01-30
    • 1970-01-01
    • 2019-07-07
    • 1970-01-01
    相关资源
    最近更新 更多