簡體   English   中英

SSE 慢得令人難以置信

[英]SSE incredibly slow

我目前正在編寫一個網頁游戲的通信框架,通信圖如下: 代碼如下:

測試.php:

<!DOCTYPE html>
<html>
    <head>
        <title> Test </title>
        <script>
            function init()
            {
                var source = new EventSource("massrelay.php");
                source.onmessage = function(event)
                {
                    console.log("massrelay sent: " + event.data);
                    var p = document.createElement("p");
                    var t = document.createTextNode(event.data);
                    p.appendChild(t);
                    document.getElementById("rec").appendChild(p);
                };
            }

            function test()
            {
                var xhr = new XMLHttpRequest();
                xhr.onreadystatechange = function () 
                {
                    if(xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) 
                    {
                        console.log("reciver responded: " + xhr.responseText);
                    }
                }
                xhr.open("GET", "reciver.php?d=" + document.getElementById("inp").value , true);
                xhr.send();
                console.log("you sent: " + document.getElementById("inp").value);
            }
        </script>
    </head>
    <body>
        <button onclick="init()">Start Test</button> 
        <textarea id="inp"></textarea>
        <button onclick="test()">click me</button>
        <div id="rec"></div>
    </body>
</html>

這需要用戶輸入(當前是一個用於測試的文本框)並將其發送到接收器,並將接收器的響應寫回控制台,我從未收到過來自接收器的錯誤。 它還為發送的 SSE 添加了一個事件偵聽器。

接收者.php:

<?php 
    $data = $_REQUEST["d"];
    (file_put_contents("data.txt", $data)) ? echo $data : echo "error writing";
?>

如您所見,這非常簡單,僅用於在發回寫入成功之前將數據寫入 data.txt 的功能。 data.txt 只是傳遞給 massrelay.php 的“管”數據。

massrelay.php:

<?php
    header('Content-Type: text/event-stream');
    header('Cache-Control: no-cache');
    while(1)
    {
        $data = file_get_contents("data.txt");
        if ($data != "NULL")
        {
            echo "data: " . $data . "\n\n";
            flush();
            file_put_contents("data.txt", "NULL");
        }
    }
?>

massrelay.php 檢查 data.txt 中是否有任何數據,如果有,將使用 SSE 將其傳遞給具有事件偵聽器的任何人,一旦讀取數據,它將清除數據文件。

整個事情實際上工作得很好,除了輕微的 ishue,massrelay.php 從數據文件發送數據可能需要 30 秒到 10 分鍾。 對於網頁游戲來說,這是完全不可接受的,因為您需要實時動作。 我想知道是否由於我的代碼中的缺陷而花費了這么長時間,或者我不是在考慮硬件(我自己在 2006 年戴爾上用 sempron 托管它)。 如果有人看到它有任何問題,請告訴我,謝謝。

我看到您的代碼存在三個問題:

  • 不睡覺
  • 沒有 ob_flush
  • 會話

您的 while() 循環不斷讀取文件系統。 你需要放慢速度。 我在下面睡了半秒; 用可接受的延遲的最大值進行試驗。

PHP 有自己的輸出緩沖區。 您使用@ob_flush()來刷新它們( @抑制錯誤)和flush()來刷新Apache 緩沖區。 兩者都需要,順序也很重要。

最后,PHP 會話會鎖定,因此如果您的客戶端可能正在發送會話 cookie,即使您的 SSE 腳本不使用會話數據,您也必須在進入無限循環之前關閉會話。

我已將所有三個更改添加到您的代碼中,如下所示。

<?php
    header('Content-Type: text/event-stream');
    header('Cache-Control: no-cache');
    session_write_close();
    while(1)
    {
        $data = file_get_contents("data.txt");
        if ($data != "NULL")
        {
            echo "data: " . $data . "\n\n";
            @ob_flush();flush();
            file_put_contents("data.txt", "NULL");
        }
        usleep(500000);
    }

順便說一句,關於使用內存數據庫的另一個答案中的建議很好,但文件系統開銷以毫秒為單位,因此它不會解釋“30 秒到 10 分鍾”的延遲。

我不知道寫入平面文件是最好的方法。 文件 I/O 將成為您的最大瓶頸(在寫入之上閱讀意味着您將很快達到最大值)。 但假設你想繼續這樣做......

您的應用程序可以從 PHP 會話中受益,以存儲一些數據,這樣您就不必等待 I/O。 這就是MemcachedRedis等中間軟件也可以為您提供幫助的地方。 您要做的是將reciver.php中的數據存儲在您的文本文件中並將其寫入內存緩存(或將其放入寫入內存存儲的會話中)。 這使得檢索非常快速並減少了文件 I/O。

我強烈建議為您的數據建立一個數據庫。 MySQL 尤其會將經常訪問的數據加載到內存中以加快讀取操作。

編輯:我刪除了這個答案,因為 OP 說我建議的測試 1.(見下文)工作正常,所以我關於輸出緩沖的理論是錯誤的。 但另一方面,他說與本機函數相同的代碼fread fwrite fclose flock不起作用,所以如果緩沖和文件 I/O 不是解決方案,我不知道它是什么。 我刪除了我的帖子,因為我認為這不是一個有效的答案。 讓我總結一下:

  • 錯誤顯示已啟用 E_ALL
  • flush工作正常
  • OP 說他正確使用了本機文件函數fopen fread fwrite flock並且它沒有幫助。

如果flush正常工作,文件系統正常工作,我只能相信OP他是對的並放棄。

所以知道我在這里的工作已經完成了,如果我不能在 OP 的系統、配置和代碼上自己嘗試,我無能為力。

我取消了我的答案,所以 OP 可以鏈接到文檔,其他人可以看到我嘗試提出解決方案。

我刪除的舊帖子


1. 測試massrelay.php

while(true) {
    echo "test!";
    sleep(1);
} 

這樣您就可以確定該問題與文件無關。

2. 確保您啟用了error_reportingdisplay_errors

我猜你會在 30 秒后得到響應,因為 PHP 腳本在時間限制后被終止。 如果您啟用了錯誤,您會看到錯誤消息通知您。

3. 確保你真的刷新了你的輸出並且它沒有被緩沖。

可能需要 30 秒到 10 分鍾不等

您能夠在 30 秒后查看數據是有意義的,因為 30 秒是 PHP 中最大執行時間的默認值。

看起來flush()在您的場景中不起作用,您應該檢查php.ini 文件中的output_buffering設置

請看這個: php flush not working

文檔:

幾年前,我嘗試使用平面文件並將數據存儲在數據庫中,以便在多個並發用戶與服務器之間進行通信(這是針對 Flash 游戲,但適用相同的原則)。

平面文件的性能最差,因為您最終會遇到讀/寫訪問問題。

對於 DB,它最終也會因請求過多而崩潰,尤其是當您每秒訪問 DB 數千次並且沒有適當的負載平衡時。

我的答案不是解決您當前的問題,而是將您引向不同的方向。 您真的必須考慮使用套接字服務器。 也許看看類似的東西: https : //github.com/reactphp/socket

您在使用套接字服務器時可能會遇到的一些問題是共享主機不允許您運行 shell 腳本。 我的解決方案是使用我的家用 PC 進行套接字通信,並將我的域用作托管游戲的公共入口點。 顯然,我們並不是都有靜態 IP 來指向我們的游戲,所以我不得不使用 dyndns,當時它是免費的: http ://dyn.com(現在可能還有一些其他新服務是免費的)。 使用家庭服務器時,您還需要設置路由器進行端口轉發,以便將 IP/路由器上的任何特定端口請求發送到 LAN 服務器。 確保在路由器和服務器上都運行防火牆以保護其他可能暴露的端口。

我知道這可能看起來很復雜,但相信我,這是最好的解決方案。 如果您需要任何幫助 PM 我,我可以嘗試指導您解決您可能遇到的任何問題。

在不得不調試 SSE 的幾個單獨實例之一中,我發現if (ob_get_level() > 0) {ob_end_clean();}具有諷刺意味的是導致了這個問題。 如果沒有任何級別,這就是防止 PHP 錯誤產生所需的代碼。 恢復到ob_end_clean(); 解決了這個問題。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM