在使用PHP 構建基於Socket 的服務器應用時, socket_accept()是非常核心的函數之一,它用於接受來自客戶端的連接請求。但如果在連接管理上處理不當,很容易造成資源洩露,甚至導致服務異常終止。本文將探討如何這些連接,確保資源能夠及時釋放,並保持服務的穩定性和可靠性。
在理解優雅關閉之前,先看一個最基本的Socket 服務端示例:
$address = '0.0.0.0';
$port = 12345;
$serverSocket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_bind($serverSocket, $address, $port);
socket_listen($serverSocket);
while (true) {
$clientSocket = socket_accept($serverSocket);
// 簡單響應
socket_write($clientSocket, "Hello client!\n");
socket_close($clientSocket);
}
在這個例子中,服務器持續運行,接收連接並響應。雖然代碼可以運行,但在高並發或長期運行時,如果不合理地關閉連接和處理異常,可能會出現如下問題:
文件描述符洩露(超出系統資源限制)
連接未關閉導致TIME_WAIT 堆積
進程被迫終止(如遇到SIGINT)
PHP 的socket_accept()是一個阻塞函數,意味著它會在沒有連接請求時一直等待。這種行為在退出服務、重啟或終止時,會阻止正常關閉流程。如果沒有正確釋放資源,可能會導致:
socket_close() 沒有執行:連接未關閉,系統資源未釋放。
腳本異常退出:導致已創建的連接沒有被清理,遺留zombie socket。
信號中斷(如Ctrl+C):沒有進行善後處理。
雖然socket_*系列提供了更底層的控制,但PHP 的stream_socket_server()更容易實現超時控制和非阻塞行為,適合簡化代碼。
不過如果堅持使用socket_* ,以下方式也能實現優雅關閉。
通過socket_set_nonblock()可以避免阻塞socket_accept() :
socket_set_nonblock($serverSocket);
這樣我們可以用循環檢查是否有連接,同時監聽終止信號:
declare(ticks = 1);
$running = true;
pcntl_signal(SIGINT, function() use (&$running) {
echo "捕獲 SIGINT,準備退出...\n";
$running = false;
});
while ($running) {
$clientSocket = @socket_accept($serverSocket);
if ($clientSocket !== false) {
socket_write($clientSocket, "Hello client!\n");
socket_close($clientSocket);
} else {
usleep(100000); // 避免 CPU 佔用過高
}
}
socket_close($serverSocket);
如果你需要在多個socket 上監聽,可以結合socket_select()來實現事件驅動模型:
$readSockets = [$serverSocket];
$write = $except = null;
if (socket_select($readSockets, $write, $except, 1) > 0) {
foreach ($readSockets as $sock) {
if ($sock === $serverSocket) {
$clientSocket = socket_accept($serverSocket);
if ($clientSocket !== false) {
socket_write($clientSocket, "Hi\n");
socket_close($clientSocket);
}
}
}
}
socket_select()能夠設置超時,也可以及時響應中斷信號,適合實現更健壯的服務器程序。
確保每一個socket_create()和socket_accept()得到的資源都配對執行socket_close() 。一個良好結構的socket 服務生命週期如下:
初始化socket
設置非阻塞/ select
主循環中接收連接,處理請求
捕獲終止信號並退出循環
清理所有打開的連接
最後關閉監聽socket
以下是一個實現了信號捕獲、非阻塞模式以及資源清理的完整示例:
declare(ticks = 1);
$serverSocket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_bind($serverSocket, '0.0.0.0', 8000);
socket_listen($serverSocket);
socket_set_nonblock($serverSocket);
$running = true;
pcntl_signal(SIGINT, function() use (&$running) {
echo "收到終止信號,準備關閉服務...\n";
$running = false;
});
while ($running) {
$clientSocket = @socket_accept($serverSocket);
if ($clientSocket) {
socket_write($clientSocket, "歡迎訪問 m66.net!\n");
socket_close($clientSocket);
} else {
usleep(100000);
}
}
socket_close($serverSocket);
echo "服務已優雅關閉。\n";
PHP 提供的socket 編程接口雖然功能強大,但也帶來了資源管理的複雜性。通過設置非阻塞模式、監聽終止信號、使用socket_select()等方法,可以在PHP 中實現“優雅關閉”,有效避免資源洩露和異常終止的問題。在構建長期運行的服務時,這些手段尤為重要。
記住:始終釋放你打開的每一個資源,包括socket。