- u_async.c: process immediate_queue BEFORE epoll_wait in uasync_poll - stcp_link.c: defensive NULL etcp_conn/etcp_link in stcp_link_close - utun_instance.c: move stcp_server_list_destroy_all to Phase J (before ETCP), add deferred drain in both Phase J and L - stcp.h/c: STCP_HS_ENC_CLIENT 6→38, STCP_HS_ENC_SERVER 7→39, exchange ed25519_pubkey in both handshake directions - etcp_connections.c: copy peer_ed25519 from STCP to ETCP_CONN in etcp_link_enter_ready_tcp; guard link->etcp in tcp_link_close_cb - chat_sync.c: fix lk->conn NULL dereference for TCP links - test_stcp.c: update stcp_server_create/stcp_client_connect callsites - tools/chat_tcp_test: change transport udp→tcp
100 lines
5.4 KiB
Markdown
100 lines
5.4 KiB
Markdown
# 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` |
|