2015-07-22 74 views
12

我從瀏覽器發送的WebSocket通過圍繞5000字節的圖像數據,但此行是接受總共只有1394個字節:socket_recv沒有收到完整的數據

while ($bytes = socket_recv($socket, $r_data, 4000, MSG_DONTWAIT)) { 
    $data .= $r_data; 
} 

這是握手完成後這是正確的正在收到。 json數據在1394字節後被截斷。 可能是什麼原因?

在瀏覽器界面正在發送圖像JSON:

websocket.send(JSON.stringify(request)); 

瀏覽器界面是罰款,它正在與我測試過的其他PHP WebSocket的免費節目。

這裏是完整的source code

+1

那麼,找出錯誤的一個好的開始就是停止忽略'socket_recv'可能給出的任何錯誤。該錯誤信息可能實際上是有用的。通過使用[錯誤抑制運算符](http://php.net/language.operators.errorcontrol),您可以拋棄所有可能有用的錯誤信息。在嘗試對可能出現錯誤的隨機猜測之前,我會先從此開始。 – Sherif

+0

你確定沒有gzip編碼? – hek2mgl

+0

是的數據被切斷了 – user5858

回答

6

您通過指定MSG_DONTWAIT將我們的套接字設置爲非阻塞,因此它會在讀取第一個數據塊後返回EAGAIN,而不是等待更多數據。刪除MSG_DONTWAIT標誌並使用MSG_WAITALL,以等待所有要接收的數據。

有知道,如果你收到了所有你期待的數據的幾種方法:

  1. 發送數據的長度。如果要發送多個可變長度內容塊,這非常有用。例如,如果我想發送三個字符串,我可能會首先發送一個「3」來告訴接收方需要多少字符串,然後爲每個字符串發送字符串的長度,然後是字符串數據。
  2. 使用固定長度的消息。如果您期待多條消息,但每條消息的大小相同,那麼您只需從套接字讀取數據,直到至少有多個字節,然後處理消息。請注意,您可能會在單個recv()調用中收到多條消息(包括部分消息)。
  3. 關閉連接。如果您只發送一條消息,則可以半連接該連接。這是有效的,因爲TCP連接維護髮送和接收的獨立狀態,所以服務器和關閉發送連接仍然讓接收者打開客戶端的回覆。在這種情況下,服務器的所有數據發送到客戶端,然後調用,如果你想在接收它來處理數據socket_shutdown(1)

1和2是有用的 - 例如,如果你在寫一個遊戲,聊天應用,或者套接字保持打開並且多個消息來回傳遞的其他地方。 #3是最簡單的一種,當您只想一次接收所有數據時(例如文件下載),該功能非常有用。

+0

這是正確答案! – hek2mgl

+0

如果根本沒有收到任何數據,它將只設置'EAGAIN',否則該調用將返回接收到的字節數,即使在非阻塞套接字上,也可能會少於接收到的字節數。 – alk

+0

對不起,如果我保持循環...在大量EAGAINS之後,它確實在一段時間後(在幾次循環迭代之後)讀取更多的數據。你是對的。但是,那麼服務器如何知道是否已收到完整的有效載荷數據,甚至沒有剩餘字節? – user5858

7

1394與MTU的常見大小差不多,特別是如果您通過VPN隧道傳輸(是嗎?)。

您不能期望讀取一次調用中的所有字節,數據包可能會根據網絡MTU分段。

+0

不。我沒有使用任何VPN或代理。 – user5858

+1

@ user5858:答案描述的正確事實涵蓋了使用TCP套接字的任何類型的傳輸。在PHP中,這至少適用於包裝系統調用'revc()'的'socket_recv()':http://man7.org/linux/man-pages/man2/recv.2.html數據也可能是被分割成任何大小的片段,最多1個字節。 – alk

+0

你的推理是正確的。 – user5858

0

我想知道如果你的websockets連接有問題。您上面引用的while循環看起來位於客戶端握手失敗的代碼的一部分,它位於if($client->getHandshake()) { ... } else { ... }else

據我可以告訴$ client是一個單獨的類,所以我看不出什麼類看起來像什麼或Client :: getHandshake()做什麼,但我猜它是一個getter保存websocket升級握手成功或失敗的布爾值。

如果我沒有問題,握手失敗,連接被客戶端關閉。從代碼中我可以看到,您使用的服務器代碼需要規範的版本13。你沒有提到你正在使用哪個客戶端庫,但其他服務器將接受除此服務器之外的其他版本。

請確保您的客戶端庫支持最新版本。

當服務器收到傳入連接並且傳輸失敗時,從服務器發送詳細輸出將對我提示的內容不正確有幫助。

3

就我這2分錢。 socket_recv可以在發生錯誤時返回false。它也可以在非阻塞IO中接收零(0)字節。

你在你的循環檢查應該是:

while(($bytes = socket_recv($resource, $r_data, 4000, MSG_DONTWAIT)) !== false) {} 

Altough我會檢查插座是否有錯誤也並添加一些usleep調用,以防止「CPU燒」。

$data = ''; 
$done = false; 
while(!$done) { 
    socket_clear_error($resource); 
    $bytes = @socket_recv($resource, $r_data, 4000, MSG_DONTWAIT); 

    $lastError = socket_last_error($resource); 

    if ($lastError != 11 && $lastError > 0) { 
     // something went wrong! do something 
     $done = true; 
    } 
    else if ($bytes === false) { 
     // something went wrong also! do something else 
     $done = true; 
    } 
    else if (intval($bytes) > 0) { 
     $data .= $r_data; 
    } 
    else { 
     usleep(2000); // prevent "CPU burn" 
    } 
} 
+1

您的邏輯是正確的。謝謝 – user5858

0

但是,是不是包含在else塊中的代碼部分?對我來說,其他的塊看起來像手搖沒有通過?

你能打印接收到的字節爲字符串嗎?

+0

不能。我怎樣才能讀取有效載荷?沒有握手? – user5858

0

我不認爲你的問題是正確的。根據源代碼,如果握手成功,則執行這部分代碼:

$data = ''; 

while (true) { 
    $ret = socket_recv($socket, $r_data, 4000, MSG_DONTWAIT); 
    if ($ret === false) { 
     $this->console("$myidentity socket_recv error"); 
     exit(0); 
    } 
    $data .= $r_data; 
    if (strlen($data) > 4000) { 
     print "breaking as data len is more than 4000\n"; 
     break; 
    } else { 
     print "curr datalen=" . strlen($data) . "\n"; 
    } 
} 

如果程序真正接觸到你,然後提供的代碼段這將是值得尋找到爲什麼握手失敗。

服務器類具有第三個參數verboseMode,當設置爲true時,將爲您提供詳細的調試日誌,瞭解究竟發生了什麼。

我們只是推測沒有調試日誌,但如果提供調試日誌,我們可以提出一個更好的建議。