development

기본 "긴 폴링"을 어떻게 구현합니까?

big-blog 2020. 9. 29. 08:03
반응형

기본 "긴 폴링"을 어떻게 구현합니까?


Long Polling이 작동하는 방식 (예 : thisthis ) 에 대한 많은 정보를 찾을 수 있지만 코드에서이를 구현하는 방법에 대한 간단한 예제는 없습니다 .

내가 찾을 수있는 것은 cometd 인데 Dojo JS 프레임 워크와 상당히 복잡한 서버 시스템에 의존합니다.

기본적으로 Apache를 사용하여 요청을 처리하는 방법과 새 메시지에 대해 서버를 "긴 폴링"하는 간단한 스크립트 (예 : PHP)를 작성하는 방법은 무엇입니까?

예제는 확장 가능하거나 안전하거나 완전 할 필요가 없으며 작동하기 만하면됩니다!


처음 생각했던 것보다 간단합니다. 기본적으로 보내려는 데이터를 사용할 수있을 때까지 (예 : 새 메시지가 도착할 때까지) 아무것도하지 않는 페이지가 있습니다.

다음은 2-10 초 후에 간단한 문자열을 보내는 매우 기본적인 예제입니다. 오류 404를 반환 할 확률 3 분의 1 (다음 자바 스크립트 예제에서 오류 처리를 보여주기 위해)

msgsrv.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와 같은 일반 웹 서버에서 실행하면 모든 "작업자 스레드"가 빠르게 묶여서 다른 요청에 응답 할 수 없게됩니다.이 문제를 해결할 수있는 방법이 있지만 작성하는 것이 좋습니다. 요청 당 하나의 스레드에 의존하지 않는 Python의 twisted 와 같은 "긴 폴 서버" . cometD 는 인기있는 것 (여러 언어로 사용 가능)이고 Tornado 는 이러한 작업을 위해 특별히 만들어진 새로운 프레임 워크입니다 (FriendFeed의 긴 폴링 코드를 위해 빌드 됨) ...하지만 간단한 예로 Apache가 충분합니다. ! 이 스크립트는 어떤 언어로도 쉽게 작성할 수 있습니다 (매우 일반적이기 때문에 Apache / PHP를 선택했고 로컬에서 실행하고있었습니다)

그런 다음 Javascript에서 위 파일 ( msg_srv.php) 을 요청하고 응답을 기다립니다. 하나를 얻으면 데이터에 따라 행동합니다. 그런 다음 파일을 요청하고 다시 기다렸다가 데이터를 처리하고 반복합니다.

다음은 이러한 페이지의 예입니다. 페이지가로드되면 msgsrv.php파일에 대한 초기 요청을 보냅니다 . 성공하면 메시지를 #messagesdiv에 추가 한 다음 1 초 후에 waitForMsg 함수를 다시 호출합니다. 대기를 트리거합니다.

1 초 setTimeout()는 정말 기본적인 속도 제한 기입니다. 이것 없이도 잘 작동하지만, msgsrv.php 항상 즉시 (예 : 구문 오류와 함께) 반환되면 브라우저가 넘쳐나고 빠르게 멈출 수 있습니다. 파일에 유효한 JSON 응답이 포함되어 있는지 확인하고 / 또는 분 / 초당 총 요청을 유지하고 적절하게 일시 중지하는 것이 좋습니다.

페이지 오류가 발생하면 #messagesdiv에 오류를 추가하고 15 초를 기다린 다음 다시 시도합니다 (각 메시지 후 1 초를 기다리는 방법과 동일).

이 접근 방식의 좋은 점은 매우 탄력적이라는 것입니다. 클라이언트 인터넷 연결이 끊어지면 시간이 초과되고 다시 연결을 시도합니다. 이것은 폴링이 작동하는 시간에 내재되어 있으며 복잡한 오류 처리가 필요하지 않습니다.

어쨌든, long_poller.htmjQuery 프레임 워크를 사용 하는 코드 :

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

slosh의 일부로 정말 간단한 채팅 예제가 있습니다 .

편집 : (모든 사람이 여기에 코드를 붙여 넣기 때문에)

long-polling 및 slosh를 사용하는 완전한 JSON 기반 다중 사용자 채팅 입니다. 이것은 호출을 수행하는 방법에 대한 데모 이므로 XSS 문제를 무시하십시오. 아무도 이것을 먼저 살균하지 않고 배포해서는 안됩니다.

클라이언트는 항상 서버에 연결되어 있으며 누군가가 메시지를 보내면 모든 사람이 메시지를 대략 즉시 볼 수 있어야합니다.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<!-- Copyright (c) 2008 Dustin Sallings <dustin+html@spy.net> -->
<html lang="en">
  <head>
    <title>slosh chat</title>
    <script type="text/javascript"
      src="http://code.jquery.com/jquery-latest.js"></script>
    <link title="Default" rel="stylesheet" media="screen" href="style.css" />
  </head>

  <body>
    <h1>Welcome to Slosh Chat</h1>

    <div id="messages">
      <div>
        <span class="from">First!:</span>
        <span class="msg">Welcome to chat. Please don't hurt each other.</span>
      </div>
    </div>

    <form method="post" action="#">
      <div>Nick: <input id='from' type="text" name="from"/></div>
      <div>Message:</div>
      <div><textarea id='msg' name="msg"></textarea></div>
      <div><input type="submit" value="Say it" id="submit"/></div>
    </form>

    <script type="text/javascript">
      function gotData(json, st) {
        var msgs=$('#messages');
        $.each(json.res, function(idx, p) {
          var from = p.from[0]
          var msg = p.msg[0]
          msgs.append("<div><span class='from'>" + from + ":</span>" +
            " <span class='msg'>" + msg + "</span></div>");
        });
        // The jQuery wrapped msgs above does not work here.
        var msgs=document.getElementById("messages");
        msgs.scrollTop = msgs.scrollHeight;
      }

      function getNewComments() {
        $.getJSON('/topics/chat.json', gotData);
      }

      $(document).ready(function() {
        $(document).ajaxStop(getNewComments);
        $("form").submit(function() {
          $.post('/topics/chat', $('form').serialize());
          return false;
        });
        getNewComments();
      });
    </script>
  </body>
</html>

Tornado 는 긴 폴링을 위해 설계되었으며 서버 코드 및 JS 클라이언트 코드를 포함하여 / examples / chatdemo에 매우 최소 (수백 줄의 Python) 채팅 앱포함합니다. 다음과 같이 작동합니다.

  • 클라이언트는 JS를 사용하여 (마지막 메시지 수) 업데이트를 요청하고 서버 URLHandler는이를 수신하고 클라이언트에 응답하는 콜백을 큐에 추가합니다.

  • 서버가 새 메시지를 받으면 onmessage 이벤트가 발생하고 콜백을 반복하며 메시지를 보냅니다.

  • 클라이언트 측 JS는 메시지를 수신하고 페이지에 추가 한 다음이 새 메시지 ID 이후의 업데이트를 요청합니다.


클라이언트가 일반적인 비동기 AJAX 요청처럼 보이지만 다시 돌아 오는 데 "오랜 시간"이 걸릴 것으로 예상합니다.

그러면 서버는 다음과 같이 보입니다.

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

outputNewData();

따라서 AJAX 요청은 마지막 업데이트 시간의 타임 스탬프를 포함하여 서버로 이동하여 hasNewData()이미 가지고있는 데이터를 알 수 있습니다. 그런 다음 서버는 새 데이터를 사용할 수있을 때까지 잠자고있는 루프에 있습니다. 그 동안 AJAX 요청은 여전히 ​​연결되어 있으며 데이터를 기다리고 있습니다. 마지막으로 새 데이터를 사용할 수있게되면 서버는이를 AJAX 요청에 제공하고 연결을 닫습니다.


다음 은 C #에서 긴 폴링에 사용하는 몇 가지 클래스입니다. 기본적으로 6 개의 클래스가 있습니다 (아래 참조).

  1. Controller : 유효한 응답 (db 작업 등) 생성에 필요한 작업을 처리합니다.
  2. 프로세서 : 웹 페이지 (자체)와의 비동기 통신 관리
  3. IAsynchProcessor :이 인터페이스를 구현하는 서비스 프로세스 인스턴스
  4. Sevice : IAsynchProcessor를 구현하는 요청 객체를 처리합니다.
  5. 요청 : 응답 (객체)을 포함하는 IAsynchProcessor 래퍼
  6. 응답 : 사용자 정의 개체 또는 필드 포함

이것은 PHP 및 jQuery를 사용하여 긴 폴링을 수행하는 방법에 대한 멋진 5 분 스크린 캐스트입니다. http://screenr.com/SNH

코드는 위의 dbr 예제 와 매우 유사합니다 .


다음은 Erik DubbelboerContent-type: multipart/x-mixed-replace헤더를 사용하여 PHP로 작성한 간단한 긴 폴링 예제입니다 .

<?

header('Content-type: multipart/x-mixed-replace; boundary=endofsection');

// Keep in mind that the empty line is important to separate the headers
// from the content.
echo 'Content-type: text/plain

After 5 seconds this will go away and a cat will appear...
--endofsection
';
flush(); // Don't forget to flush the content to the browser.


sleep(5);


echo 'Content-type: image/jpg

';

$stream = fopen('cat.jpg', 'rb');
fpassthru($stream);
fclose($stream);

echo '
--endofsection
';

그리고 여기에 데모가 있습니다.

http://dubbelboer.com/multipart.php


내가 사용 혜성과 그립에 도착, 나는 또한 자바 글래스 피시 서버를 사용하여 혜성을 설정하고 cometdaily.com에 가입하여 다른 사례를 많이 발견했다


Python / Django / gevent 로 된 간단한 채팅 앱에 대한 코드가있는 이 블로그 게시물살펴보십시오 .


아래는 Inform8 Web 용으로 개발 한 긴 폴링 솔루션입니다. 기본적으로 클래스를 재정의하고 loadData 메서드를 구현합니다. loadData가 값을 반환하거나 작업 시간이 초과되면 결과를 인쇄하고 반환합니다.

스크립트 처리에 30 초 이상 걸릴 수있는 경우 set_time_limit () 호출을 더 긴 것으로 변경해야 할 수 있습니다.

Apache 2.0 라이선스. github의 최신 버전 https://github.com/ryanhend/Inform8/blob/master/Inform8-web/src/config/lib/Inform8/longpoll/LongPoller.php

라이언

abstract class LongPoller {

  protected $sleepTime = 5;
  protected $timeoutTime = 30;

  function __construct() {
  }


  function setTimeout($timeout) {
    $this->timeoutTime = $timeout;
  }

  function setSleep($sleep) {
    $this->sleepTime = $sleepTime;
  }


  public function run() {
    $data = NULL;
    $timeout = 0;

    set_time_limit($this->timeoutTime + $this->sleepTime + 15);

    //Query database for data
    while($data == NULL && $timeout < $this->timeoutTime) {
      $data = $this->loadData();
      if($data == NULL){

        //No new orders, flush to notify php still alive
        flush();

        //Wait for new Messages
        sleep($this->sleepTime);
        $timeout += $this->sleepTime;
      }else{
        echo $data;
        flush();
      }
    }

  }


  protected abstract function loadData();

}

코드 dbr 주셔서 감사합니다 . 줄 주위 long_poller.htm 의 작은 오타

1000 /* ..after 1 seconds */

나는 그것이되어야한다고 생각한다

"1000"); /* ..after 1 seconds */

작동합니다.

For those interested, I tried a Django equivalent. Start a new Django project, say lp for long polling:

django-admin.py startproject lp

Call the app msgsrv for message server:

python manage.py startapp msgsrv

Add the following lines to settings.py to have a templates directory:

import os.path
PROJECT_DIR = os.path.dirname(__file__)
TEMPLATE_DIRS = (
    os.path.join(PROJECT_DIR, 'templates'),
)

Define your URL patterns in urls.py as such:

from django.views.generic.simple import direct_to_template
from lp.msgsrv.views import retmsg

urlpatterns = patterns('',
    (r'^msgsrv\.php$', retmsg),
    (r'^long_poller\.htm$', direct_to_template, {'template': 'long_poller.htm'}),
)

And msgsrv/views.py should look like:

from random import randint
from time import sleep
from django.http import HttpResponse, HttpResponseNotFound

def retmsg(request):
    if randint(1,3) == 1:
        return HttpResponseNotFound('<h1>Page not found</h1>')
    else:
        sleep(randint(2,10))
        return HttpResponse('Hi! Have a random number: %s' % str(randint(1,10)))

Lastly, templates/long_poller.htm should be the same as above with typo corrected. Hope this helps.


This is one of the scenarios that PHP is a very bad choice for. As previously mentioned, you can tie up all of your Apache workers very quickly doing something like this. PHP is built for start, execute, stop. It's not built for start, wait...execute, stop. You'll bog down your server very quickly and find that you have incredible scaling problems.

That said, you can still do this with PHP and have it not kill your server using the nginx HttpPushStreamModule: http://wiki.nginx.org/HttpPushStreamModule

You setup nginx in front of Apache (or whatever else) and it will take care of holding open the concurrent connections. You just respond with payload by sending data to an internal address which you could do with a background job or just have the messages fired off to people that were waiting whenever the new requests come in. This keeps PHP processes from sitting open during long polling.

This is not exclusive to PHP and can be done using nginx with any backend language. The concurrent open connections load is equal to Node.js so the biggest perk is that it gets you out of NEEDING Node for something like this.

You see a lot of other people mentioning other language libraries for accomplishing long polling and that's with good reason. PHP is just not well built for this type of behavior naturally.


Why not consider the web sockets instead of long polling? They are much efficient and easy to setup. However they are supported only in modern browsers. Here is a quick reference.


The WS-I group published something called "Reliable Secure Profile" that has a Glass Fish and .NET implementation that apparently inter-operate well.

With any luck there is a Javascript implementation out there as well.

There is also a Silverlight implementation that uses HTTP Duplex. You can connect javascript to the Silverlight object to get callbacks when a push occurs.

There are also commercial paid versions as well.


For a ASP.NET MVC implementation, look at SignalR which is available on NuGet.. note that the NuGet is often out of date from the Git source which gets very frequent commits.

Read more about SignalR on a blog on by Scott Hanselman


You can try icomet(https://github.com/ideawu/icomet), a C1000K C++ comet server built with libevent. icomet also provides a JavaScript library, it is easy to use as simple as

var comet = new iComet({
    sign_url: 'http://' + app_host + '/sign?obj=' + obj,
    sub_url: 'http://' + icomet_host + '/sub',
    callback: function(msg){
        // on server push
        alert(msg.content);
    }
});

icomet supports a wide range of Browsers and OSes, including Safari(iOS, Mac), IEs(Windows), Firefox, Chrome, etc.


Simplest 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);
}

Production wise scenario in Express for exmaple you would get response in the middleware. Do you what you need to do, can scope out all of the long polled methods to Map or something (that is visible to other flows), and invoke <Response> response.end() whenever you are ready. There is nothing special about long polled connections. Rest is just how you normally structure your application.

If you dont know what i mean by scoping out, this should give you idea

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);

As you see, you could really respond to all connections, one, do whatever you want. There is id for every request so you should be able to use map and access specific out of api call.

참고URL : https://stackoverflow.com/questions/333664/how-do-i-implement-basic-long-polling

반응형