# Сокеты

Используйте сокет SDK, чтобы подключить TCP- или UDP-клиент своего приложения через Device URnetwork. У него привычные чтение, запись, дедлайны и поведение при закрытии, а его трафик идёт по пути соединения, выбранному Device. Им можно пользоваться для одного клиента, не направляя остальную машину через VPN операционной системы.

Начните с инициализированного Device — см. [Начало работы с SDK](/docs/getting-started-sdk). Методы сокетов доступны в обновлённых исходниках SDK и требуют соответствующей поддержки сокетов на удалённом Device. Используйте сборки SDK, содержащие эти API, на обеих сторонах RPC-соединения.

Команды нативных менеджеров пакетов ищите на странице [Установка SDK](/docs/install-sdk),
а запускаемые интеграции сокетов и HTTP-библиотек на двенадцати языках,
включая отдельные программы на JavaScript для Node и для браузера, — в
[примерах](/docs/examples).

## Чем он отличается от сокета ядра

Сокет ядра принадлежит сетевому стеку операционной системы. Сокет SDK принадлежит Device и использует внутрипроцессный стек TCP/IP. Device отправляет свои пакеты по настроенному маршруту, включая выбранного провайдера, когда подключён через URnetwork.

| | Сокет ядра | Сокет SDK |
| --- | --- | --- |
| Сетевой путь | Таблица маршрутизации ОС и выбор интерфейса | Маршрутизация Device, выбор провайдера и политика |
| Интерфейс приложения | Файловый дескриптор или платформенный объект сокета | `net.Conn` в Go, `Conn` в JS, хендл в C или `Socket` на мобильных платформах |
| Область действия | Соединение приложения в сети хоста | Соединение приложения, которым владеет один Device |
| Нужна ли для этого соединения системная VPN | Зависит от настройки маршрута ОС | Интерфейс TUN/VPN в ОС не требуется только ради вызовов сокетов SDK |
| Локальный адрес | Интерфейс/адресное пространство хоста | Виртуальный адрес в стеке пользовательского пространства Device |
| Закрытие Device | Не связано | Закрывает его соединения SDK |
| Опции сокета и слушатели | Платформозависимые API ОС | Только исходящие соединения; нет ни файлового дескриптора, ни произвольных опций сокета, ни API прослушивания |

Виртуальный `localAddr` — не публичный IP выхода провайдера, и привязка к нему не делает ваше приложение достижимым из интернета. Нижележащее соединение URnetwork и провайдер по-прежнему могут пользоваться сетью операционной системы. Сокет SDK сам по себе не меняет настроенный режим маршрутизации Device и не создаёт соединения с провайдером.

TLS и DTLS шифруют соединение приложения до его назначения. Простые TCP и UDP сохраняют открытый протокол приложения; транспортное шифрование URnetwork до провайдера не превращает этот протокол в TLS до назначения.

## Сети и имена хостов

Устанавливайте соединение с `host:port`, указывая одну из этих сетей:

| Сеть | Поведение |
| --- | --- |
| `tcp` | TCP с Happy Eyeballs по IPv4/IPv6 для имени хоста |
| `tcp4`, `tcp6` | TCP, ограниченный этим семейством адресов |
| `udp` | UDP; для имени хоста с обоими семействами адресов пир выбирается гонкой первой датаграммы/ответа |
| `udp4`, `udp6` | UDP, ограниченный этим семейством адресов |

Литералы IPv6 заключайте в квадратные скобки, например `[2001:db8::10]:443`. Для разрешения имён хостов используются резолвер сокетов Device и его пакетный путь. Когда провайдер недоступен, откат к разрешению или открытию назначения напрямую через хост не происходит.

Happy Eyeballs для TCP устраивает гонку попыток соединения. Методы установки соединения TLS/DTLS выбирают семейство после того, как его защищённое рукопожатие завершится успешно. Явные имена семейств и литеральные адреса обходят гонку между двумя семействами.

### UDP: возможная доставка дубликатов

У UDP нет рукопожатия при установке соединения. Для `udp`, когда у имени есть и DNS-запись A, и AAAA, первая операция записи уходит на IPv6, а через 250 мс без ответа — на IPv4. Немедленная ошибка записи в IPv6 запускает откат раньше. **Начальная датаграмма приложения может дойти до обоих адресов.** Первый ответ выбирает пира, и последующие операции записи идут только к этому пиру.

Когда доставка дубликатов важна, используйте идентификаторы запросов или другой механизм обработки дубликатов на уровне приложения. Успешная первая операция записи не доказывает доставку и не выбирает пира. Если сервер так и не ответит, последующие операции записи ждут выбора до своего дедлайна или закрытия. Для протоколов, работающих только на отправку, используйте `udp4`, `udp6` или литеральный IP. Ответ нулевой длины — допустимый ответ.

Каждая операция записи UDP — одна датаграмма. Каждое чтение потребляет одну датаграмму; если буфер чтения мал, оставшиеся байты отбрасываются. Пустая датаграмма — это данные, а не EOF. Укладывайте сообщения в ограничения протокола назначения и пути.

## Go

`DeviceLocal` и `DeviceRemote` предлагают `Dial`, `DialContext`, `DialTls` и `DialTlsContext`. Обычные методы установки соединения используют те же сигнатуры, что и `net.Dialer` в Go, и возвращают соединение, совместимое с `net.Conn`.

```go
// device is an initialized sdk.Device.
conn, err := device.DialContext(ctx, "tcp", "example.com:80")
if err != nil {
    return err
}
defer conn.Close()
if err := conn.SetDeadline(time.Now().Add(5 * time.Second)); err != nil {
    return err
}
_, err = conn.Write([]byte("GET / HTTP/1.0\r\nHost: example.com\r\n\r\n"))
```

Существующий HTTP-клиент может использовать Device напрямую:

```go
transport := &http.Transport{DialContext: device.DialContext}
defer transport.CloseIdleConnections()
client := &http.Client{Transport: transport, Timeout: 20 * time.Second}
// net/http performs normal HTTPS certificate verification above this dialer.
```

Для прямого шифрования вызовите `device.DialTlsContext(ctx, "tcp", "example.com:443", nil)`. TCP использует TLS; UDP — DTLS 1.2. При конфигурации TLS, равной nil, используется обычная проверка сертификатов. Имя сервера по умолчанию берётся из исходного имени хоста. DTLS поддерживает меньшую поверхность конфигурации, чем TLS в Go, и отвергает неподдерживаемые опции безопасности.

Чтение и запись могут вернуть частичные данные вместе с ошибкой; обрабатывайте возвращённые байты. Контекст установки соединения управляет установкой, а не временем жизни успешного соединения. Для последующего ввода-вывода задавайте абсолютные дедлайны, а чтобы снять их, используйте `time.Time{}`. Закончив, закройте соединение. Предел по умолчанию на установку соединения и защищённое рукопожатие — 30 секунд; более ранний дедлайн вызывающего его сокращает.

## JavaScript и TypeScript

Обёртки Device в SDK предоставляют методы `dial` и `dialTls` на основе Promise. Страница браузера использует настроенный удалённый Device с поддержкой сокетов. SDK открывает удалённое соединение через WASM; ему не нужны ни нативная поддержка Direct Sockets в браузере, ни упаковка в Isolated Web App.

```js
const conn = await device.dialTls("tcp", "example.com:443", undefined, {
  timeoutMillis: 5000,
});
try {
  await conn.setDeadline(Date.now() + 5000);
  await conn.write(new TextEncoder().encode(
    "GET / HTTP/1.0\r\nHost: example.com\r\n\r\n"
  ));
  for (let chunk; (chunk = await conn.read()) !== null;) {
    console.log(new TextDecoder().decode(chunk));
  }
} finally {
  await conn.close();
}
```

`read()` возвращает `Uint8Array` или `null` при EOF. Пустой массив — допустимая UDP-датаграмма. `readable` и `writable` предоставляют адаптеры Web Streams; для каждого направления выберите либо прямые методы, либо адаптеры. Закрытие потока TCP отправляет FIN со стороны записи, если это поддерживается. Явный `close()` закрывает всё соединение.

Чтобы отменить открытие, используйте `AbortSignal`. Для установленных сокетов используются дедлайны и закрытие. Методы дедлайнов принимают миллисекунды эпохи Unix, `Date` или `null`, чтобы снять дедлайн. Ошибка частичной записи содержит `bytesWritten`; частичные данные чтения возвращаются первыми, а сопутствующая им ошибка — при следующем прямом чтении. Трафик сокета останавливается, когда отключается владеющий им удалённый сервис; после переподключения он не воспроизводится повторно.

### API Direct Sockets

Используйте сигнатуры конструкторов Direct Sockets с UR Device:

```js
const {TCPSocket, UDPSocket} = device.directSockets;
const socket = new TCPSocket("echo.example", 9000);
const {readable, writable} = await socket.opened;
const reader = readable.getReader({mode: "byob"});
const writer = writable.getWriter();
try {
  await writer.write(new TextEncoder().encode("hello"));
  await writer.close();
  console.log(await reader.read(new Uint8Array(1024)));
} finally {
  await Promise.allSettled([reader.cancel(), writer.abort()]);
  reader.releaseLock(); writer.releaseLock();
  await socket.close();
  await socket.closed;
}
```

Для UDP создайте `new UDPSocket({remoteAddress: "echo.example", remotePort: 9001})`. Дождитесь `opened`, затем записывайте `{data: new Uint8Array([1, 2, 3])}` и читайте `value.data`. Каждый объект — одна датаграмма, включая датаграмму нулевой длины. Сообщения подключённого UDP опускают поля удалённого адреса и отвергают переопределение назначения для отдельного сообщения.

Экспортируемая фабрика `createDirectSockets(device)` тоже возвращает эти конструкторы. У обоих интерфейсов есть промисы `opened` и `closed` и асинхронный `close()`. TCP поддерживает читателей по умолчанию и BYOB-читателей, а также запись BufferSource. Закрытие его потока записи отправляет FIN, а чтение при этом остаётся доступным. Перед закрытием сокета отмените или прервите ожидающие операции и снимите блокировки читателя и писателя; иначе `close()` отклоняется с `InvalidStateError`. Сетевые сбои приводят к отклонению с `NetworkError`.

Нативный API Chrome доступен приложениям Isolated Web Apps. Реализация SDK работает в обычных браузерах и в Node через настроенный Device, без пакета IWA и без нативного разрешения Direct Sockets. Она не подменяет глобальные объекты браузера. См. [документацию Chrome](https://developer.chrome.com/docs/iwa/direct-sockets) и [предложение Direct Sockets](https://wicg.github.io/direct-sockets/).

Этот релиз реализует исходящий TCP и подключённый UDP. Чтобы ограничить разрешение имён, используйте `dnsQueryType: "ipv4"` или `"ipv6"`; если параметр опущен, сохраняется Happy Eyeballs самого Device. Привязанный UDP, мультикаст, слушатели и настройка буфера/no-delay/keep-alive для отдельного сокета недоступны, и корректные запросы этих возможностей завершаются ошибкой `NotSupportedError`. Это профиль совместимости для клиентов, а не полный браузерный API.

Для имён UDP с обоими семействами адресов промис `opened` выполняется до отправки. Его поля конечной точки сначала описывают первого кандидата и обновляются после получения первого ответа. **Начальная датаграмма может дойти до обоих адресов.** Если конечная точка должна быть фиксированной уже при открытии, выберите литеральный IP или семейство DNS. Примеры используют таймер, чтобы отменять зависший потоковый ввод-вывод; отдельный API `Conn` к тому же предоставляет дедлайны.

Конструкторы Direct Sockets открывают простые соединения TCP/UDP. Для TLS/DTLS используйте `dialTls`. Запускаемые [примеры на JavaScript для Node и браузера](/docs/examples/javascript) и [пример на TypeScript](/docs/examples/typescript) включают эхо по TCP/UDP, тайм-ауты, очистку и интеграцию с HTTP-библиотеками.

## Нативные биндинги и поддерживаемые платформы

| Биндинг | Поверхность сокетов и платформа |
| --- | --- |
| Go | Поведение `net.Conn` на целевых платформах Go, которые поддерживает SDK |
| Android Kotlin/Java | Переносимые `OpenSocket`/`Socket` в AAR; Android API 24+ с поставляемыми ABI |
| Apple Swift | Переносимые `OpenSocket`/`Socket` в XCFramework; iOS 16+ и macOS 13.5+ с поставляемыми срезами для устройств и симулятора |
| C/C++ и другие FFI-клиенты | C ABI для Windows 10+ и Linux glibc 2.35+ на amd64/arm64; сборки для хоста macOS — для разработки |
| JS/TS в браузере и Node | WASM, Conn и Web Streams в Direct Sockets, плюс доступный эндпоинт Device RPC с поддержкой сокетов |

Мобильный код вызывает `OpenSocket(network, address, timeoutMillis, tlsOptions)`. Опции TLS, равные nil, запрашивают простое соединение; конфигурация, отличная от nil, запрашивает TLS/DTLS. `Socket.Read` возвращает результат с `Data` и `Eof`; методы дедлайнов принимают миллисекунды эпохи Unix, а ноль снимает дедлайн. Для блокирующих операций используйте рабочий поток или подходящий диспетчер корутин.

Код на C вызывает `urnet_device_dial` / `urnet_device_dial_tls`, а затем `urnet_conn_read`, `urnet_conn_write` и функции дедлайнов и закрытия. Чтение возвращает число байтов и отдельный флаг EOF. Оно потребляет данные сразу; это не запрос размера выходного буфера. Обрабатывайте частичное число байтов, даже когда возвращается строка ошибки. Строки ошибок и адресов освобождайте через `urnet_free_string`, а хендл соединения — через `urnet_release`, который его заодно и закрывает.

Сейчас SDK предоставляет клиентские соединения. Серверные сокеты и сокеты-слушатели появятся отдельным дополнением с явной семантикой достижимости и владения. Модель биндингов и сборки описана в [туре по SDK](/docs/tour-sdk), а настройка Device — в [начале работы](/docs/getting-started-sdk).
