diff --git a/lib/socket_compat.h b/lib/socket_compat.h index b6b7bf05..a0b14ff7 100644 --- a/lib/socket_compat.h +++ b/lib/socket_compat.h @@ -2,6 +2,8 @@ * Единые socket_t, коды ошибок и wrappers сокетов POSIX/Windows (включая MSYS2 UCRT64). * platform_init/cleanup обслуживают Winsock; UDP/nonblocking/options/sendto/recvfrom — операции ОС. * Созданные сокеты закрывает владелец через socket_close_wrapper; event loop находится в u_async.h. + * Использование: platform_init → create_udp → set_nonblocking/options → sendto/recvfrom → close → cleanup. + * До регистрации в uasync вызывающий должен включить nonblocking. Winsock init/cleanup согласовать между потоками. */ #ifndef SOCKET_COMPAT_H @@ -45,12 +47,14 @@ extern "C" { #define ERR_INTR EINTR #endif -// Platform initialization/cleanup +// POSIX: 0 без системной инициализации. Windows: WSAStartup при первом init, счётчик вложенности; 0 / -1. int socket_platform_init(void); +// Парный cleanup: Windows уменьшает счётчик и вызывает WSACleanup на нуле; POSIX ничего не освобождает. void socket_platform_cleanup(void); -// Socket operations +// UDP-сокет для family (обычно AF_INET/AF_INET6); SOCKET_INVALID при ошибке. Владение передаётся вызывающему. socket_t socket_create_udp(int family); +// Включает nonblocking: 0 — успех, -1 — ошибка. int socket_set_nonblocking(socket_t sock); /* Закрыть сокет средствами ОС; на POSIX sock=0 оставляет дескриптор открытым и возвращает 0. */ int socket_close_wrapper(socket_t sock); @@ -62,22 +66,27 @@ static inline int socket_get_error(void) { #endif } -// Socket options +// Пробует установить оба буфера SO_SNDBUF/SO_RCVBUF: 0 при успехе обоих, -1 при любой ошибке; отката нет. int socket_set_buffers(socket_t sock, int sndbuf, int rcvbuf); +// Устанавливает SO_REUSEADDR в значение reuse; 0 / -1. int socket_set_reuseaddr(socket_t sock, int reuse); +// SO_BINDTODEVICE для строки ifname (не NULL); 0 / -1. Без поддержки опции, в том числе Windows, возвращает -1. int socket_bind_to_device(socket_t sock, const char* ifname); +// SO_MARK в значение mark; 0 / -1. Без поддержки опции, в том числе Windows, возвращает -1. int socket_set_mark(socket_t sock, int mark); -// I/O operations +// Один sendto/recvfrom с flags=0: число байт или SOCKET_ERROR_CODE; errno/WSA-код читать через socket_get_error. +// буферами владеет вызывающий; для Windows len должен помещаться в int. dest_len — размер адреса назначения; +// src_len при приёме — входная ёмкость/выходная длина адреса (может быть NULL по правилам recvfrom ОС). ssize_t socket_sendto(socket_t sock, const void* buf, size_t len, const struct sockaddr* dest, socklen_t dest_len); ssize_t socket_recvfrom(socket_t sock, void* buf, size_t len, struct sockaddr* src, socklen_t* src_len); -// Utility +// Заимствованный текст ошибки: Windows — один статический буфер (перезаписывается следующим вызовом), POSIX — strerror. const char* socket_strerror(int err); -// взять порт из sockaddr_storage +// IPv4/IPv6-порт в host byte order; 0 при NULL или другом семействе. uint16_t ss_get_port(const struct sockaddr_storage *addr); // Фактический IPv4/IPv6-порт уже привязанного сокета; 0 при ошибке. uint16_t socket_get_bound_port(socket_t sock); diff --git a/lib/socket_compat_doc.md b/lib/socket_compat_doc.md deleted file mode 100644 index df3e3d31..00000000 --- a/lib/socket_compat_doc.md +++ /dev/null @@ -1,99 +0,0 @@ -# socket_compat — Cross-platform socket abstraction - -## 1. Назначение -Унифицирует работу с UDP-сокетами между POSIX и Windows (MSYS2 UCRT64). Скрывает различия в типах дескрипторов (`int` vs `SOCKET`), инициализации подсистемы (WSAStartup/WSACleanup), кодах ошибок и сигнатурах системных вызовов. - -## 2. Как пользоваться - -### Инициализация (однократно при старте) -```c -if (socket_platform_init() != 0) { /* фатальная ошибка */ } -// ... работа с сокетами ... -socket_platform_cleanup(); // при завершении -``` -На Windows WSAStartup/WSACleanup с refcount — можно вызывать init/cleanup вложенно. - -### Создание и настройка сокета -```c -socket_t sock = socket_create_udp(AF_INET); // или AF_INET6 -if (sock == SOCKET_INVALID) { /* ошибка */ } - -socket_set_nonblocking(sock); // обязательно для u_async -socket_set_reuseaddr(sock, 1); -socket_set_buffers(sock, 256*1024, 256*1024); // SO_SNDBUF/SO_RCVBUF -socket_bind_to_device(sock, "eth0"); // только Linux, SO_BINDTODEVICE -socket_set_mark(sock, 42); // только Linux, SO_MARK -``` - -### Отправка и приём -```c -ssize_t sent = socket_sendto(sock, buf, len, (struct sockaddr*)&addr, addr_len); -ssize_t recv = socket_recvfrom(sock, buf, sizeof(buf), (struct sockaddr*)&src, &src_len); - -uint16_t port = ss_get_port(&src); // порт из sockaddr_storage -``` - -### Обработка ошибок -```c -if (sent == SOCKET_ERROR_CODE) { - int err = socket_get_error(); - if (err == ERR_WOULDBLOCK) { /* нормально для неблокирующего */ } - // на POSIX ERR_WOULDBLOCK == EWOULDBLOCK (может совпадать с EAGAIN) - DEBUG_ERROR(..., "%s", socket_strerror(err)); -} -``` - -### Закрытие -```c -socket_close_wrapper(sock); // на POSIX: close() с защитой от fd 0 -``` - -### Ключевые нюансы -- **Всегда вызывать `socket_platform_init()` перед работой** — иначе на Windows сокеты не будут работать -- **Всегда `socket_set_nonblocking()`** — u_async требует неблокирующих сокетов -- `socket_t` — на POSIX `int`, на Windows `SOCKET` (unsigned) -- `SOCKET_INVALID` — константа для невалидного сокета (`-1` на POSIX, `INVALID_SOCKET` на Windows) -- `SOCKET_ERROR_CODE` — код ошибки сокетных вызовов (`-1` на POSIX, `SOCKET_ERROR` на Windows) -- `socket_strerror()` на Windows использует статический буфер — не thread-safe для параллельного использования -- `socket_bind_to_device` и `socket_set_mark` — только Linux, на остальных платформах возвращают -1 с DEBUG-сообщением -- `socket_close_wrapper` на POSIX не закроет fd 0 (защита) - -## 3. API - -### Типы и константы -| Имя | Назначение | -|-----|------------| -| `socket_t` | Кроссплатформенный тип дескриптора сокета (`int` / `SOCKET`) | -| `SOCKET_INVALID` | Невалидный сокет (`-1` / `INVALID_SOCKET`) | -| `SOCKET_ERROR_CODE` | Код ошибки сокетного вызова (`-1` / `SOCKET_ERROR`) | -| `ERR_WOULDBLOCK` / `ERR_AGAIN` / `ERR_INTR` | Кроссплатформенные коды ошибок | - -### Инициализация -| Функция | Назначение | -|---------|------------| -| `socket_platform_init()` | Инициализация сокетной подсистемы (WSAStartup на Windows). 0 — успех | -| `socket_platform_cleanup()` | Деинициализация (WSACleanup на Windows), refcount | - -### Создание и настройка -| Функция | Назначение | -|---------|------------| -| `socket_create_udp(family)` | Создать UDP-сокет (AF_INET или AF_INET6) | -| `socket_set_nonblocking(sock)` | Перевести в неблокирующий режим | -| `socket_set_buffers(sock, snd, rcv)` | Установить SO_SNDBUF и SO_RCVBUF | -| `socket_set_reuseaddr(sock, reuse)` | Установить SO_REUSEADDR | -| `socket_bind_to_device(sock, ifname)` | Привязать к интерфейсу (Linux SO_BINDTODEVICE) | -| `socket_set_mark(sock, mark)` | Установить SO_MARK (Linux) | - -### I/O -| Функция | Назначение | -|---------|------------| -| `socket_sendto(sock, buf, len, dest, dest_len)` | Отправить UDP-датаграмму | -| `socket_recvfrom(sock, buf, len, src, src_len)` | Принять UDP-датаграмму | - -### Утилиты -| Функция | Назначение | -|---------|------------| -| `socket_get_error()` | Текущий код ошибки (inline: `WSAGetLastError()` / `errno`) | -| `socket_strerror(err)` | Текстовое описание ошибки (Windows: статический буфер, не thread-safe) | -| `socket_close_wrapper(sock)` | Закрыть сокет (POSIX: защита от закрытия fd 0) | -| `ss_get_port(addr)` | Извлечь порт в host byte order из `sockaddr_storage` |