Руководство по подключению XrplCSharp
Это руководство объясняет, как настроить и управлять WebSocket-подключениями к узлам XRP Ledger с помощью библиотеки XrplCSharp.
Содержание
- Быстрый старт
- Параметры подключения
- Состояния подключения
- Автоматическое переподключение
- Keepalive и мониторинг соединения
- Особенности MAUI и мобильных приложений
- Особенности WebAssembly / Blazor
- Потоковые подписки
- Политики обработки запросов
- Обработка событий
- Обработка ошибок
- Примеры использования
- Лучшие практики
Быстрый старт
using Xrpl.Client;
// Создание клиента с настройками по умолчанию
var client = new XrplClient("wss://s.altnet.rippletest.net:51233");
// Подключение
await client.Connect();
// Выполнение запросов
var response = await client.Request(new AccountInfoRequest { Account = "rAddress..." });
// Отключение по завершении
await client.Disconnect();
Параметры подключения
Настройте поведение подключения, передав ClientOptions при создании клиента:
var client = new XrplClient("wss://s.altnet.rippletest.net:51233", new XrplClient.ClientOptions
{
RequestTimeout = TimeSpan.FromSeconds(30),
MaxReconnectAttempts = 10,
StopAfterMaxAttempts = false
});
Доступные параметры
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
RequestTimeout |
TimeSpan | 40 секунд | Таймаут для отдельных API-запросов после установки соединения |
ConnectionAttemptTimeout |
TimeSpan | 20 секунд | Таймаут для одной попытки подключения WebSocket |
ReconnectBaseDelay |
TimeSpan | 2 секунды | Базовая задержка между попытками автоматического переподключения |
ReconnectMaxDelay |
TimeSpan | 30 секунд | Максимальная задержка между попытками переподключения (предел экспоненциального роста) |
MaxReconnectAttempts |
int | 5 | Максимальное количество попыток переподключения после разрыва соединения |
StopAfterMaxAttempts |
bool | true | Прекращать ли попытки переподключения после достижения максимума |
UseCustomPing |
bool | true | Включить пользовательский ping/pong для обнаружения проблем соединения |
UseCheckHealth |
bool | false | Лёгкая фоновая проверка состояния каждые 20 секунд. Проверяет только локальное состояние WebSocket через IsConnected() — сетевые запросы не отправляются. Если WebSocket не подключён (Closed/Aborted), запускается автоматическое переподключение. Автоматически включается при UseCustomPing = true. Может быть включён отдельно для быстрого обнаружения разрывов без нагрузки keepalive |
RequestPolicy |
RequestFailurePolicy | WaitForConnection | Как обрабатывать запросы при отсутствии соединения |
ConnectionAcquisitionTimeout |
TimeSpan | 5 минут | Максимальное время ожидания соединения при использовании политики WaitForConnection |
Состояния подключения
Соединение может находиться в одном из четырёх состояний, доступных через client.connection.CurrentConnectionState:
| Состояние | Описание |
|---|---|
Disconnected |
Не подключён. Начальное состояние, после отключения пользователем или после превышения максимального числа попыток |
Connecting |
Установка начального соединения |
Connected |
Успешно подключён и готов к запросам |
RestoringConnection |
Попытка восстановить соединение после неожиданного разрыва |
Диаграмма состояний
┌─────────────────┐
│ Disconnected │ (начальное)
└────────┬────────┘
│ Connect()
▼
┌─────────────────┐
│ Connecting │
└────────┬────────┘
│ успех
▼
┌─────────────────┐
┌─────────│ Connected │◄────────┐
│ └────────┬────────┘ │
│ │ потеря связи │ успех
│ ▼ │
│ ┌─────────────────┐ │
│ │RestoringConnection│───────┘
│ └────────┬────────┘
│ │ макс. попыток или Disconnect()
│ ▼
│ ┌─────────────────┐
└────────►│ Disconnected │
пользователь └────────────────┘
вызвал Disconnect()
Автоматическое переподключение
При неожиданной потере соединения (перезапуск сервера, проблемы сети) клиент автоматически пытается переподключиться.
Алгоритм задержки
Задержка между попытками переподключения использует экспоненциальный рост с джиттером:
задержка = min(ReconnectBaseDelay * 2^(попытка-1), ReconnectMaxDelay) + случайный_джиттер
Пример с настройками по умолчанию:
- Попытка 1: ~2 секунды
- Попытка 2: ~4 секунды
- Попытка 3: ~8 секунд
- Попытка 4: ~16 секунд
- Попытка 5: ~30 секунд (достигнут предел)
Поведение переподключения
| Сценарий | Поведение |
|---|---|
| Сервер закрыл соединение | Автопереподключение начинается |
| Обнаружен таймаут сети | Автопереподключение начинается |
Пользователь вызвал Disconnect() |
Автопереподключения нет, состояние становится Disconnected |
Превышено макс. попыток (StopAfterMaxAttempts = true) |
Переподключение останавливается, состояние Disconnected |
Превышено макс. попыток (StopAfterMaxAttempts = false) |
Продолжает попытки с предупреждающими сообщениями |
Ручное переподключение
После исчерпания максимального числа попыток (с StopAfterMaxAttempts = true) вы можете переподключиться вручную:
// После окончательного сбоя соединения
await client.Connect();
Это сбрасывает счётчик попыток и начинает заново.
Быстрое переподключение (Fast Reconnect)
Для определённых сценариев библиотека использует быстрое переподключение (3-5 секунд) вместо экспоненциальной задержки:
| Сценарий | Поведение | Время |
|---|---|---|
Вызван ChangeServer() |
Немедленное переключение на новый сервер | 3-5 секунд |
| Таймаут ping (нет pong 15 сек) | Немедленное переподключение к тому же серверу | 3-5 секунд |
| Потеря сети (IOException, SocketException) | Немедленное переподключение при восстановлении сети | 3-5 секунд |
Быстрое переподключение отличается от стандартного:
- Без экспоненциальной задержки - подключается немедленно
- Изоляция сессий - старая сессия завершается, создаётся новая
- Отмена pending-запросов - избегает устаревших ответов от старого соединения
- ReconnectInfo доступен -
CurrentAttempt = 1во время быстрого переподключения
client.connection.OnConnectionStatus += (status) =>
{
if (status.ConnectionState == XrpConnectionState.RestoringConnection)
{
// ReconnectInfo всегда доступен во время переподключения (включая быстрое)
Console.WriteLine($"Переподключение: попытка {status.Reconnect?.CurrentAttempt}");
}
};
Keepalive и мониторинг соединения
Серверы XRPL (s1/s2.ripple.com) требуют активности на уровне приложения и закрывают соединения примерно через 60-80 секунд молчания клиента, вне зависимости от транспортного уровня WebSocket keepalive (RFC 6455 ping/pong фреймы). Библиотека предоставляет два взаимодополняющих механизма для поддержания стабильности соединения.
UseCustomPing
При включении (по умолчанию: true) библиотека отправляет ping-команды на уровне приложения каждые 20 секунд:
- Активное соединение (данные получены в последние 30 секунд): отправляет fire-and-forget
{"command":"ping"}напрямую в WebSocket без ожидания ответа. Это поддерживает серверное соединение живым без блокировки потока. - Простаивающее соединение (нет данных 30+ секунд): отправляет полный request-response ping с таймаутом 45 секунд. Если ответ не получен — запускается переподключение.
- Таймаут неактивности: если активность не обнаружена в течение 60+ секунд, соединение считается мёртвым и запускается переподключение.
Включение UseCustomPing автоматически включает UseCheckHealth.
UseCheckHealth
При включении (по умолчанию: false, автоматически включается с UseCustomPing) лёгкая фоновая проверка запускается каждые 20 секунд:
- Проверяет локальное состояние WebSocket через
IsConnected()(проверяет State == Open) - Если WebSocket не подключён (Closed, Aborted и т.д.), немедленно запускается автоматическое переподключение
- Сетевые запросы не отправляются — это исключительно локальная проверка состояния
- Таймаут неактивности 60 секунд применяется только при включённом
UseCustomPing
Рекомендуемые конфигурации
| Сценарий | UseCustomPing | UseCheckHealth | Поведение |
|---|---|---|---|
| Полная защита (по умолчанию) | true |
авто-включён | Keepalive пинги + обнаружение разрывов + таймаут неактивности |
| Только обнаружение разрывов | false |
true |
Быстрое обнаружение изменений состояния WebSocket, без нагрузки keepalive |
| Без мониторинга | false |
false |
Нет проверок состояния. Соединение может незаметно умереть через ~60-80 секунд неактивности |
Особенности MAUI и мобильных приложений
При использовании XrplCSharp в MAUI или мобильных приложениях библиотека обеспечивает специальную обработку мобильных сетевых условий.
Отсутствие Critical-логирования
Библиотека подавляет Critical-уровень логирования для типичных мобильных сетевых исключений:
ObjectDisposedException- сокет закрыт во время переподключенияIOException- ошибки сетевого ввода-выводаSocketException- низкоуровневые ошибки сокетаTaskCanceledException- операции отменены при отключении- Ошибки DNS на iOS (например, "nodename nor servname provided")
Это предотвращает переполнение глобальных обработчиков исключений вашего приложения ожидаемыми сетевыми событиями.
Автоматическое восстановление сети
При восстановлении сетевого подключения (например, переключение с WiFi на мобильную сеть) библиотека:
- Обнаруживает потерю сети через таймаут ping или исключение сокета
- Инициирует быстрое переподключение (не медленный exponential backoff)
- Переподключается в течение 3-5 секунд
- Отправляет обновления статуса
RestoringConnection→Connected
Специфика iOS
Библиотека распознаёт специфические для iOS сетевые ошибки:
- Ошибки разрешения DNS с HRESULT
0xFFFDFFFF - Общие ошибки с HRESULT
0x80004005(E_FAIL) - Паттерны сообщений об ошибках типа "nodename nor servname"
Они обрабатываются как восстановимые потери сети, а не как критические ошибки.
Лучшие практики для мобильных приложений
var client = new XrplClient(url, new XrplClient.ClientOptions
{
// Мобильные сети ненадёжны - будьте терпеливы
MaxReconnectAttempts = 50,
StopAfterMaxAttempts = false,
// Ждать соединения - мобильное устройство может временно потерять связь
RequestPolicy = RequestFailurePolicy.WaitForConnection,
ConnectionAcquisitionTimeout = TimeSpan.FromMinutes(5),
// Держите ping включённым для проактивного обнаружения сбоев
UseCustomPing = true
});
// Мониторинг соединения для обновления UI
client.connection.OnConnectionStatus += (status) =>
{
MainThread.BeginInvokeOnMainThread(() =>
{
UpdateConnectionIndicator(status.ConnectionState);
});
};
Особенности WebAssembly / Blazor
При использовании XrplCSharp в приложениях Blazor WebAssembly библиотека адаптируется к однопоточной среде браузера.
Однопоточная среда
WebAssembly работает в главном потоке браузера. В отличие от Desktop и MAUI:
Task.Run()не создаёт реальных фоновых потоков — вся работа выполняется в одном потокеChannel<T>не обеспечивает настоящую конкурентную обработку- Все асинхронные операции кооперативны — они отдают управление через
await
Отсутствие RFC 6455 Keepalive
Браузерный WebSocket API не поддерживает keepalive на транспортном уровне (RFC 6455 ping/pong фреймы). Это означает:
KeepAliveIntervalне действует в WebAssembly- Keepalive на уровне приложения через
UseCustomPing— единственный способ предотвратить серверный таймаут - Без
UseCustomPingсерверы XRPL закроют соединение через ~60-80 секунд молчания
Обработка потоковых сообщений
Потоковые сообщения (транзакции, события леджера) обрабатываются по паттерну fire-and-forget:
- Используется
ProcessStreamMessageFireAndForgetAsync()вместоChannel<T> - Цикл приёма не блокируется — обработка стрима планируется через
ConfigureAwait(false) - Ответы на запросы (со свойством
"id") всегда приоритизируются над потоковыми сообщениями через быстрое сканирование строкиIsLikelyResponse()
Поведение ReceiveAsync
В WebAssembly ReceiveAsync использует только общий токен отмены без искусственного таймаута. Состояние соединения контролируется UseCheckHealth, который проверяет состояние WebSocket каждые 20 секунд. Если WebSocket переходит в состояние Closed или Aborted, автоматически запускается переподключение.
Лучшие практики для Blazor WebAssembly
var client = new XrplClient(url, new XrplClient.ClientOptions
{
// Обязательно для стабильных соединений — предотвращает серверный таймаут
UseCustomPing = true,
// Рекомендуется для Blazor — быстрый отказ с обработкой в UI
RequestPolicy = RequestFailurePolicy.ImmediateFail,
// Разумное количество попыток переподключения
MaxReconnectAttempts = 10,
StopAfterMaxAttempts = false
});
Потоковые подписки
Библиотека поддерживает подписку на потоки XRPL (транзакции, события леджера) в реальном времени через client.Subscribe().
Базовое использование
// Подписка на потоки транзакций и леджеров
var subscribeRequest = new SubscribeRequest
{
Streams = new List<string> { "transactions", "ledger" }
};
client.connection.OnTransaction += (tx) =>
{
Console.WriteLine($"Транзакция: {tx.Transaction.TransactionType}");
};
client.connection.OnLedgerClosed += (ledger) =>
{
Console.WriteLine($"Леджер: {ledger.LedgerIndex}");
};
await client.Subscribe(subscribeRequest);
// Отписка по завершении
await client.Unsubscribe(new UnsubscribeRequest
{
Streams = new List<string> { "transactions", "ledger" }
});
Обработка высоконагруженных потоков
На основной сети (s1/s2.ripple.com) потоки транзакций генерируют 200+ сообщений в секунду. Библиотека использует быструю обработку сообщений (fast-path):
IsLikelyResponse()сканирует каждое сообщение на наличие свойства"id"чистым сканированием строки (без парсинга JSON)- Ответы на запросы (со свойством
"id") обрабатываются немедленно черезrequestManager.HandleResponse() - Потоковые сообщения (без
"id") обрабатываются асинхронно — черезChannel<T>на Desktop/MAUI или fire-and-forget в WebAssembly
Известное ограничение: таймауты запросов при тяжёлом потоке
При подписке на высоконагруженные потоки на основной сети запросы типа request-response (server_info, account_info, unsubscribe и др.) могут получать таймауты. Это происходит потому что:
- Сервер XRPL буферизирует исходящие потоковые сообщения
- Ответ на ваш запрос стоит в очереди за сотнями потоковых сообщений в буфере отправки сервера
- Даже несмотря на то, что клиентский fast-path приоритизирует ответы, ответ физически не может дойти до клиента, пока сервер не отправит его через буфер
- Стандартный
RequestTimeout(40 секунд) может истечь до доставки ответа
Обходные решения:
- Увеличьте
RequestTimeoutдля операций во время активных подписок - Используйте политику
ImmediateFailи реализуйте логику повторов с увеличенными таймаутами - Рассмотрите использование отдельных экземпляров клиента для подписок и запросов request-response
Примечание: В будущей версии будет реализован режим Response-Seeking Drain или архитектура с двумя WebSocket-соединениями для полного решения этого ограничения.
Политики обработки запросов
Параметр RequestPolicy определяет, как обрабатываются запросы при отсутствии соединения.
ImmediateFail
Запросы немедленно выбрасывают NotConnectedException, если нет соединения:
var client = new XrplClient(url, new XrplClient.ClientOptions
{
RequestPolicy = RequestFailurePolicy.ImmediateFail
});
try
{
var response = await client.Request(...);
}
catch (NotConnectedException)
{
// Обработка отсутствия соединения
}
Когда использовать: Когда нужна немедленная обратная связь и вы сами управляете повторами.
WaitForConnection (По умолчанию)
Запросы ожидают установки соединения (до ConnectionAcquisitionTimeout):
var client = new XrplClient(url, new XrplClient.ClientOptions
{
RequestPolicy = RequestFailurePolicy.WaitForConnection,
ConnectionAcquisitionTimeout = TimeSpan.FromMinutes(2)
});
// Это будет ждать соединения, если отключено
var response = await client.Request(...);
Когда использовать: Для ботов и сервисов 24/7, которые должны переживать временные проблемы сети.
Обработка событий
События статуса подключения
Подпишитесь на OnConnectionStatus для получения обновлений состояния в реальном времени:
client.connection.OnConnectionStatus += (status) =>
{
Console.WriteLine($"Состояние: {status.ConnectionState}");
Console.WriteLine($"Сообщение: {status.Message}");
Console.WriteLine($"Важность: {status.Severity}");
if (status.Reconnect != null)
{
Console.WriteLine($"Попытка: {status.Reconnect.CurrentAttempt}/{status.Reconnect.MaxAttempts}");
Console.WriteLine($"Следующая попытка через: {status.Reconnect.RemainingDelay.TotalSeconds}с");
}
};
Свойства ConnectionStatusInfo
| Свойство | Тип | Описание |
|---|---|---|
ConnectionState |
XrpConnectionState | Текущее состояние подключения |
Message |
string | Человекочитаемое сообщение о статусе |
Severity |
ConnectionCloseSeverity | Info, Warning или Error |
Reconnect |
ReconnectInfo? | Детали переподключения (null если не переподключается) |
Свойства ReconnectInfo
| Свойство | Тип | Описание |
|---|---|---|
CurrentAttempt |
int | Номер текущей попытки переподключения |
MaxAttempts |
int | Максимальное настроенное количество попыток |
RemainingDelay |
TimeSpan | Время до следующей попытки переподключения |
Другие события
// Вызывается при установке соединения
client.connection.OnConnected += () => { ... };
// Вызывается при потере соединения
client.connection.OnDisconnect += (code, reason) => { ... };
// Вызывается при ошибках
client.connection.OnError += (errorCode, errorMessage, message, error) => { ... };
Обработка ошибок
Распространённые исключения
| Исключение | Когда возникает |
|---|---|
NotConnectedException |
Запрос при отсутствии соединения (с политикой ImmediateFail) |
TimeoutException |
Превышен таймаут запроса (RequestTimeout) |
DisconnectedException |
Соединение потеряно во время ожидающего запроса |
XrplException |
Ошибки протокола XRPL |
Обработка отключений
client.connection.OnConnectionStatus += (status) =>
{
switch (status.ConnectionState)
{
case XrpConnectionState.Disconnected:
if (status.Severity == ConnectionCloseSeverity.Error)
{
// Постоянный сбой - может потребоваться ручное вмешательство
LogError(status.Message);
}
break;
case XrpConnectionState.RestoringConnection:
// Автопереподключение в процессе
LogInfo($"Переподключение... попытка {status.Reconnect?.CurrentAttempt}");
break;
}
};
Примеры использования
Базовое подключение с мониторингом статуса
var client = new XrplClient("wss://s.altnet.rippletest.net:51233");
client.connection.OnConnectionStatus += (status) =>
{
Console.WriteLine($"[{status.ConnectionState}] {status.Message}");
};
await client.Connect();
// ... использование клиента ...
await client.Disconnect();
Конфигурация для бота 24/7
var client = new XrplClient("wss://s.altnet.rippletest.net:51233", new XrplClient.ClientOptions
{
// Никогда не прекращать попытки переподключения
MaxReconnectAttempts = 100,
StopAfterMaxAttempts = false,
// Ждать соединения при запросах
RequestPolicy = RequestFailurePolicy.WaitForConnection,
ConnectionAcquisitionTimeout = TimeSpan.FromMinutes(10),
// Увеличенные таймауты для стабильности
RequestTimeout = TimeSpan.FromSeconds(60),
ConnectionAttemptTimeout = TimeSpan.FromSeconds(30)
});
Конфигурация с быстрым отказом
var client = new XrplClient("wss://s.altnet.rippletest.net:51233", new XrplClient.ClientOptions
{
// Немедленный отказ, если нет соединения
RequestPolicy = RequestFailurePolicy.ImmediateFail,
// Ограниченное количество попыток
MaxReconnectAttempts = 3,
StopAfterMaxAttempts = true,
// Короткие таймауты
RequestTimeout = TimeSpan.FromSeconds(10),
ConnectionAttemptTimeout = TimeSpan.FromSeconds(5)
});
Переключение серверов
// Переключение на другой сервер (отключается и переподключается)
await client.connection.ChangeServer("wss://s1.ripple.com:443");
Лучшие практики
- Всегда подписывайтесь на
OnConnectionStatusдля мониторинга состояния соединения - Используйте политику
WaitForConnectionдля сервисов, требующих отказоустойчивости - Установите
StopAfterMaxAttempts = falseдля приложений 24/7 - Обрабатывайте состояние
Disconnectedв вашем UI для отображения статуса подключения - Не вызывайте
Connect()повторно - это безопасно, но излишне (идемпотентно) - Используйте
CancellationTokenдля корректного завершения работы