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

Использование socket_sendto для сетевого взаимодействия в PHP
Раздел: Сокеты
socket_sendto(resource socket, string buf, int len, int flags, string addr [, int port]): int

Основные сведения о socket_sendto

Назначение функции

socket_sendto — это низкоуровневая функция в PHP, предназначенная для отправки данных через сетевой сокет без установки соединения, преимущественно по протоколу UDP или для работы с RAW-сокетами. В отличие от функций для TCP, она не требует предварительного подключения к удаленному узлу.

Аргументы функции

Функция принимает шесть параметров:

  • $socket (Socket) — ресурс сокета, созданный функцией socket_create.
  • $data (string) — данные для отправки в виде строки.
  • $length (int) — количество байт для отправки из $data.
  • $flags (int) — битовая маска флагов отправки. MSG_EOR, MSG_OOB, MSG_DONTROUTE, MSG_CONFIRM и другие, определенные системой.
  • $address (string) — IP-адрес получателя.
  • $port (int|null) — порт получателя. Для RAW-сокетов может быть 0 или null.

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

Короткие примеры использования

Базовый пример отправки UDP пакета
<?php
// Создание UDP сокета
$socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
if ($socket === false) {
    echo "Ошибка создания сокета: " . socket_strerror(socket_last_error());
    exit;
}

$data = "Hello UDP!\n";
$len = strlen($data);
$flags = 0;
$address = '127.0.0.1';
$port = 12345;

$sent = socket_sendto($socket, $data, $len, $flags, $address, $port);

if ($sent !== false) {
    echo "Отправлено байт: $sent";
} else {
    echo "Ошибка отправки: " . socket_strerror(socket_last_error($socket));
}

socket_close($socket);
?>
Отправлено байт: 11
Использование флага MSG_DONTROUTE
<?php
$socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
$data = "Test data";
$sent = socket_sendto($socket, $data, strlen($data), MSG_DONTROUTE, '10.0.0.1', 9999);
if ($sent === false) {
    echo "Ошибка (возможно, маршрут недоступен): " . socket_strerror(socket_last_error($socket));
}
socket_close($socket);
?>
Ошибка (возможно, маршрут недоступен): Network is unreachable

Похожие функции в PHP

Используется для записи в потоковый сокет (TCP). Требует предварительного соединения через socket_connect или socket_accept. Не требует указания адреса и порта для каждого вызова.

Аналогична socket_sendto, но работает с уже подключенными сокетами. Применяется для TCP или подключенных UDP-сокетов.

stream_socket_sendto

Функция для потоков. Работает с сокетами, созданными через stream_socket_server/client. Поддерживает контексты потоков и таймауты.

socket_sendto предпочтительнее для сценариев, где адрес получателя может меняться для каждого пакета (например, UDP-сервер для ответов разным клиентам). Для постоянных TCP-соединений лучше подходят socket_write или socket_send.

Альтернативы в других языках

Python: socket.sendto
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sent = sock.sendto(b"Hello", ('127.0.0.1', 12345))
print(f"Отправлено: {sent}")
Отправлено: 5

Отличие: Python работает с объектами сокетов, а не ресурсами. Метод является частью объекта. В PHP функция является процедурной.

Node.js: dgram.send
const dgram = require('dgram');
const socket = dgram.createSocket('udp4');
socket.send('Hello', 12345, '127.0.0.1', (err) => {
    if (err) console.error(err);
    socket.close();
});

Отличие: Асинхронный API с callback-функцией. В PHP socket_sendto работает синхронно.

Go: net.DialUDP и WriteTo
package main
import "net"
func main() {
    conn, _ := net.DialUDP("udp", nil, &net.UDPAddr{IP: net.IPv4(127,0,0,1), Port: 12345})
    defer conn.Close()
    conn.Write([]byte("Hello"))
}

Отличие: Go использует интерфейс io.Writer. Требует явного создания объекта соединения.

Типичные ошибки

Отправка данных длиннее указанного размера
<?php
$socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
$data = "1234567890";
// Указана длина больше, чем реальная строка
$result = socket_sendto($socket, $data, 20, 0, '127.0.0.1', 9999);
if ($result === false) {
    echo "Ошибка: " . socket_strerror(socket_last_error($socket));
}
?>
Ошибка: Invalid argument
Использование TCP-сокета без соединения
<?php
// Создание TCP сокета, но соединение не устанавливается
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
$result = socket_sendto($socket, "data", 4, 0, '127.0.0.1', 80);
if ($result === false) {
    echo "Ошибка: " . socket_strerror(socket_last_error($socket));
}
?>
Ошибка: Socket is not connected
Некорректный IP-адрес
<?php
$socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
$result = socket_sendto($socket, "data", 4, 0, '999.999.999.999', 80);
if ($result === false) {
    echo "Ошибка: " . socket_strerror(socket_last_error($socket));
}
?>
Ошибка: Invalid argument

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

В PHP 8.0 тип параметра $socket изменен с ресурса (resource) на объект класса Socket. Код, который использовал is_resource($socket), требует обновления.

<?php
// PHP 7.x
$socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
var_dump(is_resource($socket)); // bool(true)

// PHP 8.0+
$socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
var_dump($socket instanceof \Socket); // bool(true)
?>
bool(true)
bool(true)

Аргумент $port стал необязательным (допускается null) для совместимости с RAW-сокетами, где номер порта не используется.

Расширенные примеры

Отправка DNS запроса через UDP
Пример php
<?php
$socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);

// Простой DNS запрос для example.com (упрощенный, без полного разбора)
$dnsRequest = "\xaa\xaa\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x07example\x03com\x00\x00\x01\x00\x01";

$sent = socket_sendto($socket, $dnsRequest, strlen($dnsRequest), 0, '8.8.8.8', 53);
if ($sent !== false) {
    echo "DNS запрос отправлен. Ждем ответ...\n";
    // Чтение ответа
    socket_recvfrom($socket, $buffer, 512, 0, $from, $port);
    echo "Ответ от $from:$port, длина: " . strlen($buffer) . " байт";
} else {
    echo "Ошибка отправки DNS запроса";
}
socket_close($socket);
?>
DNS запрос отправлен. Ждем ответ...
Ответ от 8.8.8.8:53, длина: 72 байт
Отправка ICMP Echo запроса (ping) через RAW сокет (требует прав root)
Пример php
<?php
// Только для Linux, требует запуск с правами root
$socket = socket_create(AF_INET, SOCK_RAW, getprotobyname('icmp'));
if (!$socket) die("Не удалось создать RAW сокет\n");

// Простейший ICMP Echo запрос (тип 8, код 0)
$checksum = 0;
$icmpPacket = pack('CCnnn', 8, 0, $checksum, 1, 1);

// Рассчет контрольной суммы
$sum = 0;
for ($i = 0; $i < strlen($icmpPacket); $i += 2) {
    $sum += ord($icmpPacket[$i]) << 8 | ord($icmpPacket[$i+1]);
}
$sum = ($sum >> 16) + ($sum & 0xffff);
$sum += $sum >> 16;
$checksum = ~$sum & 0xffff;

$icmpPacket = pack('CCnnn', 8, 0, $checksum, 1, 1);

$sent = socket_sendto($socket, $icmpPacket, strlen($icmpPacket), 0, '8.8.8.8', 0); // Порт 0 для ICMP
if ($sent) {
    echo "ICMP пакет отправлен на 8.8.8.8\n";
}
socket_close($socket);
?>
ICMP пакет отправлен на 8.8.8.8
Многоадресная рассылка (Multicast) UDP
Пример php
<?php
$socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);

// Включение опции повторного использования адреса
socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, 1);

// Привязка сокета к порту
socket_bind($socket, '0.0.0.0', 54321);

// Включение членства в multicast группе
$multicastIp = '239.255.255.250';
$interface = '0.0.0.0';
$mreq = pack('I', ip2long($multicastIp)) . pack('I', ip2long($interface));
socket_set_option($socket, IPPROTO_IP, MCAST_JOIN_GROUP, $mreq);

// Отправка сообщения в multicast группу
$data = "Multicast test message";
$sent = socket_sendto($socket, $data, strlen($data), 0, $multicastIp, 54321);
if ($sent) {
    echo "Multicast сообщение отправлено в группу $multicastIp\n";
}
socket_close($socket);
?>
Multicast сообщение отправлено в группу 239.255.255.250

PHP socket_sendto function comments

En
Socket sendto Sends a message to a socket, whether it is connected or not