Socket shutdown: примеры (PHP)

socket_shutdown: Управление каналами передачи данных
Раздел: Сокеты
socket_shutdown(resource socket [, int how]): bool
Описание функции socket_shutdown

Функция socket_shutdown останавливает передачу данных в одном или обоих направлениях по сокету. Она используется для корректного завершения сетевого взаимодействия, сигнализируя удаленной стороне о завершении отправки или получения данных, при этом сам сокет не закрывается.

Когда используется: При реализации протоколов, требующих явного уведомления о конце передачи (например, FTP), для graceful shutdown серверов или клиентов, при необходимости прекратить только отправку или только прием данных.

Синтаксис:
socket_shutdown(Socket $socket, int $mode = 2): bool

Аргументы:

  • $socket - экземпляр сокета, созданный socket_create или принятый socket_accept.
  • $mode (необязательный, по умолчанию 2) - определяет, какое направление передачи останавливается:
    • 0 (SHUT_RD): Запрещает дальнейшее чтение из сокета.
    • 1 (SHUT_WR): Запрещает дальнейшую запись в сокет. Это стандартный способ уведомить peer о завершении отправки.
    • 2 (SHUT_RDWR): Запрещает и чтение, и запись.

Функция возвращает true в случае успеха или false в случае ошибки.

Базовые примеры использования
Завершение только отправки данных (SHUT_WR)
<?php
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_connect($socket, '127.0.0.1', 8080);
socket_write($socket, "Final message");
// Сигнал о завершении отправки
$result = socket_shutdown($socket, 1);
var_dump($result); // bool(true)
// Можно еще читать ответ...
// socket_close($socket);
?>
bool(true)
Полное отключение сокета (SHUT_RDWR)
<?php
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_connect($socket, '127.0.0.1', 8080);
// Немедленное прекращение всех операций
$result = socket_shutdown($socket, 2);
var_dump($result);
// socket_write($socket, "test"); // Вызовет предупреждение
?>
bool(true)
Использование констант вместо чисел
<?php
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if (defined('SHUT_RDWR')) {
    $result = socket_shutdown($socket, SHUT_RDWR);
    echo "Использована константа SHUT_RDWR\n";
}
?>
Использована константа SHUT_RDWR
Похожие функции в PHP

socket_close - полностью закрывает и уничтожает ресурс сокета. В отличие от socket_shutdown, после закрытия сокет становится непригодным для любых операций. Обычно socket_shutdown вызывают перед socket_close для graceful завершения.

stream_socket_shutdown - аналог для потоков (stream), созданных через stream_socket_client/server. Принимает те же константы режима (STREAM_SHUT_RD, STREAM_SHUT_WR, STREAM_SHUT_RDWR). Работает на более высоком уровне абстракции.

fclose - закрывает файловый указатель, включая сокеты, открытые как потоки. Действует аналогично socket_close, а не поэтапному отключению.

Выбор функции зависит от API: для расширения Socket используйте socket_shutdown, для потоков - stream_socket_shutdown.

Аналоги в других языках программирования
Python: метод shutdown()
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('127.0.0.1', 8080))
s.send(b'data')
s.shutdown(socket.SHUT_WR)  # Останавливаем запись
# s.shutdown(socket.SHUT_RD)
# s.shutdown(socket.SHUT_RDWR)
s.close()
# Функция выполняется без возвращаемого значения в случае успеха
JavaScript (Node.js): socket.end() и socket.destroy()
const net = require('net');
const client = net.createConnection({ port: 8080 }, () => {
  client.write('data');
  // Аналог SHUT_WR: прекращает запись, но позволяет читать
  client.end(); // Отправляет FIN пакет
  // Принудительное закрытие (аналог SHUT_RDWR)
  // client.destroy();
});
// socket.end() - асинхронный, вызывает событие 'finish'

В Python метод shutdown() почти идентичен PHP. В Node.js нет прямого аналога: socket.end() выполняет SHUT_WR и закрывает сокет после отправки буфера, socket.destroy() - принудительное закрытие (SHUT_RDWR). MySQL не предоставляет функций для низкоуровневой работы с сокетами.

Типичные ошибки
Попытка отключения уже закрытого сокета
<?php
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_close($socket);
$result = @socket_shutdown($socket, 2); // Подавляем предупреждение
var_dump($result); // bool(false)
if ($result === false) {
    echo "Ошибка: " . socket_strerror(socket_last_error()) . "\n";
}
?>
bool(false)
Ошибка: Socket argument is not a valid socket resource
Неверный режим отключения
<?php
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
$result = @socket_shutdown($socket, 5); // Несуществующий режим
var_dump($result);
?>
bool(false)
Отключение неподключенного сокета
<?php
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
$result = @socket_shutdown($socket, 2);
var_dump($result);
?>
bool(false)

Все ошибки приводят к возврату false. Рекомендуется проверять результат и использовать socket_last_error() для диагностики.

Изменения в последних версиях PHP

В PHP 8.0 тип параметра $socket изменен с resource на Socket (объект). Теперь передача некорректного ресурса вызывает TypeError.

// PHP 7
$result = socket_shutdown(fopen('test.txt', 'r'), 2); // Предупреждение
// PHP 8
$result = socket_shutdown(fopen('test.txt', 'r'), 2); // TypeError

Других значимых изменений в поведении функции не было. Константы SHUT_RD, SHUT_WR, SHUT_RDWR доступны с ранних версий.

Расширенные примеры
Graceful shutdown TCP-сервера
Пример php
<?php
$server = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_bind($server, '0.0.0.0', 9000);
socket_listen($server);
$client = socket_accept($server);
// Диалог...
socket_write($client, "Server shutting down write\n");
// Прекращаем отправку данных клиенту
socket_shutdown($client, SHUT_WR);
// Но еще можем читать финальное сообщение от клиента
$final = socket_read($client, 1024);
echo "От клиента: $final";
// Теперь полностью закрываем соединение
socket_shutdown($client, SHUT_RDWR);
socket_close($client);
socket_close($server);
?>
Реализация полузакрытого соединения (Half-close)
Пример php
<?php
// Клиент отправляет запрос и сигнализирует о конце отправки,
// но ожидает большой ответ от сервера
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_connect($socket, 'example.com', 80);
$request = "GET /largefile HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n";
socket_write($socket, $request);
// Сигнал серверу, что клиент больше не будет отправлять данные
socket_shutdown($socket, SHUT_WR);
// Но продолжает читать ответ
$response = '';
while ($buf = socket_read($socket, 4096)) {
    $response .= $buf;
}
echo strlen($response) . " байт получено\n";
socket_close($socket);
?>
Обработка нескольких клиентов с поэтапным отключением
Пример php
<?php
$server = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_set_nonblock($server); // Неблокирующий режим
socket_bind($server, '0.0.0.0', 9001);
socket_listen($server);
$clients = [];
while (true) {
    $client = @socket_accept($server);
    if ($client) {
        $clients[] = $client;
        socket_write($client, "Welcome\n");
    }
    foreach ($clients as $i => $client) {
        // Читаем команду
        $cmd = socket_read($client, 1024, PHP_NORMAL_READ);
        if (trim($cmd) === 'QUIT') {
            // Graceful shutdown для этого клиента
            socket_shutdown($client, SHUT_WR);
            socket_shutdown($client, SHUT_RD);
            socket_close($client);
            unset($clients[$i]);
        }
    }
}
?>
Использование с UDP сокетами
Пример php
<?php
// Для UDP сокетов shutdown имеет ограниченный смысл,
// так как нет установленного соединения
$socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
socket_bind($socket, '127.0.0.1', 10000);
// Остановка чтения предотвратит получение новых датаграмм
socket_shutdown($socket, SHUT_RD);
// socket_recvfrom($socket, $buf, 1024, 0, $from, $port); // Не сработает
socket_close($socket);
?>

PHP socket_shutdown function comments

En
Socket shutdown Shuts down a socket for receiving, sending, or both