在PHP 中, socket_recv和socket_select函數是處理網絡編程中非常常用的工具,尤其是當你需要進行非阻塞式的I/O 操作時。使用這兩個函數,你可以避免因等待網絡數據而導致的阻塞,從而提高程序的效率和響應能力。本文將詳細介紹如何利用這兩個函數實現非阻塞接收。
在傳統的阻塞式接收中,當你使用socket_recv等函數接收數據時,程序會停在那一行代碼,直到有數據到達或者發生超時才會繼續執行。這意味著,如果沒有數據到達,程序就會“卡住”,無法進行其他操作。
而非阻塞接收則是指程序不會在等待數據的過程中停止執行,它會在接收數據時進行檢查,若沒有數據,則可以繼續進行其他操作。這樣一來,程序在進行I/O 操作時就不會因為等待數據而阻塞。
socket_select函數提供了一種檢查多個socket 是否就緒的方式,它可以幫助我們避免阻塞式的等待。當我們調用socket_select時,它會檢查給定的socket 是否有數據可讀,若有,則返回相應的socket 對象。你可以通過該函數在多個socket 上執行非阻塞接收操作。
<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> &</span><span><span class="hljs-variable">$read</span></span><span>, </span><span><span class="hljs-keyword">array</span></span><span> &</span><span><span class="hljs-variable">$write</span></span><span>, </span><span><span class="hljs-keyword">array</span></span><span> &</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 ,則為無限等待。
<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> &</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。
以下是一個使用socket_recv搭配socket_select實現非阻塞接收的完整示例:
<span><span><span class="hljs-meta"><?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> > </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> > </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">?></span></span><span>
</span></span>
創建和綁定socket :使用socket_create創建一個TCP socket,並用socket_bind綁定到指定的地址和端口。
socket_select 監聽多個socket :在socket_select中傳入監聽的$read數組, $server socket 會被用來接受新的客戶端連接。 $timeout參數設置為10 秒,即在10 秒內,如果沒有數據傳入或新連接, socket_select會返回。
接收數據:如果有數據可讀,通過socket_recv進行非阻塞接收。如果客戶端斷開連接,關閉對應的socket,並從$read數組中移除。
非阻塞模式:在本例中,我們使用了MSG_DONTWAIT標誌來實現非阻塞接收,意味著如果沒有數據可讀, socket_recv不會阻塞程序。
socket_select的使用: socket_select用於檢查多個socket 的狀態。在非阻塞模式下,它可以幫助你避免一直等待某個socket 的數據,而是可以在有數據時立即處理。
內存管理:使用socket_select時,務必確保從$read數組中移除已經關閉的socket,避免內存洩漏和無效的socket 檢測。
通過結合使用socket_recv和socket_select ,你可以非常方便地實現非阻塞接收。這種方式不僅可以避免程序因等待數據而阻塞,同時也能夠高效地處理多個socket 連接,實現更高效的網絡通信。
非阻塞接收是一個重要的技術,尤其在構建高並發、低延遲的網絡服務時非常有用。通過對PHP socket 編程的靈活運用,可以提高應用程序的性能和可擴展性。