當前位置: 首頁> 最新文章列表> socket_recv 搭配socket_select 怎麼實現非阻塞接收?附代碼

socket_recv 搭配socket_select 怎麼實現非阻塞接收?附代碼

M66 2025-06-22

在PHP 中, socket_recvsocket_select函數是處理網絡編程中非常常用的工具,尤其是當你需要進行非阻塞式的I/O 操作時。使用這兩個函數,你可以避免因等待網絡數據而導致的阻塞,從而提高程序的效率和響應能力。本文將詳細介紹如何利用這兩個函數實現非阻塞接收。

1. 什麼是非阻塞接收?

在傳統的阻塞式接收中,當你使用socket_recv等函數接收數據時,程序會停在那一行代碼,直到有數據到達或者發生超時才會繼續執行。這意味著,如果沒有數據到達,程序就會“卡住”,無法進行其他操作。

而非阻塞接收則是指程序不會在等待數據的過程中停止執行,它會在接收數據時進行檢查,若沒有數據,則可以繼續進行其他操作。這樣一來,程序在進行I/O 操作時就不會因為等待數據而阻塞。

2. 使用socket_select函數

socket_select函數提供了一種檢查多個socket 是否就緒的方式,它可以幫助我們避免阻塞式的等待。當我們調用socket_select時,它會檢查給定的socket 是否有數據可讀,若有,則返回相應的socket 對象。你可以通過該函數在多個socket 上執行非阻塞接收操作。

socket_select的基本語法:

 <span><span><span class="hljs-keyword">int</span></span><span> </span><span><span class="hljs-title function_ invoke__">socket_select</span></span><span> ( </span><span><span class="hljs-keyword">array</span></span><span> &amp;</span><span><span class="hljs-variable">$read</span></span><span>, </span><span><span class="hljs-keyword">array</span></span><span> &amp;</span><span><span class="hljs-variable">$write</span></span><span>, </span><span><span class="hljs-keyword">array</span></span><span> &amp;</span><span><span class="hljs-variable">$except</span></span><span>, </span><span><span class="hljs-keyword">int</span></span><span> </span><span><span class="hljs-variable">$tv_sec</span></span><span>, </span><span><span class="hljs-keyword">int</span></span><span> </span><span><span class="hljs-variable">$tv_usec</span></span><span> )
</span></span>
  • $read :一個數組,包含所有需要檢測是否可讀的socket。

  • $write :一個數組,包含所有需要檢測是否可寫的socket。

  • $except :一個數組,包含所有需要檢測是否有異常的socket。

  • $tv_sec$tv_usec :指定超時時間(秒和微秒)。若設為null ,則為無限等待。

socket_recv的基本語法:

 <span><span><span class="hljs-keyword">int</span></span><span> </span><span><span class="hljs-title function_ invoke__">socket_recv</span></span><span> ( resource </span><span><span class="hljs-variable">$socket</span></span><span> , </span><span><span class="hljs-keyword">string</span></span><span> &amp;</span><span><span class="hljs-variable">$buf</span></span><span> , </span><span><span class="hljs-keyword">int</span></span><span> </span><span><span class="hljs-variable">$length</span></span><span> , </span><span><span class="hljs-keyword">int</span></span><span> </span><span><span class="hljs-variable">$flags</span></span><span> )
</span></span>
  • $socket :要接收數據的socket 資源。

  • $buf :接收到的數據存儲在這個變量中。

  • $length :要接收的最大字節數。

  • $flags :控制接收操作的標誌,通常為0。

3. 示例代碼

以下是一個使用socket_recv搭配socket_select實現非阻塞接收的完整示例:

 <span><span><span class="hljs-meta">&lt;?php</span></span><span>
</span><span><span class="hljs-comment">// 創建一個 TCP socket</span></span><span>
</span><span><span class="hljs-variable">$server</span></span><span> = </span><span><span class="hljs-title function_ invoke__">socket_create</span></span><span>(AF_INET, SOCK_STREAM, SOL_TCP);

</span><span><span class="hljs-comment">// 設置服務器地址和端口</span></span><span>
</span><span><span class="hljs-variable">$address</span></span><span> = </span><span><span class="hljs-string">'127.0.0.1'</span></span><span>;
</span><span><span class="hljs-variable">$port</span></span><span> = </span><span><span class="hljs-number">12345</span></span><span>;

</span><span><span class="hljs-comment">// 綁定 socket</span></span><span>
</span><span><span class="hljs-title function_ invoke__">socket_bind</span></span><span>(</span><span><span class="hljs-variable">$server</span></span><span>, </span><span><span class="hljs-variable">$address</span></span><span>, </span><span><span class="hljs-variable">$port</span></span><span>);

</span><span><span class="hljs-comment">// 開始監聽</span></span><span>
</span><span><span class="hljs-title function_ invoke__">socket_listen</span></span><span>(</span><span><span class="hljs-variable">$server</span></span><span>);

</span><span><span class="hljs-comment">// 設置超時時間</span></span><span>
</span><span><span class="hljs-variable">$timeout</span></span><span> = </span><span><span class="hljs-number">10</span></span><span>;  </span><span><span class="hljs-comment">// 超時 10 秒</span></span><span>
</span><span><span class="hljs-variable">$read</span></span><span> = [</span><span><span class="hljs-variable">$server</span></span><span>];  </span><span><span class="hljs-comment">// 用於檢測是否有連接</span></span><span>
</span><span><span class="hljs-variable">$write</span></span><span> = </span><span><span class="hljs-variable">$except</span></span><span> = [];

</span><span><span class="hljs-comment">// 進入循環,等待客戶端連接</span></span><span>
</span><span><span class="hljs-keyword">while</span></span><span> (</span><span><span class="hljs-literal">true</span></span><span>) {
    </span><span><span class="hljs-comment">// 使用 socket_select 來檢測是否有新連接</span></span><span>
    </span><span><span class="hljs-variable">$changed</span></span><span> = </span><span><span class="hljs-title function_ invoke__">socket_select</span></span><span>(</span><span><span class="hljs-variable">$read</span></span><span>, </span><span><span class="hljs-variable">$write</span></span><span>, </span><span><span class="hljs-variable">$except</span></span><span>, </span><span><span class="hljs-variable">$timeout</span></span><span>);

    </span><span><span class="hljs-keyword">if</span></span><span> (</span><span><span class="hljs-variable">$changed</span></span><span> === </span><span><span class="hljs-literal">false</span></span><span>) {
        </span><span><span class="hljs-keyword">echo</span></span><span> </span><span><span class="hljs-string">"socket_select failed\n"</span></span><span>;
        </span><span><span class="hljs-keyword">break</span></span><span>;
    }

    </span><span><span class="hljs-keyword">if</span></span><span> (</span><span><span class="hljs-variable">$changed</span></span><span> &gt; </span><span><span class="hljs-number">0</span></span><span>) {
        </span><span><span class="hljs-comment">// 檢查是否有新的客戶端連接</span></span><span>
        </span><span><span class="hljs-keyword">if</span></span><span> (</span><span><span class="hljs-title function_ invoke__">in_array</span></span><span>(</span><span><span class="hljs-variable">$server</span></span><span>, </span><span><span class="hljs-variable">$read</span></span><span>)) {
            </span><span><span class="hljs-variable">$client</span></span><span> = </span><span><span class="hljs-title function_ invoke__">socket_accept</span></span><span>(</span><span><span class="hljs-variable">$server</span></span><span>);
            </span><span><span class="hljs-variable">$read</span></span><span>[] = </span><span><span class="hljs-variable">$client</span></span><span>;  </span><span><span class="hljs-comment">// 新連接加入讀取數組</span></span><span>
            </span><span><span class="hljs-keyword">echo</span></span><span> </span><span><span class="hljs-string">"Client connected\n"</span></span><span>;
        }

        </span><span><span class="hljs-comment">// 遍歷所有連接的 socket,檢查是否有數據可讀</span></span><span>
        </span><span><span class="hljs-keyword">foreach</span></span><span> (</span><span><span class="hljs-variable">$read</span></span><span> </span><span><span class="hljs-keyword">as</span></span><span> </span><span><span class="hljs-variable">$sock</span></span><span>) {
            </span><span><span class="hljs-keyword">if</span></span><span> (</span><span><span class="hljs-variable">$sock</span></span><span> !== </span><span><span class="hljs-variable">$server</span></span><span>) {
                </span><span><span class="hljs-variable">$data</span></span><span> = </span><span><span class="hljs-string">''</span></span><span>;
                </span><span><span class="hljs-variable">$bytes_received</span></span><span> = </span><span><span class="hljs-title function_ invoke__">socket_recv</span></span><span>(</span><span><span class="hljs-variable">$sock</span></span><span>, </span><span><span class="hljs-variable">$data</span></span><span>, </span><span><span class="hljs-number">1024</span></span><span>, MSG_DONTWAIT);
                </span><span><span class="hljs-keyword">if</span></span><span> (</span><span><span class="hljs-variable">$bytes_received</span></span><span> === </span><span><span class="hljs-number">0</span></span><span>) {
                    </span><span><span class="hljs-keyword">echo</span></span><span> </span><span><span class="hljs-string">"Client disconnected\n"</span></span><span>;
                    </span><span><span class="hljs-title function_ invoke__">socket_close</span></span><span>(</span><span><span class="hljs-variable">$sock</span></span><span>);
                    </span><span><span class="hljs-variable">$read</span></span><span> = </span><span><span class="hljs-title function_ invoke__">array_diff</span></span><span>(</span><span><span class="hljs-variable">$read</span></span><span>, [</span><span><span class="hljs-variable">$sock</span></span><span>]);  </span><span><span class="hljs-comment">// 從讀取列表中移除該 socket</span></span><span>
                } </span><span><span class="hljs-keyword">elseif</span></span><span> (</span><span><span class="hljs-variable">$bytes_received</span></span><span> &gt; </span><span><span class="hljs-number">0</span></span><span>) {
                    </span><span><span class="hljs-keyword">echo</span></span><span> </span><span><span class="hljs-string">"Received data: <span class="hljs-subst">$data</span></span></span><span>\n";
                }
            }
        }
    }

    </span><span><span class="hljs-comment">// 這裡可以添加更多的處理邏輯,比如檢查寫入操作、超時处理等</span></span><span>
}

</span><span><span class="hljs-title function_ invoke__">socket_close</span></span><span>(</span><span><span class="hljs-variable">$server</span></span><span>);
</span><span><span class="hljs-meta">?&gt;</span></span><span>
</span></span>

代碼解讀:

  1. 創建和綁定socket :使用socket_create創建一個TCP socket,並用socket_bind綁定到指定的地址和端口。

  2. socket_select 監聽多個socket :在socket_select中傳入監聽的$read數組, $server socket 會被用來接受新的客戶端連接。 $timeout參數設置為10 秒,即在10 秒內,如果沒有數據傳入或新連接, socket_select會返回。

  3. 接收數據:如果有數據可讀,通過socket_recv進行非阻塞接收。如果客戶端斷開連接,關閉對應的socket,並從$read數組中移除。

4. 注意事項

  • 非阻塞模式:在本例中,我們使用了MSG_DONTWAIT標誌來實現非阻塞接收,意味著如果沒有數據可讀, socket_recv不會阻塞程序。

  • socket_select的使用socket_select用於檢查多個socket 的狀態。在非阻塞模式下,它可以幫助你避免一直等待某個socket 的數據,而是可以在有數據時立即處理。

  • 內存管理:使用socket_select時,務必確保從$read數組中移除已經關閉的socket,避免內存洩漏和無效的socket 檢測。

5. 總結

通過結合使用socket_recvsocket_select ,你可以非常方便地實現非阻塞接收。這種方式不僅可以避免程序因等待數據而阻塞,同時也能夠高效地處理多個socket 連接,實現更高效的網絡通信。

非阻塞接收是一個重要的技術,尤其在構建高並發、低延遲的網絡服務時非常有用。通過對PHP socket 編程的靈活運用,可以提高應用程序的性能和可擴展性。