Содержание статьи

Знакомая картина: короткие запросы летают как ласточки, а стоит запустить большую выгрузку или скачать увесистый файл через мобильный прокси, как соединение обрывается на середине. Файл на 40 процентов, потом тишина. Повтор запроса иногда помогает, иногда нет. И самое обидное: логи молчат, сервер вроде живой, а данные не доходят. Если вы сталкивались с этим, вы попали по адресу.

Эта статья посвящена не кодам ответа и не логике повторов на уровне HTTP (про 429 и экспоненциальный backoff есть отдельный материал, на который мы будем ссылаться). Здесь мы разбираем транспортный уровень: почему рвётся TCP, кто именно обрывает соединение, как работают три разных таймаута на пути пакета, что такое MTU и фрагментация в сотовых сетях, и почему смена базовой станции убивает долгие сессии. А главное, мы построим клиента, который всё это переживёт.

Введение: скачивание падает на середине, короткие запросы проходят

Почему тема настолько актуальна именно в 2026 году? Потому что мобильные прокси стали рабочим инструментом для парсинга, автоматизации, тестирования и работы с маркетплейсами. Сотовая сеть по своей природе нестабильнее проводной. Она была спроектирована для человека с телефоном, который открывает страницу, читает её и закрывает. Она не была спроектирована для машины, которая держит одно TCP-соединение открытым десять минут и качает через него гигабайт.

Отсюда и парадокс, вынесенный в заголовок раздела. Короткие запросы проходят, потому что успевают завершиться раньше, чем сработает любой из таймаутов или произойдёт хендовер (переключение между базовыми станциями). Длинные операции живут дольше, а значит, повышают вероятность встретить каждую из проблем: простой таймаута, разрыв при смене соты, потерю крупного пакета из-за неправильного MTU.

Что вы узнаете, дочитав до конца:

  • Как за пять минут понять, кто именно оборвал соединение: ваш клиент, прокси, оператор или целевой сервер.
  • Чем TCP keepalive принципиально отличается от HTTP keep-alive, и почему нужны обе настройки.
  • Как измерить три таймаута на пути и найти самый короткий, который и решает судьбу соединения.
  • Почему в сотовых сетях MTU ниже стандартных 1500 байт, и как выглядит поломка PMTUD (симптом: зависание на больших ответах).
  • Готовый код клиента на Python и Node с докачкой по Range, разбиением выгрузки и правильными таймаутами.

Мы будем говорить техническим языком, но каждый термин объясним по-человечески. Поехали.

Основы: как устроено соединение через мобильный прокси

Прежде чем разбирать поломки, договоримся о картине мира. Когда вы делаете запрос через мобильный прокси, пакет проходит длинную цепочку. Понимание этой цепочки - половина успеха в диагностике.

Путь пакета от клиента до сервера

Представьте маршрут посылки. Она идёт по этапам, и на каждом её могут задержать или потерять:

  1. Ваш клиент - программа, которая делает запрос. У неё свои таймауты и настройки сокетов.
  2. Прокси-сервер - принимает ваше соединение и открывает своё к цели. Часто это два разных TCP-соединения, склеенных вместе.
  3. Мобильный модем и радиоинтерфейс - тот самый узкий и капризный участок. Здесь пакет летит по радиоволне до базовой станции.
  4. Ядро сети оператора - NAT, шлюзы, системы приоритизации трафика.
  5. Публичный интернет - магистральные каналы до дата-центра цели.
  6. Целевой сервер - конечная точка, у которой тоже есть свои лимиты на длину соединения.

Ключевой инсайт: соединение через прокси - это не одна труба, а минимум две. Клиент держит соединение до прокси, прокси держит соединение до цели. Разрыв может случиться на любом из этих сегментов, и симптомы будут разными.

Что такое сессия и почему она хрупкая

TCP-соединение - это виртуальный канал. Физически никакого провода между вами и сервером нет: есть договорённость двух сторон о том, что они обмениваются нумерованными байтами. Пока обе стороны помнят номера и состояние, соединение живёт. Как только одна из сторон забыла состояние (перезагрузился NAT, истёк таймаут, сменилась сота), соединение фактически мертво, хотя другая сторона об этом может не знать ещё долго.

Именно это и создаёт коварные полуоткрытые соединения (half-open): одна сторона считает канал живым и ждёт данных, а вторая давно всё забыла. Клиент висит, никакой ошибки не приходит, время идёт. Знакомо?

Три уровня, на которых бывают проблемы

  • Радиоуровень - потери пакетов, задержки, смена соты. Природа сотовой связи.
  • Сетевой уровень - NAT-таймауты оператора, MTU, фрагментация.
  • Прикладной уровень - таймауты HTTP-сервера прокси и цели, лимиты на размер тела ответа.

Дальше мы пройдёмся по каждому и научимся отличать один от другого.

Глубокое погружение: кто может разорвать соединение и как понять, кто именно

Это самый важный диагностический раздел. Пока вы не знаете виновника, вы стреляете вслепую. Хорошая новость: у каждого виновника свой почерк.

Четыре подозреваемых

1. Ваш клиент. Самый частый и самый недооценённый виновник. Библиотеки HTTP имеют дефолтные таймауты, которые вы могли не заметить. Например, таймаут на общее время запроса, таймаут на чтение сокета, таймаут на бездействие. Если ваш клиент оборвал соединение сам, в логах будет ошибка вида read timeout или socket timeout с указанием вашего кода в стектрейсе.

2. Прокси-сервер. У прокси есть свои лимиты: максимальное время жизни соединения, idle timeout, максимальный размер ответа. Когда прокси рвёт соединение, вы часто получаете внезапный connection reset или закрытие без ответа посреди передачи тела. При этом до прокси пинг проходит, короткие запросы работают.

3. Оператор связи. Самый незаметный. Операторские NAT-таблицы имеют время жизни записи. Если через соединение долго нет трафика, оператор удаляет запись из NAT, и последующие пакеты просто некуда доставить. Симптом: соединение зависает именно во время пауз, а не во время активной передачи. Также оператор виновник при смене базовой станции.

4. Целевой сервер. У него свои настройки keep-alive и лимиты. Многие серверы закрывают соединение после N запросов или после T секунд. Симптом: сервер присылает заголовок Connection: close или корректно завершает соединение (FIN), а не рвёт (RST).

Диагностика по симптомам: таблица почерков

Разберём характерные признаки, чтобы вы могли определить виновника за минуты.

  • Обрыв строго во время пауз, во время активной передачи всё хорошо - почти наверняка NAT-таймаут оператора или idle timeout прокси. Лечится keepalive-трафиком.
  • Обрыв всегда примерно на одном и том же объёме данных (например, около 8 или 10 мегабайт) - лимит на размер ответа у прокси или сервера. Лечится разбиением через Range.
  • Обрыв всегда примерно через одинаковое время (например, ровно 60 или 300 секунд) - хардовый лимит на длину соединения. Ищите наименьший из таймаутов.
  • Зависание именно на больших ответах, мелкие проходят - классическая поломка PMTUD и проблема MTU. Об этом целый раздел ниже.
  • Случайные обрывы без привязки к объёму и времени, чаще при движении - смена базовой станции, хендовер, деградация радиосигнала.
  • Мгновенный RST при попытке передать данные - соединение уже мертво на одной стороне (полуоткрытое), либо активный сброс прокси.

Инструменты первичной диагностики

Чтобы отличить FIN (вежливое закрытие) от RST (грубый сброс) и понять, на каком сегменте рвётся, применяйте:

  • Логирование на уровне сокета - фиксируйте точное время обрыва, количество переданных байт, тип исключения.
  • Анализ трафика - утилита захвата пакетов покажет, кто прислал RST или FIN. Если RST приходит с адреса прокси - виноват прокси. Если соединение просто замолкает без пакетов - виноват промежуточный узел (оператор).
  • Контрольные замеры - повторите ту же операцию через проводное соединение напрямую. Если через провод всё стабильно, а через мобильный прокси рвётся, проблема в мобильном сегменте.

Авторское наблюдение: в 70 процентах обращений, которые мы разбирали, виновником оказывался NAT-таймаут оператора или дефолтный idle timeout, а вовсе не прокси и не сервер. Люди винят прокси, а лечение находится в паре строк настройки сокета.

Keep-alive и idle timeout: три таймаута на пути, побеждает наименьший

Вот фундаментальный принцип, который стоит вытатуировать на подкорке: на пути пакета живёт несколько независимых таймаутов бездействия, и судьбу соединения решает самый короткий из них. Это как цепь: рвётся по самому слабому звену.

Где живут таймауты

  1. Idle timeout вашего клиента. Сколько ваша программа готова ждать данных, ничего не получая. По умолчанию у разных библиотек от 30 секунд до бесконечности.
  2. Idle timeout прокси. Сколько прокси держит бездействующее соединение открытым. Типичные значения от 60 до 300 секунд.
  3. NAT-таймаут оператора. Сколько ядро сети хранит запись трансляции адресов без трафика. Для TCP это часто 300-600 секунд, но для UDP и в загруженные часы бывает и 30-60 секунд.
  4. Keep-alive таймаут целевого сервера. Сколько сервер держит соединение между запросами. Часто 5-75 секунд.

Представьте, что клиент готов ждать 120 секунд, прокси рвёт через 90, а оператор чистит NAT через 60. Кто победит? Оператор. Соединение умрёт на 60-й секунде простоя, и ни клиент, ни прокси об этом сразу не узнают.

Как измерить наименьший таймаут

Методика простая и надёжная. Устанавливаем соединение, делаем один запрос, затем замолкаем и ждём, засекая время до обрыва. Повторяем несколько раз, чтобы исключить случайность.

  1. Откройте соединение через прокси к тестовому серверу, который умеет держать keep-alive.
  2. Сделайте один короткий запрос, получите ответ.
  3. Не закрывайте соединение. Запустите таймер.
  4. Периодически (раз в секунду) проверяйте, живо ли соединение, пытаясь прочитать из сокета в неблокирующем режиме.
  5. Зафиксируйте момент, когда пришёл RST, FIN или сокет стал нечитаемым.

Проведите замер трижды. Если обрыв стабильно происходит около 60 секунд - это ваш потолок бездействия. Значит, keepalive-трафик нужно слать чаще, чем раз в 60 секунд, с хорошим запасом - например, каждые 20-25 секунд.

Стратегия победы над idle timeout

Раз наименьший таймаут решает всё, наша задача - не давать соединению простаивать дольше этого лимита. Два подхода:

  • Наполнять соединение полезным трафиком - при активной передаче данных idle-таймауты не срабатывают, потому что бездействия нет. Поэтому непрерывная скачка редко страдает от idle timeout, зато страдает от лимитов на размер и времени жизни.
  • Слать keepalive-пробы в паузах - когда полезных данных нет (например, вы ждёте генерацию отчёта на сервере), нужно поддерживать соединение искусственным трафиком. Здесь и вступают в игру два механизма keep-alive, которые мы разберём отдельно.

TCP keepalive против HTTP keep-alive: разные механизмы, нужны оба

Огромная путаница в индустрии рождается из-за похожих названий. TCP keepalive и HTTP keep-alive - это совершенно разные вещи, работающие на разных уровнях. И для устойчивого мобильного клиента нужны обе.

HTTP keep-alive: переиспользование соединения

HTTP keep-alive (он же persistent connection) - это про то, чтобы не открывать новое TCP-соединение на каждый запрос. Вместо этого одно соединение обслуживает несколько запросов подряд. Это уровень приложения.

Зачем это важно для мобильных сетей? Установка нового TCP-соединения через сотовую сеть - это дорого. Тройное рукопожатие плюс, если применяется шифрование, ещё и TLS-хендшейк. По радиоканалу с большими задержками это может занять сотни миллисекунд. Переиспользуя соединение, вы экономите это время на каждом последующем запросе.

Но! HTTP keep-alive никак не помогает от NAT-таймаута оператора в паузах. Он лишь позволяет держать соединение открытым для следующего запроса, но сам по себе не генерирует трафик в промежутках.

TCP keepalive: пульс на уровне транспорта

TCP keepalive - это механизм самого протокола TCP. Операционная система периодически отправляет пустой служебный пакет (keepalive probe), чтобы проверить, жива ли вторая сторона, и заодно освежить записи в промежуточных NAT-таблицах. Это уровень ядра ОС.

Именно TCP keepalive - ваше главное оружие против NAT-таймаутов оператора и idle-таймаутов промежуточных узлов. Каждая проба - это трафик, который сбрасывает счётчик бездействия на всём пути.

У TCP keepalive три ключевых параметра:

  • keepalive idle (или keepalive time) - через сколько секунд бездействия начать слать пробы. По умолчанию в большинстве ОС это 7200 секунд, то есть два часа. Катастрофа для мобильных сетей.
  • keepalive interval - интервал между пробами, если ответа нет.
  • keepalive count (probes) - сколько неотвеченных проб подряд считать признаком мёртвого соединения.

Рекомендуемые значения для мобильных сетей

Дефолтные два часа абсолютно не годятся. Оператор почистит NAT задолго до первой пробы. Вот рабочие значения, проверенные практикой:

  • keepalive idle: 15-25 секунд. Начинаем слать пробы после короткой паузы, чтобы гарантированно опередить самый агрессивный NAT-таймаут.
  • keepalive interval: 10-15 секунд. Если проба не дошла, повторяем через небольшой интервал.
  • keepalive count: 3-4. После трёх-четырёх неотвеченных проб признаём соединение мёртвым и переустанавливаем его, не дожидаясь бесконечного зависания.

Эта комбинация даёт двойную выгоду: соединение не рвётся оператором в паузах, а если оно всё-таки умерло (например, из-за хендовера), клиент узнает об этом через 45-70 секунд, а не через часы. Быстрое обнаружение мёртвого соединения - половина устойчивости.

Почему нужны обе настройки

Аналогия. HTTP keep-alive - это как оставить дверь переговорной забронированной на весь день, чтобы не бегать каждый раз бронировать заново. А TCP keepalive - это периодически заходить в эту переговорную, чтобы уборщик не решил, что она пустует, и не запер её. Забронировали, но не заходите - запрут. Заходите, но каждый раз бронируете заново - тратите время. Нужны оба.

MTU и фрагментация: почему в сотовых сетях пакеты меньше

Переходим к самой недооценённой причине зависаний. Если у вас мелкие ответы проходят, а большие намертво зависают - с вероятностью 90 процентов дело в MTU и сломанном PMTUD.

Что такое MTU простыми словами

MTU (Maximum Transmission Unit) - это максимальный размер одного пакета, который можно отправить в сеть без разбиения. В классическом Ethernet это 1500 байт. Представьте, что это ширина двери: мебель шире двери придётся разбирать на части, чтобы пронести.

Проблема в том, что в сотовых сетях эффективный MTU часто ниже 1500. Причина - инкапсуляция. Мобильный трафик заворачивается в дополнительные протокольные оболочки внутри сети оператора. Каждая оболочка добавляет свои байты заголовков, и на полезные данные остаётся меньше места. Реальный рабочий MTU в мобильных сетях нередко оказывается в диапазоне 1400-1480 байт, а иногда и ниже.

Что такое PMTUD и почему он ломается

PMTUD (Path MTU Discovery) - механизм автоматического определения максимального размера пакета на всём пути. Работает он так: клиент отправляет большой пакет с флагом Don't Fragment (не фрагментировать). Если по дороге встречается узел с меньшим MTU, этот узел отбрасывает пакет и присылает обратно специальное ICMP-сообщение вида fragmentation needed с указанием допустимого размера. Клиент получает подсказку и уменьшает размер пакетов.

Красивая схема. Но она держится на одном хрупком условии: ICMP-сообщения должны доходить обратно до клиента. А в реальности многие сети, файрволы и настройки безопасности блокируют ICMP целиком. И вот тут случается катастрофа под названием PMTUD black hole (чёрная дыра).

Как выглядит поломка PMTUD: анатомия зависания

Сценарий классический и очень характерный:

  1. Клиент устанавливает соединение. Рукопожатие проходит маленькими пакетами - всё отлично.
  2. Клиент шлёт короткий запрос - маленький пакет проходит, ответ приходит. Мелкие ответы работают.
  3. Сервер начинает слать большой ответ полноразмерными пакетами по 1500 байт с флагом Don't Fragment.
  4. Где-то на пути узел с MTU 1400 отбрасывает эти пакеты, потому что они больше и фрагментировать нельзя.
  5. Узел шлёт обратно ICMP fragmentation needed, но этот ICMP блокируется файрволом и не доходит.
  6. Сервер не получает подсказки, продолжает слать те же большие пакеты, они снова отбрасываются. Бесконечный цикл.
  7. Результат: маленькие пакеты (заголовки, ACK) проходят, а данные ответа - нет. Соединение зависает намертво посреди большого ответа.

Вот почему короткие запросы проходят, а большие ответы зависают. Это не мистика, это чёрная дыра PMTUD. И это, пожалуй, самый частый источник седых волос у инженеров, работающих с мобильным трафиком.

Как лечить проблему MTU

Есть несколько уровней решения, от прикладного до сетевого:

  • MSS clamping. Самый надёжный метод на уровне сети. MSS (Maximum Segment Size) - это максимальный размер сегмента TCP, о котором стороны договариваются в самом начале, во время рукопожатия. Если явно ограничить MSS так, чтобы итоговый пакет умещался в реальный MTU (например, MSS 1360-1400), сервер с самого начала будет слать пакеты нужного размера, и никакой ICMP не понадобится. Это лечит чёрную дыру в корне.
  • Уменьшение MTU интерфейса. Если вы управляете модемом или машиной, через которую идёт трафик, можно выставить MTU интерфейса в 1400 или ниже. Тогда все исходящие пакеты будут заведомо не больше безопасного размера.
  • Разрешить прохождение ICMP. Если у вас есть контроль над файрволами на пути, разрешите ICMP-сообщения типа fragmentation needed. Тогда штатный PMTUD заработает как задумано.
  • Прикладной уровень. Если вы качаете большой файл, разбивайте его на части через Range-запросы. Каждая часть - отдельная короткая передача, которая с меньшей вероятностью упрётся в проблемы больших потоков. Об этом подробно в практическом разделе.

Инсайт: даже если вы не управляете сетью, вы почти всегда можете повлиять на MSS через настройки соединения или через параметры прокси-сервиса. Хороший мобильный прокси-провайдер уже настраивает корректный MSS clamping на своей стороне, избавляя вас от чёрных дыр. Это один из критериев качества сервиса.

Смена базовой станции и переезд между сетями: почему TCP этого не переживает

Теперь про самую фундаментальную несовместимость. Сотовая сеть создана для мобильности, а TCP - нет. И это конфликт на уровне архитектуры.

Почему TCP привязан к адресу

TCP-соединение однозначно определяется четвёркой: адрес источника, порт источника, адрес назначения, порт назначения. Это как почтовый адрес соединения. Если хоть один элемент меняется, это уже другое соединение, а старое становится недействительным.

А что происходит в мобильной сети? При некоторых сценариях смены базовой станции или при переходе между технологиями (например, между разными поколениями связи или между сетями) внешний IP-адрес мобильного узла может смениться. И вот тут TCP оказывается бессилен: он не умеет продолжать соединение с новым адресом. Старое соединение просто мертво. Все данные, которые были в пути, потеряны.

Хендовер: не всегда фатально, но всегда рискованно

Справедливости ради, современные сети стараются сделать хендовер бесшовным: удержать тот же IP при переключении соты. Часто это удаётся, и вы не замечаете переключения. Но иногда происходит смена IP, короткий разрыв радиоканала на секунду-две, или всплеск потерь пакетов. Для короткого запроса это незаметно. Для десятиминутной сессии это лотерея, в которую вы играете каждый раз.

Симптомы разрыва при хендовере

  • Обрывы носят случайный характер, не привязаны к объёму данных или ко времени.
  • Частота обрывов растёт, если устройство физически движется (что типично для мобильных прокси на реальных SIM).
  • После обрыва новое соединение устанавливается нормально - сеть-то жива, умерло лишь конкретное старое соединение.

Что с этим делать: смириться и построить устойчивость

Побороть смену IP на уровне TCP невозможно. Но можно построить клиента, для которого разрыв соединения - штатная, а не аварийная ситуация. Философия проста: соединение через мобильную сеть эфемерно по определению, и клиент обязан уметь бесшовно переустанавливать его и продолжать работу с того места, где остановился.

Конкретные приёмы, которые мы реализуем в коде:

  • Идемпотентность операций. Проектируйте запросы так, чтобы их можно было безопасно повторить. Скачивание по Range идемпотентно: повторный запрос того же диапазона байт даёт тот же результат.
  • Докачка, а не перекачка. При обрыве не начинайте файл заново, а продолжайте с последнего полученного байта. Экономит трафик и время.
  • Быстрое обнаружение смерти соединения. Через агрессивные TCP keepalive, о которых мы говорили, вы узнаёте о мёртвом соединении за секунды, а не минуты.
  • Короткие транзакции. Чем короче отдельная передача, тем меньше шанс, что именно её застигнет хендовер. Разбиение большой выгрузки на части - прямое следствие этого принципа.

Практика на Python: устойчивый клиент с докачкой и таймаутами

Хватит теории, давайте строить. Начнём с Python. Соберём клиента, который: настраивает TCP keepalive, задаёт разумные таймауты, качает файл по частям через Range и умеет докачивать после обрыва.

Шаг 1: настройка TCP keepalive на сокете

Стандартные HTTP-библиотеки не выставляют агрессивный keepalive из коробки. Нужно добраться до сокета. В экосистеме requests это делается через собственный транспортный адаптер, который выставляет опции сокета: включает keepalive, задаёт idle 20 секунд, interval 10 секунд, count 3. На уровне описания логика такая: мы регистрируем адаптер, который при создании соединения устанавливает нужные параметры на низком уровне.

Смысловая часть настройки в псевдокоде на словах: включить опцию SO_KEEPALIVE, затем задать TCP_KEEPIDLE равным 20, TCP_KEEPINTVL равным 10, TCP_KEEPCNT равным 3. Названия опций слегка отличаются между операционными системами, поэтому в продакшене их выбирают с проверкой платформы.

Шаг 2: разумные таймауты

Ключевое правило: всегда задавайте таймауты явно и разделяйте таймаут соединения и таймаут чтения. Дефолт без таймаута - это ловушка, где клиент виснет навечно на полуоткрытом соединении.

  • Connect timeout: 10 секунд. Установка соединения через мобильную сеть медленнее, но 10 секунд с запасом хватает.
  • Read timeout: 30 секунд. Это таймаут на бездействие между порциями данных, а не на всю передачу. Если за 30 секунд не пришло ни байта - соединение считаем проблемным.
  • Общий бюджет операции контролируйте отдельно на уровне логики докачки, а не одним гигантским таймаутом на весь запрос.

Шаг 3: скачивание по Range с докачкой

Идея алгоритма. Мы открываем целевой файл на запись. Узнаём размер уже скачанного (если файл частично существует). Запрашиваем оставшийся диапазон через заголовок Range: bytes от текущей позиции до конца. Пишем поток в файл по мере поступления. Если ловим обрыв (исключение чтения, разрыв соединения), не паникуем: снова смотрим, сколько байт уже на диске, и повторяем запрос с новой стартовой позиции. Так продолжаем, пока файл не будет получен целиком.

Пошагово логика на словах:

  1. Определить полный размер файла запросом заголовков (HEAD или GET с Range: bytes=0-0, чтобы прочитать Content-Range).
  2. Проверить локальный размер уже скачанной части.
  3. Если локальный размер равен полному - файл готов, выходим.
  4. Иначе сформировать запрос с Range начиная с локального размера.
  5. Читать ответ порциями по 64-256 килобайт, дописывая в файл.
  6. При успешном завершении - проверить целостность (размер, при возможности контрольная сумма).
  7. При обрыве - увеличить счётчик попыток, применить короткую паузу и вернуться к пункту 2.
  8. Ограничить максимальное число попыток на транспортном уровне (например, 5-8), чтобы не крутиться вечно при системной проблеме.

Важный нюанс: убедитесь, что сервер поддерживает Range. Признак - заголовок Accept-Ranges: bytes в ответе и код 206 Partial Content на Range-запрос. Если сервер отдаёт 200 и игнорирует Range, докачка невозможна, и придётся качать целиком, но тогда особенно важны keepalive и корректный MSS.

Шаг 4: разбиение большой выгрузки

Когда вы не скачиваете, а выгружаете большой объём (например, отправляете данные или получаете большой отчёт), применяйте тот же принцип дробления. Разбейте задачу на страницы или чанки фиксированного размера. Каждый чанк - отдельная короткая транзакция с собственной обработкой ошибок. Ведите журнал прогресса: какие чанки уже подтверждены. При обрыве повторяйте только неподтверждённые. Это превращает одну хрупкую десятиминутную операцию в серию устойчивых коротких.

Что касается стратегий повторов по кодам ответа сервера - здесь мы их не рассматриваем, потому что это тема отдельного материала про 429 и экспоненциальный backoff, на который стоит перейти после этой статьи. Наш фокус - транспорт: обрыв, таймаут, размер.

Практика на Node.js: устойчивый HTTP-агент и стриминг

Теперь тот же набор принципов в экосистеме Node.js. Здесь центральную роль играет объект агента, управляющий пулом соединений.

Шаг 1: настройка агента с keep-alive

В Node создаётся HTTP или HTTPS агент с включённым keepAlive. Параметр keepAlive true заставляет агент переиспользовать соединения (это HTTP-уровень). Дополнительно задаётся keepAliveMsecs - интервал, с которым отправляются TCP keepalive-пробы на уровне сокета. Для мобильных сетей выставляйте keepAliveMsecs в районе 15000-20000 миллисекунд, чтобы опережать NAT-таймаут оператора. Также ограничьте maxSockets и maxFreeSockets, чтобы не плодить лишние соединения.

Шаг 2: таймауты на разных уровнях

В Node таймауты задаются в нескольких местах, и важно не забыть ни один:

  • Таймаут установки соединения - через опцию на запросе или через обработчик события подключения сокета.
  • Таймаут бездействия сокета - метод, устанавливающий socket timeout. При его срабатывании нужно явно уничтожить сокет и обработать это как обрыв. Node не закрывает сокет автоматически при таймауте, он лишь эмитит событие - об этом часто забывают, и соединение продолжает висеть.
  • Обработка событий error и close на запросе и на сокете - каждое из них должно вести к контролируемой логике повторной попытки.

Рекомендованные значения аналогичны Python: connect около 10 секунд, idle сокета около 30 секунд, keepalive-пробы каждые 15-20 секунд.

Шаг 3: стриминг скачивания с докачкой

В Node естественно работать с потоками. Логика докачки та же, что в Python: проверяем размер локального файла, открываем поток записи в режиме добавления, формируем запрос с заголовком Range начиная с текущего размера, подписываем обработчики на события data, end и error. По событию data пишем чанк в файл. По событию end проверяем, получен ли файл целиком. По событию error или преждевременному close, когда получено меньше ожидаемого, инициируем повтор с новой позиции.

Ключевой момент устойчивости в Node - корректная обработка преждевременного завершения потока. Событие end может прийти, даже если получено не всё, если соединение оборвалось. Поэтому всегда сверяйте фактически полученный объём с ожидаемым из заголовка Content-Length или Content-Range. Не полагайтесь только на факт срабатывания end.

Шаг 4: обёртка повторных попыток на транспортном уровне

Заверните всю операцию скачивания в цикл с ограниченным числом попыток. Между попытками - короткая пауза (для транспортных обрывов достаточно 1-3 секунд, потому что причина не в перегрузке сервера, а в сетевом событии). Считайте попытки. При исчерпании лимита пробрасывайте ошибку наверх с диагностической информацией: сколько байт получено, какой был тип ошибки, сколько попыток сделано. Эта диагностика бесценна при разборе инцидентов.

Типичные ошибки: чего делать не нужно

Разберём антипаттерны, которые мы регулярно встречаем в чужом коде. Избегайте их - и половина проблем исчезнет.

Ошибка 1: отсутствие явных таймаутов

Самая распространённая и самая болезненная. Клиент без таймаута на полуоткрытом соединении виснет навечно. Поток заблокирован, ресурс не освобождается, а вы думаете, что операция всё ещё идёт. Всегда задавайте таймауты явно. Отсутствие таймаута - это не бесконечное терпение, это скрытая бомба.

Ошибка 2: полагаться на дефолтный TCP keepalive

Дефолтные два часа делают TCP keepalive бесполезным для мобильных сетей. Многие включают SO_KEEPALIVE и успокаиваются, не подозревая, что первая проба уйдёт лишь через 7200 секунд, когда оператор давно всё почистил. Настраивайте idle, interval и count явно.

Ошибка 3: перекачивать файл целиком при каждом обрыве

Обрыв на 95 процентах и полный рестарт с нуля - это не только потерянное время, но и лишний расход мобильного трафика, который у прокси обычно тарифицируется. Докачка по Range обязательна для любых крупных загрузок.

Ошибка 4: игнорировать MTU и MSS

Люди месяцами борются с зависаниями на больших ответах, перебирая таймауты и прокси, тогда как причина - чёрная дыра PMTUD. Если большие ответы зависают, а маленькие проходят - в первую очередь проверяйте MTU и настраивайте MSS clamping. Это сэкономит вам недели.

Ошибка 5: путать транспортные обрывы с ответами сервера

Транспортный обрыв (RST, таймаут чтения, мёртвый сокет) и ответ сервера с кодом ошибки - это разные ситуации с разной стратегией реакции. Для транспортных обрывов уместны быстрые повторы с короткой паузой и докачкой. Для ответов сервера с кодами вроде 429 нужен экспоненциальный backoff - и это тема отдельной статьи. Не смешивайте эти два слоя в одном обработчике.

Ошибка 6: слишком агрессивные повторы

Бесконечный цикл повторов без ограничения при системной проблеме (например, прокси недоступен вовсе) превращается в паразитную нагрузку. Всегда ставьте потолок числа попыток и осмысленно завершайте операцию с диагностикой.

Ошибка 7: не проверять целостность результата

Факт завершения загрузки не равен факту получения корректных данных. Обрыв мог оставить усечённый файл, который по всем формальным признакам выглядит завершённым. Всегда сверяйте размер, а для важных данных - контрольную сумму.

Ошибка 8: держать одно соединение слишком долго

Чем дольше живёт соединение через мобильную сеть, тем выше кумулятивная вероятность встретить хендовер, смену IP или NAT-таймаут. Периодическая переустановка соединения - это не слабость, а разумная гигиена. Не бойтесь пересоздавать соединения.

Инструменты и ресурсы для диагностики и построения устойчивости

Правильный инструмент экономит часы. Вот арсенал, который стоит держать под рукой.

Диагностика сети и пакетов

  • Захват и анализ трафика. Инструмент захвата пакетов - ваш микроскоп. Он покажет, кто прислал RST, дошёл ли ICMP fragmentation needed, какого размера пакеты реально идут, где обрывается поток. Без него диагностика MTU и определение виновника обрыва превращаются в гадание.
  • Утилиты проверки пути. Инструменты трассировки маршрута и проверки MTU помогают понять, где на пути падает размер пакета. Есть режимы, которые целенаправленно ищут максимальный проходящий размер пакета - именно то, что нужно для настройки MSS.
  • Тестовые эхо-серверы. Простой сервер, который умеет держать keep-alive и отдавать данные заданного размера, незаменим для замера idle-таймаутов и воспроизведения проблем с большими ответами в контролируемых условиях.

Библиотеки и подходы для клиента

  • HTTP-клиенты с гибкой настройкой сокетов. Выбирайте библиотеки, которые дают доступ к параметрам соединения и позволяют выставлять таймауты и keepalive на низком уровне.
  • Механизмы стриминга. Потоковая обработка тела ответа обязательна для больших загрузок - вы не должны держать весь ответ в памяти.
  • Журналирование прогресса. Простое хранилище состояния (какие чанки получены, сколько байт на диске) превращает докачку в тривиальную задачу.

Качество прокси-инфраструктуры

Отдельно подчеркнём: значительная часть транспортных проблем решается на стороне качественного прокси-провайдера. Грамотно настроенный MSS clamping, адекватные idle-таймауты, стабильное удержание IP при хендовере, прозрачная работа с keep-alive - всё это признаки зрелого сервиса. Сервисы вроде MobileProxy.space проектируют инфраструктуру с учётом этих транспортных нюансов, что снимает с клиента часть головной боли. Но даже с идеальным прокси клиент обязан быть устойчивым - потому что радиоканал непредсказуем в принципе.

Кейсы и результаты: как это работает на практике

Разберём несколько обобщённых кейсов, отражающих типовые ситуации и их решения. Цифры усреднённые, но отражают реальные порядки величин.

Кейс 1: выгрузка каталога зависала на середине

Ситуация. Команда выгружала большой каталог товаров через мобильный прокси одним запросом. Ответ около 15 мегабайт. Стабильно зависал в районе 6-8 мегабайт, без ошибки, просто тишина, пока не срабатывал общий таймаут через несколько минут.

Диагностика. Маленькие запросы проходили идеально. Захват трафика показал, что большие пакеты уходили с флагом Don't Fragment, а ICMP fragmentation needed не возвращался. Классическая чёрная дыра PMTUD.

Решение. Настроили MSS clamping на значение, гарантирующее пакет в пределах реального MTU мобильной сети, плюс разбили выгрузку на постраничные запросы по 2 мегабайта.

Результат. Зависания исчезли полностью. Время выгрузки стало предсказуемым, а при редких обрывах повторялась лишь одна страница, а не весь каталог. Доля успешных выгрузок выросла с примерно 40 до фактически 100 процентов.

Кейс 2: соединение умирало в паузах ожидания

Ситуация. Клиент отправлял запрос на генерацию тяжёлого отчёта, сервер думал 90-120 секунд, а потом должен был отдать результат. Но соединение к моменту готовности отчёта было уже мертво.

Диагностика. Замер idle-таймаута показал стабильный обрыв около 60 секунд простоя. Виновник - NAT-таймаут оператора: во время ожидания трафика не было вообще.

Решение. Включили TCP keepalive с idle 20 секунд и interval 10. Теперь во время ожидания соединение освежалось служебными пробами каждые 20 секунд.

Результат. Соединение доживало до готовности отчёта. Обрывы в паузах прекратились. Дополнительный бонус - клиент стал за 45-60 секунд узнавать о действительно мёртвых соединениях вместо зависания на минуты.

Кейс 3: случайные обрывы при активном парсинге

Ситуация. Долгоживущие сессии парсинга обрывались хаотично, без привязки к объёму или времени. Особенно часто в определённые часы.

Диагностика. Признаки указывали на смену IP при хендовере. После обрыва новое соединение поднималось мгновенно - сеть была жива.

Решение. Переработали клиента под философию эфемерных соединений: короткие идемпотентные транзакции, журнал прогресса, быстрая переустановка соединения при обрыве, ограниченные повторы с короткой паузой.

Результат. Обрывы никуда не делись физически (побороть смену IP нельзя), но они перестали быть проблемой. Каждый обрыв стоил повтора одной короткой транзакции - доли секунды. Общая надёжность процесса выросла драматически, а инженеры перестали дежурить у логов по ночам.

Общий вывод по кейсам

Заметьте закономерность: в каждом кейсе решение лежало не в замене прокси, а в понимании транспортного уровня и правильной настройке клиента. Диагностика по симптомам вывела на конкретного виновника, а дальше лечение было точечным и быстрым.

FAQ: частые вопросы про обрывы соединений в мобильных сетях

Почему через мобильный прокси короткие запросы работают, а длинные рвутся?

Потому что длинные операции живут дольше и успевают встретить каждую из транспортных проблем: idle-таймаут в паузах, лимит на размер ответа, лимит на время жизни соединения, хендовер со сменой IP и чёрную дыру PMTUD на больших пакетах. Короткие запросы завершаются раньше, чем что-либо из этого срабатывает. Решение - настроить keepalive, MSS и внедрить докачку по Range.

Как понять, кто именно рвёт соединение - прокси, оператор или сервер?

Смотрите на почерк. Обрывы в паузах - NAT-таймаут оператора или idle прокси. Обрыв на фиксированном объёме - лимит размера у прокси или сервера. Обрыв на фиксированном времени - лимит времени жизни. Зависание на больших ответах - MTU и PMTUD. Случайные обрывы при движении - хендовер. Для точного ответа используйте захват трафика: он покажет, с какого адреса пришёл RST и дошёл ли ICMP.

В чём разница между TCP keepalive и HTTP keep-alive?

HTTP keep-alive работает на уровне приложения и позволяет переиспользовать одно соединение для нескольких запросов, экономя на установке. TCP keepalive работает на уровне ядра ОС и периодически шлёт служебные пробы, поддерживая соединение живым в паузах и освежая NAT. Первый экономит время на новых запросах, второй спасает от обрыва в бездействии. Нужны оба.

Какие значения TCP keepalive выставлять для мобильных сетей?

Отправная точка: idle 15-25 секунд, interval 10-15 секунд, count 3-4. Такая комбинация опережает агрессивные NAT-таймауты операторов и одновременно обеспечивает быстрое обнаружение мёртвых соединений. Точные значения подберите замером своего idle-таймаута на конкретном операторе и прокси.

Что такое чёрная дыра PMTUD и как её распознать?

Это ситуация, когда узел на пути отбрасывает слишком большие пакеты, а его ICMP-уведомление о необходимости уменьшить размер блокируется и не доходит до отправителя. В итоге большие пакеты бесконечно теряются, а маленькие проходят. Распознаётся по симптому: рукопожатие и мелкие ответы работают, а большие ответы зависают намертво. Лечится через MSS clamping или уменьшение MTU.

Можно ли сохранить TCP-соединение при смене базовой станции?

Если сеть удерживает тот же внешний IP при хендовере - да, соединение переживёт переключение, возможно с короткой задержкой. Если IP меняется - нет, старое TCP-соединение необратимо мертво, потому что TCP жёстко привязан к паре адресов и портов. Правильная стратегия - не пытаться сохранить соединение любой ценой, а строить клиента, который бесшовно переустанавливает соединение и продолжает с места обрыва.

Как правильно докачивать файл после обрыва?

Проверьте, поддерживает ли сервер Range (заголовок Accept-Ranges: bytes и код 206 на Range-запрос). Затем при обрыве узнавайте размер уже полученной части и запрашивайте заголовком Range остаток начиная с этого байта, дописывая в тот же файл. Повторяйте до полного получения, ограничив число попыток. В конце обязательно сверьте итоговый размер с ожидаемым.

Какие таймауты ставить в HTTP-клиенте для мобильного прокси?

Разделяйте таймаут установки соединения (около 10 секунд) и таймаут бездействия чтения (около 30 секунд, как пауза между порциями данных, а не на всю передачу). Общий бюджет операции контролируйте на уровне логики докачки, а не одним огромным таймаутом. Никогда не оставляйте таймауты неограниченными.

Нужно ли что-то менять, если стратегии повторов по кодам ответа уже настроены?

Да. Повторы по кодам ответа (например, обработка 429 с экспоненциальным backoff) - это прикладной уровень, отдельная тема. Транспортные обрывы - RST, таймауты чтения, мёртвые сокеты - требуют своей логики: быстрые повторы с короткой паузой, докачка, keepalive и корректный MSS. Эти два слоя не заменяют друг друга и должны сосуществовать в клиенте.

Влияет ли выбор прокси-провайдера на транспортную устойчивость?

Существенно. Зрелый провайдер настраивает MSS clamping, адекватные idle-таймауты, старается удерживать IP при хендовере и корректно работает с keep-alive. Это снимает часть проблем ещё до вашего кода. Но даже идеальный прокси не отменяет необходимости строить устойчивого клиента, потому что радиоканал непредсказуем в принципе.

Заключение: соединение эфемерно, но данные должны дойти

Мы прошли путь от симптома до устойчивого клиента. Давайте зафиксируем главное, чтобы эта статья стала вашей закладкой.

Первое. Разрыв долгого соединения через мобильный прокси - не мистика, а следствие вполне конкретных механизмов: idle-таймаутов на трёх уровнях, лимитов размера и времени, проблем MTU и хендовера. У каждого свой почерк, и диагностика по симптомам почти всегда выводит на виновника.

Второе. Наименьший таймаут на пути решает судьбу соединения. Измерьте его и настройте TCP keepalive агрессивнее этого лимита - idle 15-25 секунд для мобильных сетей. Не забудьте, что TCP keepalive и HTTP keep-alive - разные механизмы, и нужны оба.

Третье. Если большие ответы зависают, а маленькие проходят - это почти наверняка чёрная дыра PMTUD. Лечится MSS clamping и корректным MTU. Не тратьте недели на перебор таймаутов, проверьте размер пакета первым делом.

Четвёртое. Смену IP при хендовере победить нельзя, но можно сделать её нестрашной. Стройте клиента вокруг эфемерности соединения: короткие идемпотентные транзакции, докачка по Range, журнал прогресса, быстрая переустановка, ограниченные повторы и обязательная проверка целостности.

Ваши следующие шаги просты. Замерьте idle-таймаут на своём прокси и операторе. Включите и настройте TCP keepalive. Проверьте поведение на больших ответах и при необходимости настройте MSS. Внедрите докачку по Range. И обязательно изучите смежный материал про стратегии повторов на уровне кодов ответа - 429 и экспоненциальный backoff - чтобы закрыть и прикладной слой, а не только транспортный.

Мобильная сеть по своей природе непредсказуема. Но клиент, построенный с уважением к её природе, превращает эту непредсказуемость из источника ночных дежурств в рутинный фоновый шум. Соединение эфемерно - и это нормально. Главное, чтобы данные всё равно доходили. Теперь вы знаете, как этого добиться.