Files
utun2/lib/tcp_io_doc.md
T
evgeny 38c9f229bc stcp: ed25519 pubkey exchange in handshake, fix instance destroy segfault, fix chat_tcp_test
- 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
2026-08-07 00:48:27 +03:00

156 lines
8.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# tcp_io — Управление TCP-соединением на uasync + ll_queue
## 1. Назначение
Асинхронный TCP-коннектор, построенный на event loop `uasync` и lock-free очередях `ll_queue`.
Одна структура `struct tcp_conn` = одно TCP-соединение.
Используется в SOCKS/HTTP-прокси, STCP, TCP-прокси и тестах.
**Ключевые свойства:**
- Чтение, запись, ошибки и connect — через `uasync` (epoll/kqueue/poll).
- Два независимых `ll_queue`: `read_queue` (входящие данные) и `write_queue` (исходящие).
- Два memory pool: `entry_pool` (struct ll_entry) и `data_pool` (буферы), всё выделение на горячем пути через пулы.
- Backpressure на чтение: high_water/low_water + `queue_waiter_wait`, EPOLLIN вкл/выкл по уровню заполнения очереди.
- Backpressure на запись: порог 32 записи в write_queue + `queue_waiter_wait`, EPOLLOUT вкл/выкл при частичной отправке.
- Graceful shutdown: FIN (shutdown SHUT_WR) и CLOSE (close сокета) — оба как сентинелы в write_queue, все предшествующие данные гарантированно отправлены.
## 2. Как пользоваться
### Создание соединения
```c
struct tcp_conn* tc = tcp_conn_create(
ua, sock,
1500, // entry_data_size — размер буфера для одного recv
8192, // write_chunk_size — размер буфера для одной send-операции
32, // read_high_water — порог приостановки EPOLLIN
8, // read_low_water — порог возобновления EPOLLIN
0, // rcvbuf_size — размер буфера сокета (0 = не менять)
on_fin_cb, on_error_cb, arg);
```
После создания нужно установить callback на read_queue и включить отложенную обработку:
```c
queue_set_callback(tc->read_queue, on_read_cb, my_conn);
queue_set_waiter_defer(tc->read_queue, 1);
```
### Чтение данных (в on_read_cb)
```c
static void on_read_cb(struct ll_queue* q, void* arg) {
struct my_conn* c = (struct my_conn*)arg;
struct ll_entry* e = queue_data_get(q);
if (!e) { queue_resume_callback(q); return; }
// обработать e->dgram (e->len байт)
queue_entry_free(e);
queue_resume_callback(q); // обязательно!
}
```
### Запись данных
Пользователь напрямую кладёт entry в `write_queue`, отправка авто (deferred callback сам шлёт когда сокет готов):
```c
struct ll_entry* e = queue_entry_new_from_pool(tc->entry_pool);
uint8_t* buf = memory_pool_alloc(tc->data_pool);
memcpy(buf, data, len);
e->dgram = buf; e->len = (uint16_t)len;
queue_data_put(tc->write_queue, e);
```
При заполнении (порог 32) нужно ждать через `queue_waiter_wait`.
### Graceful shutdown
```c
tcp_conn_push_fin(tc); // shutdown(SHUT_WR) после отправки всех данных → on_fin_sent
tcp_conn_push_close(tc); // close сокета после отправки всех данных → on_closed
```
Можно вызывать из callback-ов (on_fin, on_read_cb и т.д.).
Повторные вызовы игнорируются (fin_local=1/closed=1 — возвращают -1).
### Уничтожение
```c
void tcp_conn_destroy(struct tcp_conn* tc);
```
Идемпотентен (`destroyed` флаг). Дренирует обе очереди, закрывает сокет, освобождает пулы через отложенный `uasync_call_soon`.
### Обработка ошибок
`on_error(tc, err, arg)` вызывается после закрытия сокета. **Реализация on_error обязана вызвать tcp_conn_destroy.** После возврата из on_error `tc` недействителен.
```c
static void on_error_cb(struct tcp_conn* tc, int err, void* arg) {
struct my_conn* c = (struct my_conn*)arg;
// почистить свои ресурсы
tcp_conn_destroy(tc); // tc после этого использовать нельзя
socket_close_wrapper(other_sock);
u_free(c);
}
```
### on_flushed (одноразовый)
```c
tcp_conn_set_flushed(tc, my_flushed_cb);
// коллбэк вызовется один раз когда write_queue + write_buf полностью опустеют, затем сбросится
```
### Приостановка чтения
```c
tcp_conn_pause_read(tc); // убирает EPOLLIN и отменяет read_waiter
```
### Ограничения и многопоточность
- **Однопоточный.** Всё работает в одном `uasync` event loop. Нельзя вызывать из другого потока.
- **FIN-сентинел в очереди.** После `push_fin` данные продолжат отправляться, сам FIN выполнится только когда все предшествующие данные отправлены.
- **Нельзя вызывать close сокета напрямую.** Только через `tcp_conn_push_close` — иначе нарушится порядок обработки очереди.
- **Можно вызывать push_fin/push_close из callback-ов** — защита от повторного вызова встроена (fin_local/closed флаги).
## 3. API
### Структуры
| Поле | Назначение |
|------|-----------|
| `struct tcp_conn` | Всё состояние TCP-соединения: сокет, очереди, пулы, коллбэки, флаги |
### Жизненный цикл
| Функция | Описание |
|---------|----------|
| `tcp_conn_create(ua, sock, entry_data_size, write_chunk_size, read_high_water, read_low_water, rcvbuf_size, on_fin, on_error, arg)` | Создать соединение: аллоцирует tc, два memory pool, два ll_queue, регистрирует сокет в uasync с EPOLLIN+EPOLLOUT. Вовращает NULL при ошибке |
| `tcp_conn_destroy(tc)` | Дренирует read_queue и write_queue, закрывает сокет, освобождает пулы через `uasync_call_soon`. Идемпотентен |
### Коллбэки (поля struct tcp_conn)
| Поле | Когда вызывается |
|------|-----------------|
| `on_fin(tc, arg)` | FIN получен от удалённой стороны (recv==0): сразу если read_queue пуста, иначе через `queue_set_empty_callback` после дренажа |
| `on_fin_sent(tc, arg)` | FIN отправлен (`shutdown(SHUT_WR)`) — после отправки всех данных перед FIN-сентинелом |
| `on_error(tc, err, arg)` | Фатальная ошибка сокета. Вызывается после close сокета. **Обязан вызвать tcp_conn_destroy.** После возврата tc недействителен |
| `on_flushed(tc, arg)` | write_queue + write_buf полностью опустели. Одноразовый — после вызова сбрасывается в NULL. Установка: `tcp_conn_set_flushed()` |
| `on_closed(tc, arg)` | Сокет закрыт через CLOSE-сентинел (`tcp_conn_push_close`) после отправки всех предшествующих данных |
### Graceful shutdown
| Функция | Описание |
|---------|----------|
| `tcp_conn_push_fin(tc)` | Помещает FIN-сентинел (`dgram=&tcp_fin_sentinel, len=0`) в write_queue. После отправки предшествующих данных: shutdown(SHUT_WR), fin_local=1, вызывает on_fin_sent. Повторный вызов → -1 |
| `tcp_conn_push_close(tc)` | Помещает CLOSE-сентинел (`dgram=NULL, len=0`) в write_queue. После отправки предшествующих данных: close сокета, удаление из uasync, closed=1, вызывает on_closed. Повторный вызов → -1 |
### Прочее
| Функция | Описание |
|---------|----------|
| `tcp_conn_set_flushed(tc, cb)` | Установить одноразовый коллбэк на полное опустошение write_queue |
| `tcp_conn_pause_read(tc)` | Принудительно выключить EPOLLIN и отменить ожидание низкого порога |