php - كيف يمكنني تنفيذ "الاقتراع الطويل" الأساسي؟




http comet (12)

أبسط 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 - 
function SomeVeryLongAction(response) {
  setTimeout(response.end, 10000);
}

يمكنني العثور على الكثير من المعلومات حول كيفية عمل Long Polling (على سبيل المثال ، this ، this ) ، ولكن لا توجد أمثلة بسيطة حول كيفية تنفيذ ذلك في التعليمات البرمجية.

كل ما يمكنني العثور عليه هو cometd ، والذي يعتمد على إطار Dojo JS ، ونظام خادم معقد إلى حد ما ..

بشكل أساسي ، كيف أستخدم أباتشي لخدمة الطلبات ، وكيف يمكنني كتابة برنامج نصي بسيط (على سبيل المثال ، في PHP) والتي من شأنها "استطلاع" الخادم للرسائل الجديدة؟

لا يجب أن يكون المثال قابلاً للتطوير أو آمنًا أو كاملًا ، بل يحتاج فقط إلى العمل!


أعتقد أن العميل يبدو كطلب AJAX غير متزامن عادي ، ولكنك تتوقع أن يستغرق "وقتًا طويلاً" للعودة.

الخادم يبدو مثل هذا.

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

outputNewData();

لذلك ، ينتقل طلب AJAX إلى الخادم ، ومن المحتمل أن hasNewData() آخر تحديث حتى يعرف hasNewData() البيانات التي حصلت عليها بالفعل. الملقم ثم يجلس في حلقة النوم حتى تتوفر بيانات جديدة. طوال الوقت ، لا يزال طلب AJAX متصلاً ، هناك معلقًا في انتظار البيانات. وأخيرًا ، عندما تتوفر بيانات جديدة ، يعطيك الخادم طلب AJAX ويغلق الاتصال.


إنه أبسط مما كنت أعتقد في البداية .. أساسا لديك صفحة لا تفعل شيئا ، حتى تتوفر البيانات التي تريد إرسالها (على سبيل المثال ، رسالة جديدة تصل).

في ما يلي مثال أساسي حقًا ، يرسل سلسلة بسيطة بعد 2-10 ثوانٍ. فرصة واحدة لثلاثة في إرجاع خطأ 404 (لإظهار معالجة الأخطاء في مثال جافا سكريبت القادم)

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 سيقوم بسرعة بربط جميع "سلاسل العمليات" وتركها غير قادرة على الاستجابة لطلبات أخرى .. هناك طرق حول هذا ، ولكن يُنصح بكتابة "خادم الاستطلاع الطويل" في شيء من هذا القبيل twisted في بيثون ، والتي لا تعتمد على موضوع واحد لكل طلب. cometD هي لغة شائعة (متوفرة بعدة لغات) ، و Tornado هو إطار عمل جديد تم تصميمه خصيصًا لمثل هذه المهام (تم تصميمه لرمز الاقتراع الطويل لـ FriendFeed) ... لكن كمثال بسيط ، فإن Apache أكثر من كافٍ ! يمكن كتابة هذا النص البرمجي بسهولة بأي لغة (اخترت Apache / PHP لأنها شائعة جدًا ، وقد حدث لي أن أديرها محليًا)

ثم ، في Javascript ، تطلب الملف أعلاه ( msg_srv.php ) ، وانتظر استجابة. عندما تحصل على واحدة ، فإنك تعمل على البيانات. ثم طلب الملف والانتظار مرة أخرى ، والعمل على البيانات (وتكرار)

ما يلي مثال لمثل هذه الصفحة .. عند تحميل الصفحة ، يرسل الطلب الأولي لملف msgsrv.php .. إذا نجح ، فإننا #messages الرسالة بـ #messages div ، ثم بعد ثانية واحدة نسميه الدالة waitForMsg مرة أخرى ، مما يؤدي إلى الانتظار.

إن setTimeout() ثانية واحدة هو محدد معدل محدد ، فهو يعمل بشكل جيد بدون هذا ، لكن إذا msgsrv.php دائمًا على الفور (مع وجود خطأ في بناء الجملة ، على سبيل المثال) ، فإنك تغرق المتصفح ويمكنه أن يتجمد بسرعة. من الأفضل أن يتم التحقق مما إذا كان الملف يحتوي على استجابة JSON صالحة و / أو الاحتفاظ بإجمالي عدد الطلبات لكل دقيقة / ثانية والإيقاف المؤقت بشكل مناسب.

إذا كانت أخطاء الصفحة ، فإنها #messages الخطأ بـ #messages ، وتنتظر 15 ثانية ثم تحاول مرة أخرى (مثل الطريقة التي #messages بعد ثانية واحدة من كل رسالة)

الشيء الجميل في هذا النهج هو أنها مرنة للغاية. في حالة وفاة اتصال الإنترنت الخاص بالعملاء ، سوف تنتهي المهلة ، ثم يحاولون إعادة الاتصال - وهذا أمر متأصل في مدة عمل الاقتراع ، ولا يتطلب الأمر معالجة معقدة للأخطاء.

على أي حال ، رمز 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>

تم تصميم Tornado للاستطلاعات الطويلة ، ويتضمن تطبيقًا صغيرًا للغاية (بضع مئات من خطوط بايثون) للدردشة في / الأمثلة / chatdemo ، بما في ذلك رمز الخادم ورمز عميل JS. يعمل مثل هذا:

  • يستخدم العملاء JS لطلب تحديثات منذ (عدد الرسائل الأخيرة) ، ويتلقى ملقم URLHandler هذه ويضيف رد استجابة إلى العميل إلى قائمة انتظار.

  • عندما يحصل الخادم على رسالة جديدة ، يتم تنشيط حدث onmessage ، يتدفق عبر عمليات رد الاتصال ، ويرسل الرسائل.

  • يتلقى JS من جانب العميل الرسالة ، ويضيفها إلى الصفحة ، ثم يسأل عن التحديثات منذ معرف الرسالة الجديد هذا.


في ما يلي مثال بسيط للاستطلاعات الطويلة في PHP بواسطة Erik Dubbelboer باستخدام عنوان Content-type: multipart/x-mixed-replace :

<?

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



لقد حصلت على مثال دردشة بسيط للغاية كجزء من slosh .

تعديل : (نظرًا لأن الجميع يلصق رمزه هنا)

هذه هي الدردشة متعددة المستخدمين المستندة إلى JSON كاملة باستخدام الاقتراع طويل الأمد slosh . هذا عرض توضيحي لكيفية إجراء المكالمات ، لذا يرجى تجاهل مشكلات 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 <[email protected]> -->
<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>

لماذا لا تنظر في مآخذ الويب بدلا من الاقتراع الطويل؟ فهي تتميز بالكفاءة والسهولة في الإعداد. ومع ذلك فهي مدعومة فقط في المتصفحات الحديثة. هنا إشارة سريعة .


هذا هو screencast لطيفة 5 دقائق على كيفية القيام باستطلاعات طويلة باستخدام PHP و jQuery: http://screenr.com/SNH

الرمز يشبه تماما المثال dbr أعلاه.


هذا هو واحد من السيناريوهات التي PHP هو خيار سيء للغاية. كما ذكرنا سابقا ، يمكنك ربط جميع العاملين في أباتشي بسرعة كبيرة في فعل شيء من هذا القبيل. بنيت PHP للبداية ، والتنفيذ ، والتوقف. إنها غير مبنية للبداية ، انتظر ... نفذ ، توقف. سوف تعطل الخادم الخاص بك بسرعة كبيرة وتجد أن لديك مشاكل كبيرة في القياس.

ومع ذلك ، لا يزال بإمكانك القيام بذلك باستخدام لغة PHP وجعله لا يقتل خادمك باستخدام nginx HttpPushStreamModule: http://wiki.nginx.org/HttpPushStreamModule

قمت بإعداد nginx أمام Apache (أو أي شيء آخر) وسيتولى الأمر عقد الاتصالات المتزامنة. أنت فقط تستجيب مع الحمولة عن طريق إرسال البيانات إلى عنوان داخلي يمكنك القيام به مع وظيفة في الخلفية أو لمجرد إطلاق الرسائل على الأشخاص الذين كانوا ينتظرون عندما تأتي الطلبات الجديدة. هذا يحافظ على عمليات PHP من الجلوس مفتوحة خلال الاقتراع الطويل.

هذا ليس مقصورًا على PHP ويمكن استخدامه باستخدام nginx مع أي لغة خلفية. تحميل الاتصالات المفتوحة المتزامن يساوي Node.js لذا فإن أكبر ميزة هي أنها تخرجك من NEEDING Node لشيء من هذا القبيل.

ترى الكثير من الأشخاص الآخرين يذكرون مكتبات اللغات الأخرى لإنجاز الاقتراع الطويل وهذا لسبب وجيه. لم يتم بناء PHP بشكل جيد لهذا النوع من السلوك بشكل طبيعي.


يوجد أدناه حل طويل للاستقصاء قمت بتطويره لـ Inform8 Web. أساسا تجاوز الطبقة وتنفيذ طريقة loadData. عندما ترجع loadData قيمة أو تنتهي مهلة العملية ، ستقوم بطباعة النتيجة والعودة.

إذا كانت معالجة البرنامج النصي تستغرق أكثر من 30 ثانية ، فقد تحتاج إلى تغيير استدعاء set_time_limit () إلى شيء أطول.

رخصة أباتشي 2.0. أحدث نسخة على جيثب 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();

}

إليك مثال node.js الذي يأتي مع عميل jquery. هناك أيضا تعليمات حول إعداده على heroku.





comet