我可以找到很多关于长轮询如何工作的信息(例如,这个和这个),但没有简单的例子说明如何在代码中实现这一点。

我能找到的只有cometd,它依赖DojoJS框架和一个相当复杂的服务器系统。。

基本上,我将如何使用Apache来服务请求,以及我将如何编写一个简单的脚本(例如,用PHP)来“长轮询”服务器以获取新消息?

该示例不一定是可扩展的、安全的或完整的,它只需要工作即可!


当前回答

这比我最初想的要简单。。基本上,在您要发送的数据可用(例如,新消息到达)之前,您的页面什么都不做。

这里是一个非常基本的示例,它在2-10秒后发送一个简单的字符串。三分之一的几率返回错误404(在接下来的Javascript示例中显示错误处理)

消息srv.php

<?php
if(rand(1,3) == 1){
    /* Fake an error */
    header("HTTP/1.0 404 Not Found");
    die();
}

/* Send a string after a random number of seconds (2-10) */
sleep(rand(2,10));
echo("Hi! Have a random number: " . rand(1,10));
?>

注意:对于一个真实的站点,在像Apache这样的常规web服务器上运行它将很快占用所有的“工作线程”,使其无法响应其他请求。。有很多方法可以解决这个问题,但建议使用Python的twisted编写一个“长轮询服务器”,它不依赖于每个请求一个线程。cometD是一个流行的框架(有多种语言可供使用),Tornado是专门为此类任务制作的新框架(它是为FriendFeed的长轮询代码构建的)。。。但作为一个简单的例子,Apache已经足够了!这个脚本可以很容易地用任何语言编写(我选择了Apache/PHP,因为它们非常常见,我碰巧在本地运行)

然后,在Javascript中,请求上述文件(msg_srv.php),并等待响应。当你得到一个时,你会根据数据采取行动。然后请求文件并再次等待,对数据进行操作(并重复)

下面是这样一个页面的示例。。加载页面后,它将发送对msgsrv.php文件的初始请求。。如果成功,我们将消息附加到#messages div,然后在1秒后再次调用waitForMsg函数,这将触发等待。

1秒的setTimeout()是一个非常基本的速率限制器,如果没有这个功能,它可以正常工作,但如果msgsrv.php总是立即返回(例如语法错误)-你会淹没浏览器,它会很快冻结。最好检查文件是否包含有效的JSON响应,和/或保持每分钟/秒的请求总数,并适当暂停。

如果页面出错,它会将错误附加到#messages div,等待15秒,然后重试(与我们在每条消息后等待1秒的方式相同)

这种方法的好处是它非常有弹性。如果客户端的互联网连接失效,它将超时,然后尝试重新连接-这是轮询工作时间的固有特征,不需要复杂的错误处理

无论如何,long_poller.htm代码使用jQuery框架:

<html>
<head>
    <title>BargePoller</title>
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.2.6/jquery.min.js" type="text/javascript" charset="utf-8"></script>

    <style type="text/css" media="screen">
      body{ background:#000;color:#fff;font-size:.9em; }
      .msg{ background:#aaa;padding:.2em; border-bottom:1px #000 solid}
      .old{ background-color:#246499;}
      .new{ background-color:#3B9957;}
    .error{ background-color:#992E36;}
    </style>

    <script type="text/javascript" charset="utf-8">
    function addmsg(type, msg){
        /* Simple helper to add a div.
        type is the name of a CSS class (old/new/error).
        msg is the contents of the div */
        $("#messages").append(
            "<div class='msg "+ type +"'>"+ msg +"</div>"
        );
    }

    function waitForMsg(){
        /* This requests the url "msgsrv.php"
        When it complete (or errors)*/
        $.ajax({
            type: "GET",
            url: "msgsrv.php",

            async: true, /* If set to non-async, browser shows page as "Loading.."*/
            cache: false,
            timeout:50000, /* Timeout in ms */

            success: function(data){ /* called when request to barge.php completes */
                addmsg("new", data); /* Add response to a .msg div (with the "new" class)*/
                setTimeout(
                    waitForMsg, /* Request next message */
                    1000 /* ..after 1 seconds */
                );
            },
            error: function(XMLHttpRequest, textStatus, errorThrown){
                addmsg("error", textStatus + " (" + errorThrown + ")");
                setTimeout(
                    waitForMsg, /* Try again after.. */
                    15000); /* milliseconds (15seconds) */
            }
        });
    };

    $(document).ready(function(){
        waitForMsg(); /* Start the inital request */
    });
    </script>
</head>
<body>
    <div id="messages">
        <div class="msg old">
            BargePoll message requester!
        </div>
    </div>
</body>
</html>

其他回答

最简单的NodeJS

const http = require('http');

const server = http.createServer((req, res) => {
  SomeVeryLongAction(res);
});

server.on('clientError', (err, socket) => {
  socket.end('HTTP/1.1 400 Bad Request\r\n\r\n');
});

server.listen(8000);

// the long running task - simplified to setTimeout here
// but can be async, wait from websocket service - whatever really
function SomeVeryLongAction(response) {
  setTimeout(response.end, 10000);
}

在Express for exmaple的生产方面的场景中,您将在中间件中得到响应。做您需要做的事情,可以将所有长轮询方法的范围扩展到Map或其他流(对其他流可见),并在准备就绪时调用<Response>Response.end()。长轮询连接没有什么特别之处。休息只是您通常构建应用程序的方式。

如果你不知道我所说的范围界定是什么意思,这会让你明白

const http = require('http');
var responsesArray = [];

const server = http.createServer((req, res) => {
  // not dealing with connection
  // put it on stack (array in this case)
  responsesArray.push(res);
  // end this is where normal api flow ends
});

server.on('clientError', (err, socket) => {
  socket.end('HTTP/1.1 400 Bad Request\r\n\r\n');
});

// and eventually when we are ready to resolve
// that if is there just to ensure you actually 
// called endpoint before the timeout kicks in
function SomeVeryLongAction() {
  if ( responsesArray.length ) {
    let localResponse = responsesArray.shift();
    localResponse.end();
  }
}

// simulate some action out of endpoint flow
setTimeout(SomeVeryLongAction, 10000);
server.listen(8000);

正如你所看到的,你真的可以对所有的联系做出反应,一是做任何你想做的事。每个请求都有id,因此您应该能够使用map和访问特定的api外调用。

我用这个来掌握Comet,我还使用Java Glassfish服务器设置了Comet,并通过订阅cometdaily.com找到了许多其他示例

这是PHP非常糟糕的选择之一。如前所述,您可以很快地将所有Apache工作人员绑在一起,这样做。PHP是为启动、执行和停止而构建的。它不是为开始而建造的,等等。。。执行,停止。你会很快让你的服务器陷入困境,并发现你有难以置信的扩展问题。

也就是说,您仍然可以使用PHP执行此操作,并且不会使用nginxHttpPushStreamModule杀死您的服务器:http://wiki.nginx.org/HttpPushStreamModule

您在Apache(或任何其他)前面设置nginx,它将负责保持并发连接的开放。您只需通过将数据发送到一个内部地址来响应有效负载,该地址可以通过后台作业来完成,也可以在收到新请求时将消息发送给正在等待的人。这可以防止PHP进程在长时间轮询期间处于打开状态。

这不是PHP独有的,可以使用nginx和任何后端语言来完成。并发开放连接的负载等于Node.js,所以最大的好处是它可以让你从NEEDING Node中解脱出来。

你会看到很多人提到其他语言库来完成长时间的投票,这是有充分理由的。PHP天生就不适合这种类型的行为。

我认为客户端看起来像一个普通的异步AJAX请求,但您希望它需要“很长时间”才能返回。

然后服务器看起来像这样。

while (!hasNewData())
    usleep(50);

outputNewData();

因此,AJAX请求将发送到服务器,可能包括上次更新的时间戳,以便hasNewData()知道您已经获得了哪些数据。然后,服务器在循环中休眠,直到新数据可用。一直以来,您的AJAX请求仍然连接,只是挂在那里等待数据。最后,当新数据可用时,服务器将其提供给AJAX请求并关闭连接。

这比我最初想的要简单。。基本上,在您要发送的数据可用(例如,新消息到达)之前,您的页面什么都不做。

这里是一个非常基本的示例,它在2-10秒后发送一个简单的字符串。三分之一的几率返回错误404(在接下来的Javascript示例中显示错误处理)

消息srv.php

<?php
if(rand(1,3) == 1){
    /* Fake an error */
    header("HTTP/1.0 404 Not Found");
    die();
}

/* Send a string after a random number of seconds (2-10) */
sleep(rand(2,10));
echo("Hi! Have a random number: " . rand(1,10));
?>

注意:对于一个真实的站点,在像Apache这样的常规web服务器上运行它将很快占用所有的“工作线程”,使其无法响应其他请求。。有很多方法可以解决这个问题,但建议使用Python的twisted编写一个“长轮询服务器”,它不依赖于每个请求一个线程。cometD是一个流行的框架(有多种语言可供使用),Tornado是专门为此类任务制作的新框架(它是为FriendFeed的长轮询代码构建的)。。。但作为一个简单的例子,Apache已经足够了!这个脚本可以很容易地用任何语言编写(我选择了Apache/PHP,因为它们非常常见,我碰巧在本地运行)

然后,在Javascript中,请求上述文件(msg_srv.php),并等待响应。当你得到一个时,你会根据数据采取行动。然后请求文件并再次等待,对数据进行操作(并重复)

下面是这样一个页面的示例。。加载页面后,它将发送对msgsrv.php文件的初始请求。。如果成功,我们将消息附加到#messages div,然后在1秒后再次调用waitForMsg函数,这将触发等待。

1秒的setTimeout()是一个非常基本的速率限制器,如果没有这个功能,它可以正常工作,但如果msgsrv.php总是立即返回(例如语法错误)-你会淹没浏览器,它会很快冻结。最好检查文件是否包含有效的JSON响应,和/或保持每分钟/秒的请求总数,并适当暂停。

如果页面出错,它会将错误附加到#messages div,等待15秒,然后重试(与我们在每条消息后等待1秒的方式相同)

这种方法的好处是它非常有弹性。如果客户端的互联网连接失效,它将超时,然后尝试重新连接-这是轮询工作时间的固有特征,不需要复杂的错误处理

无论如何,long_poller.htm代码使用jQuery框架:

<html>
<head>
    <title>BargePoller</title>
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.2.6/jquery.min.js" type="text/javascript" charset="utf-8"></script>

    <style type="text/css" media="screen">
      body{ background:#000;color:#fff;font-size:.9em; }
      .msg{ background:#aaa;padding:.2em; border-bottom:1px #000 solid}
      .old{ background-color:#246499;}
      .new{ background-color:#3B9957;}
    .error{ background-color:#992E36;}
    </style>

    <script type="text/javascript" charset="utf-8">
    function addmsg(type, msg){
        /* Simple helper to add a div.
        type is the name of a CSS class (old/new/error).
        msg is the contents of the div */
        $("#messages").append(
            "<div class='msg "+ type +"'>"+ msg +"</div>"
        );
    }

    function waitForMsg(){
        /* This requests the url "msgsrv.php"
        When it complete (or errors)*/
        $.ajax({
            type: "GET",
            url: "msgsrv.php",

            async: true, /* If set to non-async, browser shows page as "Loading.."*/
            cache: false,
            timeout:50000, /* Timeout in ms */

            success: function(data){ /* called when request to barge.php completes */
                addmsg("new", data); /* Add response to a .msg div (with the "new" class)*/
                setTimeout(
                    waitForMsg, /* Request next message */
                    1000 /* ..after 1 seconds */
                );
            },
            error: function(XMLHttpRequest, textStatus, errorThrown){
                addmsg("error", textStatus + " (" + errorThrown + ")");
                setTimeout(
                    waitForMsg, /* Try again after.. */
                    15000); /* milliseconds (15seconds) */
            }
        });
    };

    $(document).ready(function(){
        waitForMsg(); /* Start the inital request */
    });
    </script>
</head>
<body>
    <div id="messages">
        <div class="msg old">
            BargePoll message requester!
        </div>
    </div>
</body>
</html>