Dlaczego długie połączenia przez mobilny proxy się zrywają i jak to przetrwać
Spis treści
- Wprowadzenie: pobieranie urywa się w połowie, krótkie żądania przechodzą
- Podstawy: jak działa połączenie przez mobilny proxy
- Dogłębne zanurzenie: kto może zerwać połączenie i jak ustalić, kto dokładnie
- Keep-alive i idle timeout: trzy timeouty na trasie, wygrywa najkrótszy
- Tcp keepalive a http keep-alive: różne mechanizmy, potrzebne oba
- Mtu i fragmentacja: dlaczego w sieciach komórkowych pakiety są mniejsze
- Zmiana stacji bazowej i przejścia między sieciami: dlaczego tcp tego nie przeżywa
- Praktyka w pythonie: stabilny klient z wznawianiem i timeoutami
- Praktyka w node.js: stabilny agent http i strumieniowanie
- Typowe błędy: czego nie robić
- Narzędzia i zasoby do diagnostyki i budowania odporności
- Przypadki i rezultaty: jak to działa w praktyce
- Faq: częste pytania o zrywanie połączeń w sieciach komórkowych
- Podsumowanie: połączenie jest efemeryczne, ale dane muszą dotrzeć
Znajomy obrazek: krótkie żądania latają jak jaskółki, ale gdy uruchomisz duże wysyłanie albo pobierzesz spory plik przez mobilny proxy, połączenie urywa się w połowie. Plik na 40 procentach i cisza. Ponowienie żądania czasem pomaga, czasem nie. Najgorsze jest to, że logi milczą, serwer wydaje się żywy, a dane nie docierają. Jeśli to znasz, trafiłeś we właściwe miejsce.
Ten artykuł nie traktuje o kodach odpowiedzi ani o logice ponawiania na poziomie HTTP (o 429 i wykładniczym backoffie jest osobny materiał, do którego będziemy się odwoływać). Tutaj zajmujemy się warstwą transportową: dlaczego TCP się zrywa, kto dokładnie przerywa połączenie, jak działają trzy różne timeouty na drodze pakietu, czym jest MTU i fragmentacja w sieciach komórkowych oraz dlaczego zmiana stacji bazowej zabija długie sesje. A przede wszystkim zbudujemy klienta, który to wszystko przetrwa.
Wprowadzenie: pobieranie urywa się w połowie, krótkie żądania przechodzą
Dlaczego ten temat jest tak ważny właśnie w 2026 roku? Ponieważ mobilne proxy stały się narzędziem roboczym do scrapowania, automatyzacji, testowania i pracy z marketplace'ami. Sieć komórkowa z natury jest mniej stabilna niż przewodowa. Została zaprojektowana dla człowieka z telefonem, który otwiera stronę, czyta ją i zamyka. Nie została zaprojektowana dla maszyny, która trzyma jedno połączenie TCP otwarte przez dziesięć minut i pobiera przez nie gigabajt.
Stąd paradoks zapowiedziany w tytule sekcji. Krótkie żądania przechodzą, bo udaje im się zakończyć, zanim zadziała którykolwiek z timeoutów albo nastąpi handover (przełączenie między stacjami bazowymi). Długie operacje żyją dłużej, a więc zwiększają prawdopodobieństwo spotkania każdego z problemów: przekroczenia timeoutu, zerwania przy zmianie komórki, utraty dużego pakietu z powodu błędnego MTU.
Czego się dowiesz, czytając do końca:
- Jak w pięć minut ustalić, kto dokładnie zerwał połączenie: twój klient, proxy, operator czy serwer docelowy.
- Czym TCP keepalive różni się zasadniczo od HTTP keep-alive i dlaczego potrzebne są obie konfiguracje.
- Jak zmierzyć trzy timeouty na trasie i znaleźć ten najkrótszy, który decyduje o losie połączenia.
- Dlaczego w sieciach komórkowych MTU jest niższe niż standardowe 1500 bajtów i jak wygląda awaria PMTUD (objaw: zawieszanie się przy dużych odpowiedziach).
- Gotowy kod klienta w Pythonie i Node z dokańczaniem przez Range, dzieleniem wysyłania i poprawnymi timeoutami.
Będziemy mówić technicznym językiem, ale każdy termin wyjaśnimy po ludzku. Zaczynamy.
Podstawy: jak działa połączenie przez mobilny proxy
Zanim przejdziemy do awarii, ustalmy obraz świata. Kiedy wysyłasz żądanie przez mobilny proxy, pakiet przechodzi długi łańcuch. Zrozumienie tego łańcucha to połowa sukcesu w diagnostyce.
Droga pakietu od klienta do serwera
Wyobraź sobie trasę paczki. Przechodzi przez etapy, a na każdym z nich mogą ją opóźnić lub zgubić:
- Twój klient – program, który wysyła żądanie. Ma własne timeouty i ustawienia socketów.
- Serwer proxy – przyjmuje twoje połączenie i otwiera własne do celu. Często są to dwa różne połączenia TCP sklejone razem.
- Modem mobilny i interfejs radiowy – ten wąski i kapryśny odcinek. Tutaj pakiet leci falami radiowymi do stacji bazowej.
- Rdzeń sieci operatora – NAT, bramy, systemy priorytetyzacji ruchu.
- Publiczny internet – kanały szkieletowe do centrum danych celu.
- Serwer docelowy – punkt końcowy, który też ma własne limity długości połączenia.
Kluczowe spostrzeżenie: połączenie przez proxy to nie jedna rura, a przynajmniej dwie. Klient trzyma połączenie do proxy, a proxy trzyma połączenie do celu. Zerwanie może nastąpić na dowolnym z tych segmentów, a objawy będą różne.
Czym jest sesja i dlaczego jest krucha
Połączenie TCP to wirtualny kanał. Fizycznie nie ma żadnego przewodu między tobą a serwerem: jest umowa dwóch stron, że wymieniają ponumerowane bajty. Dopóki obie strony pamiętają numery i stan, połączenie żyje. Gdy tylko jedna ze stron zapomni o stanie (NAT się zrestartował, timeout minął, komórka się zmieniła), połączenie jest praktycznie martwe, chociaż druga strona może o tym nie wiedzieć jeszcze długo.
To właśnie tworzy podstępne półotwarte połączenia (half-open): jedna strona uważa kanał za żywy i czeka na dane, a druga dawno o wszystkim zapomniała. Klient wisi, żaden błąd nie przychodzi, czas mija. Brzmi znajomo?
Trzy poziomy, na których występują problemy
- Poziom radiowy – utrata pakietów, opóźnienia, zmiana komórki. Natura sieci komórkowej.
- Poziom sieciowy – timeouty NAT operatora, MTU, fragmentacja.
- Poziom aplikacji – timeouty serwera HTTP proxy i celu, limity rozmiaru treści odpowiedzi.
Przejdziemy teraz przez każdy z nich i nauczymy się je odróżniać.
Dogłębne zanurzenie: kto może zerwać połączenie i jak ustalić, kto dokładnie
To najważniejsza sekcja diagnostyczna. Dopóki nie znasz winowajcy, strzelasz na oślep. Dobra wiadomość: każdy winowajca ma swój charakterystyczny sposób działania.
Czterech podejrzanych
1. Twój klient. Najczęstszy i najbardziej niedoceniany winowajca. Biblioteki HTTP mają domyślne timeouty, których mogłeś nie zauważyć. Na przykład timeout na całkowity czas żądania, timeout na odczyt z socketa, timeout na bezczynność. Jeśli twój klient sam zerwał połączenie, w logach pojawi się błąd typu read timeout lub socket timeout ze wskazaniem twojego kodu w stack trace'u.
2. Serwer proxy. Proxy ma własne limity: maksymalny czas życia połączenia, idle timeout, maksymalny rozmiar odpowiedzi. Kiedy proxy zrywa połączenie, często dostajesz nagły connection reset lub zamknięcie bez odpowiedzi w trakcie przesyłania treści. Ping do proxy działa, krótkie żądania działają.
3. Operator sieci. Najmniej widoczny. Tabele NAT operatora mają czas życia wpisu. Jeśli przez połączenie długo nie ma ruchu, operator usuwa wpis z NAT i kolejnych pakietów nie ma gdzie dostarczyć. Objaw: połączenie zawiesza się właśnie podczas pauz, a nie podczas aktywnego przesyłania. Operator jest też winowajcą przy zmianie stacji bazowej.
4. Serwer docelowy. Ma własne ustawienia keep-alive i limity. Wiele serwerów zamyka połączenie po N żądaniach lub po T sekundach. Objaw: serwer wysyła nagłówek Connection: close lub poprawnie kończy połączenie (FIN), a nie zrywa (RST).
Diagnostyka po objawach: tabela wzorców
Omówimy charakterystyczne oznaki, abyś mógł w kilka minut ustalić winowajcę.
- Zerwanie dokładnie podczas pauz, podczas aktywnego przesyłania wszystko działa – prawie na pewno timeout NAT operatora lub idle timeout proxy. Leczy się ruchem keepalive.
- Zerwanie zawsze mniej więcej przy tej samej objętości danych (na przykład około 8 albo 10 megabajtów) – limit rozmiaru odpowiedzi po stronie proxy lub serwera. Leczy się dzieleniem przez Range.
- Zerwanie zawsze mniej więcej po tym samym czasie (na przykład dokładnie 60 lub 300 sekund) – twardy limit długości połączenia. Szukaj najkrótszego z timeoutów.
- Zawieszanie się właśnie przy dużych odpowiedziach, małe przechodzą – klasyczna awaria PMTUD i problem z MTU. O tym będzie cała sekcja poniżej.
- Losowe zerwania bez związku z objętością i czasem, częstsze podczas ruchu – zmiana stacji bazowej, handover, degradacja sygnału radiowego.
- Natychmiastowy RST przy próbie przesłania danych – połączenie jest już martwe po jednej stronie (półotwarte) albo aktywne zerwanie przez proxy.
Narzędzia podstawowej diagnostyki
Aby odróżnić FIN (grzeczne zamknięcie) od RST (brutalne zerwanie) i ustalić, na którym segmencie następuje zerwanie, stosuj:
- Logowanie na poziomie socketa – zapisuj dokładny czas zerwania, liczbę przesłanych bajtów, typ wyjątku.
- Analiza ruchu – narzędzie do przechwytywania pakietów pokaże, kto wysłał RST lub FIN. Jeśli RST przychodzi z adresu proxy – winne jest proxy. Jeśli połączenie po prostu milknie bez pakietów – winny jest węzeł pośredni (operator).
- Pomiary kontrolne – powtórz tę samą operację przez połączenie przewodowe bezpośrednio. Jeśli po kablu wszystko jest stabilne, a przez mobilny proxy się zrywa, problem leży w segmencie mobilnym.
Spostrzeżenie autora: w 70 procentach zgłoszeń, które analizowaliśmy, winowajcą okazywał się timeout NAT operatora albo domyślny idle timeout, a nie proxy ani serwer. Ludzie obwiniają proxy, a rozwiązanie to kilka linijek konfiguracji socketa.
Keep-alive i idle timeout: trzy timeouty na trasie, wygrywa najkrótszy
Oto fundamentalna zasada, którą warto sobie wypalić w pamięci: na drodze pakietu żyje kilka niezależnych timeoutów bezczynności, a o losie połączenia decyduje najkrótszy z nich. To jak łańcuch: zrywa się w najsłabszym ogniwie.
Gdzie mieszkają timeouty
- Idle timeout twojego klienta. Jak długo twój program jest gotów czekać na dane, nic nie otrzymując. Domyślnie w różnych bibliotekach od 30 sekund do nieskończoności.
- Idle timeout proxy. Jak długo proxy trzyma otwarte nieaktywne połączenie. Typowe wartości od 60 do 300 sekund.
- Timeout NAT operatora. Jak długo rdzeń sieci przechowuje wpis translacji adresów bez ruchu. Dla TCP to często 300–600 sekund, ale dla UDP i w godzinach szczytu bywa to 30–60 sekund.
- Keep-alive timeout serwera docelowego. Jak długo serwer trzyma połączenie między żądaniami. Często 5–75 sekund.
Wyobraź sobie, że klient jest gotów czekać 120 sekund, proxy zrywa po 90, a operator czyści NAT po 60. Kto wygrywa? Operator. Połączenie umrze w 60. sekundzie bezczynności i ani klient, ani proxy nie dowiedzą się o tym od razu.
Jak zmierzyć najkrótszy timeout
Metoda jest prosta i niezawodna. Ustanawiamy połączenie, wysyłamy jedno żądanie, potem milkniemy i czekamy, mierząc czas do zerwania. Powtarzamy kilka razy, aby wykluczyć przypadek.
- Otwórz połączenie przez proxy do testowego serwera, który potrafi utrzymać keep-alive.
- Wyślij jedno krótkie żądanie, odbierz odpowiedź.
- Nie zamykaj połączenia. Uruchom timer.
- Okresowo (raz na sekundę) sprawdzaj, czy połączenie żyje, próbując czytać z socketa w trybie nieblokującym.
- Zanotuj moment, w którym przyszedł RST, FIN albo socket stał się nieczytelny.
Wykonaj pomiar trzy razy. Jeśli zerwanie następuje stabilnie po około 60 sekundach – to twój limit bezczynności. Oznacza to, że ruch keepalive trzeba wysyłać częściej niż raz na 60 sekund, z dobrym zapasem – na przykład co 20–25 sekund.
Strategia pokonania idle timeout
Skoro najkrótszy timeout decyduje o wszystkim, naszym zadaniem jest nie dopuścić, aby połączenie bezczynnie czekało dłużej niż ten limit. Są dwa podejścia:
- Wypełniać połączenie użytecznym ruchem – przy aktywnej transmisji danych idle-timeouty nie działają, bo nie ma bezczynności. Dlatego ciągłe pobieranie rzadko cierpi z powodu idle timeout, za to cierpi z powodu limitów rozmiaru i czasu życia.
- Wysyłać próby keepalive w pauzach – gdy nie ma użytecznych danych (na przykład czekasz na wygenerowanie raportu na serwerze), trzeba podtrzymywać połączenie sztucznym ruchem. Tutaj w grę wchodzą dwa mechanizmy keep-alive, które omówimy osobno.
TCP keepalive a HTTP keep-alive: różne mechanizmy, potrzebne oba
Ogromne zamieszanie w branży wynika z podobnych nazw. TCP keepalive i HTTP keep-alive to zupełnie różne rzeczy, działające na różnych poziomach. Do stabilnego mobilnego klienta potrzebne są obie.
HTTP keep-alive: ponowne wykorzystanie połączenia
HTTP keep-alive (inaczej persistent connection) polega na tym, aby nie otwierać nowego połączenia TCP dla każdego żądania. Zamiast tego jedno połączenie obsługuje kilka żądań pod rząd. To poziom aplikacji.
Dlaczego to ważne w sieciach komórkowych? Ustanowienie nowego połączenia TCP przez sieć komórkową jest kosztowne. Trójstopniowe uzgadnianie (three-way handshake) plus, jeśli stosowane jest szyfrowanie, jeszcze TLS-handshake. W kanale radiowym z dużymi opóźnieniami może to zająć setki milisekund. Ponownie wykorzystując połączenie, oszczędzasz ten czas przy każdym kolejnym żądaniu.
Ale! HTTP keep-alive w żaden sposób nie pomaga na timeout NAT operatora w pauzach. Pozwala jedynie trzymać połączenie otwarte na następne żądanie, ale samo w sobie nie generuje ruchu w międzyczasie.
TCP keepalive: puls na poziomie transportu
TCP keepalive to mechanizm samego protokołu TCP. System operacyjny okresowo wysyła pusty pakiet kontrolny (keepalive probe), aby sprawdzić, czy druga strona żyje, a przy okazji odświeżyć wpisy w pośrednich tabelach NAT. To poziom jądra systemu operacyjnego.
To właśnie TCP keepalive jest twoją główną bronią przeciwko timeoutom NAT operatora i idle-timeoutom węzłów pośrednich. Każda próba to ruch, który resetuje licznik bezczynności na całej trasie.
TCP keepalive ma trzy kluczowe parametry:
- keepalive idle (lub keepalive time) – po ilu sekundach bezczynności zacząć wysyłać próby. Domyślnie w większości systemów operacyjnych to 7200 sekund, czyli dwie godziny. Katastrofa dla sieci komórkowych.
- keepalive interval – odstęp między próbami, jeśli nie ma odpowiedzi.
- keepalive count (probes) – ile nieodebranych prób pod rząd uznać za oznakę martwego połączenia.
Zalecane wartości dla sieci komórkowych
Domyślne dwie godziny zupełnie się nie nadają. Operator wyczyści NAT na długo przed pierwszą próbą. Oto działające wartości, sprawdzone w praktyce:
- keepalive idle: 15–25 sekund. Zaczynamy wysyłać próby po krótkiej pauzie, aby na pewno wyprzedzić najbardziej agresywny timeout NAT.
- keepalive interval: 10–15 sekund. Jeśli próba nie dotarła, powtarzamy po krótkim odstępie.
- keepalive count: 3–4. Po trzech–czterech nieodebranych próbach uznajemy połączenie za martwe i ustanawiamy je ponownie, nie czekając na nieskończone zawieszenie.
Ta kombinacja daje podwójną korzyść: połączenie nie jest zrywane przez operatora w pauzach, a jeśli już umrze (na przykład z powodu handoveru), klient dowie się o tym po 45–70 sekundach, a nie po godzinach. Szybkie wykrycie martwego połączenia to połowa stabilności.
Dlaczego potrzebne są obie konfiguracje
Analogia. HTTP keep-alive to jak zarezerwowanie sali konferencyjnej na cały dzień, żeby nie trzeba było jej rezerwować za każdym razem. A TCP keepalive to okresowe zaglądanie do tej sali, żeby sprzątaczka nie uznała, że jest pusta, i jej nie zamknęła. Zarezerwowałeś, ale nie zaglądasz – zamkną. Zaglądasz, ale za każdym razem rezerwujesz od nowa – tracisz czas. Potrzebne są oba.
MTU i fragmentacja: dlaczego w sieciach komórkowych pakiety są mniejsze
Przechodzimy do najbardziej niedocenianej przyczyny zawieszeń. Jeśli małe odpowiedzi przechodzą, a duże zawieszają się na amen – z prawdopodobieństwem 90 procent chodzi o MTU i zepsute PMTUD.
Czym jest MTU w prostych słowach
MTU (Maximum Transmission Unit) – to maksymalny rozmiar pojedynczego pakietu, który można wysłać do sieci bez dzielenia. W klasycznym Ethernetie to 1500 bajtów. Wyobraź sobie, że to szerokość drzwi: meble szersze niż drzwi trzeba rozłożyć na części, żeby je wnieść.
Problem polega na tym, że w sieciach komórkowych efektywne MTU często jest niższe niż 1500. Powodem jest enkapsulacja. Ruch mobilny jest zawijany w dodatkowe powłoki protokołowe wewnątrz sieci operatora. Każda powłoka dodaje swoje bajty nagłówków, a na dane użyteczne zostaje mniej miejsca. Realne robocze MTU w sieciach komórkowych często wynosi od 1400 do 1480 bajtów, a czasem jeszcze mniej.
Czym jest PMTUD i dlaczego się psuje
PMTUD (Path MTU Discovery) – to mechanizm automatycznego określania maksymalnego rozmiaru pakietu na całej trasie. Działa tak: klient wysyła duży pakiet z flagą Don't Fragment (nie fragmentuj). Jeśli po drodze napotka węzeł z mniejszym MTU, ten węzeł odrzuca pakiet i odsyła specjalną wiadomość ICMP typu fragmentation needed z informacją o dopuszczalnym rozmiarze. Klient otrzymuje podpowiedź i zmniejsza rozmiar pakietów.
Piękny schemat. Ale trzyma się jednego kruchego warunku: wiadomości ICMP muszą wracać do klienta. W rzeczywistości wiele sieci, firewalli i ustawień bezpieczeństwa blokuje ICMP w całości. I tutaj dochodzi do katastrofy zwanej PMTUD black hole (czarna dziura).
Jak wygląda awaria PMTUD: anatomia zawieszenia
Scenariusz klasyczny i bardzo charakterystyczny:
- Klient ustanawia połączenie. Uzgadnianie przechodzi małymi pakietami – wszystko działa.
- Klient wysyła krótkie żądanie – mały pakiet przechodzi, odpowiedź przychodzi. Małe odpowiedzi działają.
- Serwer zaczyna wysyłać dużą odpowiedź w pełnowymiarowych pakietach po 1500 bajtów z flagą Don't Fragment.
- Gdzieś po drodze węzeł z MTU 1400 odrzuca te pakiety, bo są za duże i nie można ich fragmentować.
- Węzeł wysyła z powrotem ICMP fragmentation needed, ale ten ICMP jest blokowany przez firewall i nie dociera.
- Serwer nie otrzymuje podpowiedzi, nadal wysyła te same duże pakiety, a one są ponownie odrzucane. Nieskończona pętla.
- Efekt: małe pakiety (nagłówki, ACK) przechodzą, a dane odpowiedzi – nie. Połączenie zawiesza się na amen w środku dużej odpowiedzi.
Oto dlaczego krótkie żądania przechodzą, a duże odpowiedzi zawieszają się. To nie magia, to czarna dziura PMTUD. I to chyba najczęstsze źródło siwych włosów u inżynierów pracujących z ruchem mobilnym.
Jak leczyć problem z MTU
Istnieje kilka poziomów rozwiązań, od aplikacyjnego po sieciowy:
- MSS clamping. Najbardziej niezawodna metoda na poziomie sieci. MSS (Maximum Segment Size) – to maksymalny rozmiar segmentu TCP, o którym strony umawiają się na samym początku, podczas uzgadniania. Jeśli jawnie ograniczysz MSS tak, aby końcowy pakiet mieścił się w rzeczywistym MTU (na przykład MSS 1360–1400), serwer od samego początku będzie wysyłał pakiety odpowiedniego rozmiaru i żaden ICMP nie będzie potrzebny. To leczy czarną dziurę u źródła.
- Zmniejszenie MTU interfejsu. Jeśli zarządzasz modemem lub maszyną, przez którą idzie ruch, możesz ustawić MTU interfejsu na 1400 lub mniej. Wtedy wszystkie pakiety wychodzące będą z góry nie większe niż bezpieczny rozmiar.
- Zezwolić na przechodzenie ICMP. Jeśli masz kontrolę nad firewallami na trasie, zezwól na wiadomości ICMP typu fragmentation needed. Wtedy standardowy PMTUD zadziała zgodnie z przeznaczeniem.
- Poziom aplikacji. Jeśli pobierasz duży plik, dziel go na części przez żądania Range. Każda część to osobna krótka transmisja, która ma mniejsze szanse natrafić na problemy z dużymi strumieniami. O tym szczegółowo w sekcji praktycznej.
Spostrzeżenie: nawet jeśli nie zarządzasz siecią, prawie zawsze możesz wpłynąć na MSS przez ustawienia połączenia lub parametry usługi proxy. Dobry dostawca mobilnego proxy już konfiguruje poprawny MSS clamping po swojej stronie, oszczędzając ci czarnych dziur. To jedno z kryteriów jakości usługi.
Zmiana stacji bazowej i przejścia między sieciami: dlaczego TCP tego nie przeżywa
Teraz o najbardziej fundamentalnej niezgodności. Sieć komórkowa została stworzona z myślą o mobilności, a TCP – nie. I to jest konflikt na poziomie architektury.
Dlaczego TCP jest przywiązany do adresu
Połączenie TCP jest jednoznacznie określone przez czwórkę: adres źródłowy, port źródłowy, adres docelowy, port docelowy. To jak adres pocztowy połączenia. Jeśli zmienia się którykolwiek element, to już inne połączenie, a stare staje się nieważne.
A co dzieje się w sieci mobilnej? W niektórych scenariuszach zmiany stacji bazowej lub przy przejściu między technologiami (na przykład między różnymi generacjami łączności lub między sieciami) zewnętrzny adres IP węzła mobilnego może się zmienić. I tutaj TCP okazuje się bezradny: nie potrafi kontynuować połączenia z nowym adresem. Stare połączenie jest po prostu martwe. Wszystkie dane, które były w drodze, są utracone.
Handover: nie zawsze fatalny, ale zawsze ryzykowny
Gwoli sprawiedliwości, nowoczesne sieci starają się, aby handover był płynny: utrzymać ten sam IP przy przełączaniu komórki. Często się to udaje i nie zauważasz przełączenia. Ale czasem dochodzi do zmiany IP, krótkiego zerwania łącza radiowego na sekundę – dwie albo skoku utraty pakietów. Dla krótkiego żądania to niezauważalne. Dla dziesięciominutowej sesji to loteria, w którą grasz za każdym razem.
Objawy zerwania podczas handoveru
- Zerwania mają charakter losowy, nie są związane z objętością danych ani z czasem.
- Częstotliwość zerwań rośnie, jeśli urządzenie fizycznie się porusza (co jest typowe dla mobilnych proxy na prawdziwych kartach SIM).
- Po zerwaniu nowe połączenie ustanawia się normalnie – sieć żyje, umarło tylko konkretne stare połączenie.
Co z tym zrobić: pogodzić się i zbudować odporność
Pokonanie zmiany IP na poziomie TCP jest niemożliwe. Ale można zbudować klienta, dla którego zerwanie połączenia jest sytuacją normalną, a nie awaryjną. Filozofia jest prosta: połączenie przez sieć komórkową jest z definicji efemeryczne, a klient musi umieć płynnie je odtwarzać i kontynuować pracę od miejsca, w którym się zatrzymał.
Konkretne techniki, które zaimplementujemy w kodzie:
- Idempotentność operacji. Projektuj żądania tak, aby można je było bezpiecznie powtórzyć. Pobieranie przez Range jest idempotentne: ponowne żądanie tego samego zakresu bajtów daje ten sam wynik.
- Wznawianie, a nie pobieranie od nowa. Przy zerwaniu nie zaczynaj pliku od początku, tylko kontynuuj od ostatniego otrzymanego bajtu. Oszczędza ruch i czas.
- Szybkie wykrywanie śmierci połączenia. Dzięki agresywnemu TCP keepalive, o którym mówiliśmy, dowiadujesz się o martwym połączeniu w sekundy, a nie minuty.
- Krótkie transakcje. Im krótsza pojedyncza transmisja, tym mniejsza szansa, że to właśnie ją złapie handover. Dzielenie dużego wysyłania na części jest bezpośrednią konsekwencją tej zasady.
Praktyka w Pythonie: stabilny klient z wznawianiem i timeoutami
Koniec teorii, budujmy. Zaczynamy od Pythona. Złożymy klienta, który: konfiguruje TCP keepalive, ustawia rozsądne timeouty, pobiera plik fragmentami przez Range i potrafi wznowić pobieranie po zerwaniu.
Krok 1: konfiguracja TCP keepalive na sockecie
Standardowe biblioteki HTTP nie ustawiają agresywnego keepalive od razu. Trzeba dostać się do socketa. W ekosystemie requests robi się to przez własny adapter transportowy, który ustawia opcje socketa: włącza keepalive, ustawia idle 20 sekund, interval 10 sekund, count 3. Na poziomie opisu logika jest taka: rejestrujemy adapter, który przy tworzeniu połączenia ustawia potrzebne parametry na niskim poziomie.
Sedno konfiguracji w pseudokodzie słownym: włącz opcję SO_KEEPALIVE, następnie ustaw TCP_KEEPIDLE na 20, TCP_KEEPINTVL na 10, TCP_KEEPCNT na 3. Nazwy opcji nieco się różnią między systemami operacyjnymi, dlatego w produkcji wybiera się je z uwzględnieniem platformy.
Krok 2: rozsądne timeouty
Kluczowa zasada: zawsze ustawiaj timeouty jawnie i rozdzielaj timeout połączenia od timeoutu odczytu. Domyślny brak timeoutu to pułapka, w której klient wisi w nieskończoność na półotwartym połączeniu.
- Connect timeout: 10 sekund. Ustanawianie połączenia przez sieć komórkową jest wolniejsze, ale 10 sekund z zapasem wystarcza.
- Read timeout: 30 sekund. To timeout na bezczynność między porcjami danych, a nie na całą transmisję. Jeśli przez 30 sekund nie przyszedł ani jeden bajt – uznajemy połączenie za problematyczne.
- Ogólny budżet operacji kontroluj osobno na poziomie logiki wznawiania, a nie jednym gigantycznym timeoutem na całe żądanie.
Krok 3: pobieranie przez Range z wznawianiem
Idea algorytmu. Otwieramy plik docelowy do zapisu. Sprawdzamy rozmiar już pobranej części (jeśli plik częściowo istnieje). Żądamy pozostałego zakresu przez nagłówek Range: bytes od bieżącej pozycji do końca. Zapisujemy strumień do pliku na bieżąco. Jeśli złapiemy zerwanie (wyjątek odczytu, przerwanie połączenia), nie panikujemy: ponownie sprawdzamy, ile bajtów jest już na dysku, i powtarzamy żądanie od nowej pozycji startowej. Tak kontynuujemy, aż plik zostanie pobrany w całości.
Krok po kroku logika słownie:
- Określ pełny rozmiar pliku żądaniem nagłówków (HEAD lub GET z Range: bytes=0-0, aby odczytać Content-Range).
- Sprawdź lokalny rozmiar już pobranej części.
- Jeśli lokalny rozmiar jest równy pełnemu – plik gotowy, kończymy.
- W przeciwnym razie utwórz żądanie z Range zaczynające się od lokalnego rozmiaru.
- Czytaj odpowiedź porcjami po 64–256 kilobajtów, dopisując do pliku.
- Po pomyślnym zakończeniu – sprawdź integralność (rozmiar, a jeśli to możliwe, sumę kontrolną).
- Przy zerwaniu – zwiększ licznik prób, zastosuj krótką pauzę i wróć do punktu 2.
- Ogranicz maksymalną liczbę prób na poziomie transportowym (na przykład 5–8), aby nie kręcić się w kółko w nieskończoność przy problemie systemowym.
Ważny niuans: upewnij się, że serwer obsługuje Range. Oznaką jest nagłówek Accept-Ranges: bytes w odpowiedzi oraz kod 206 Partial Content na żądanie Range. Jeśli serwer zwraca 200 i ignoruje Range, wznawianie nie jest możliwe i trzeba pobierać całość, ale wtedy szczególnie ważne są keepalive i poprawny MSS.
Krok 4: dzielenie dużego wysyłania
Kiedy nie pobierasz, ale wysyłasz dużą objętość (na przykład przesyłasz dane albo otrzymujesz duży raport), stosuj tę samą zasadę dzielenia. Podziel zadanie na strony albo fragmenty o stałym rozmiarze. Każdy fragment to osobna krótka transakcja z własną obsługą błędów. Prowadź dziennik postępu: które fragmenty zostały potwierdzone. Przy zerwaniu powtarzaj tylko niepotwierdzone. To zamienia jedną kruchą dziesięciominutową operację w serię odpornych krótkich.
Jeśli chodzi o strategie ponawiania w zależności od kodów odpowiedzi serwera – nie omawiamy ich tutaj, bo to temat osobnego materiału o 429 i wykładniczym backoffie, do którego warto przejść po tym artykule. Naszym celem jest transport: zerwanie, timeout, rozmiar.
Praktyka w Node.js: stabilny agent HTTP i strumieniowanie
Teraz ten sam zestaw zasad w ekosystemie Node.js. Tutaj centralną rolę odgrywa obiekt agenta zarządzający pulą połączeń.
Krok 1: konfiguracja agenta z keep-alive
W Node tworzy się agent HTTP lub HTTPS z włączonym keepAlive. Parametr keepAlive true wymusza na agencie ponowne wykorzystywanie połączeń (to poziom HTTP). Dodatkowo ustawia się keepAliveMsecs – interwał, z jakim wysyłane są próby TCP keepalive na poziomie socketa. Dla sieci komórkowych ustawiaj keepAliveMsecs w okolicach 15000–20000 milisekund, aby wyprzedzić timeout NAT operatora. Ogranicz też maxSockets i maxFreeSockets, żeby nie mnożyć zbędnych połączeń.
Krok 2: timeouty na różnych poziomach
W Node timeouty ustawia się w kilku miejscach i ważne jest, aby nie zapomnieć o żadnym:
- Timeout ustanawiania połączenia – przez opcję na żądaniu albo przez handler zdarzenia podłączenia socketa.
- Timeout bezczynności socketa – metoda ustawiająca socket timeout. Po jego zadziałaniu trzeba jawnie zniszczyć socket i potraktować to jako zerwanie. Node nie zamyka socketa automatycznie po timeoutcie, tylko emituje zdarzenie – o tym często się zapomina i połączenie dalej wisi.
- Obsługa zdarzeń error i close na żądaniu i na sockecie – każde z nich powinno prowadzić do kontrolowanej logiki ponowienia próby.
Zalecane wartości są analogiczne do Pythona: connect około 10 sekund, idle socketa około 30 sekund, próby keepalive co 15–20 sekund.
Krok 3: strumieniowe pobieranie z wznawianiem
W Node naturalne jest pracowanie ze strumieniami. Logika wznawiania jest taka sama jak w Pythonie: sprawdzamy rozmiar lokalnego pliku, otwieramy strumień zapisu w trybie dopisywania, tworzymy żądanie z nagłówkiem Range zaczynające się od bieżącego rozmiaru, podpinamy handlery pod zdarzenia data, end i error. Przy zdarzeniu data zapisujemy chunk do pliku. Przy zdarzeniu end sprawdzamy, czy plik został pobrany w całości. Przy zdarzeniu error lub przedwczesnym close, gdy otrzymano mniej niż oczekiwano, inicjujemy ponowienie od nowej pozycji.
Kluczowym momentem odporności w Node jest poprawna obsługa przedwczesnego zakończenia strumienia. Zdarzenie end może przyjść, nawet jeśli nie otrzymano wszystkiego, gdy połączenie zostało zerwane. Dlatego zawsze porównuj faktycznie otrzymaną objętość z oczekiwaną z nagłówka Content-Length lub Content-Range. Nie polegaj tylko na tym, że end zadziałał.
Krok 4: opakowanie ponowień na poziomie transportowym
Owiń całą operację pobierania w pętlę z ograniczoną liczbą prób. Między próbami – krótka pauza (dla zerwań transportowych wystarczy 1–3 sekundy, bo przyczyna nie leży w przeciążeniu serwera, tylko w zdarzeniu sieciowym). Licz próby. Po wyczerpaniu limitu wyrzuć błąd wyżej z informacją diagnostyczną: ile bajtów otrzymano, jaki był typ błędu, ile prób wykonano. Ta diagnostyka jest bezcenna przy analizie incydentów.
Typowe błędy: czego nie robić
Omówimy antywzorce, które regularnie spotykamy w cudzym kodzie. Unikaj ich – a połowa problemów zniknie.
Błąd 1: brak jawnych timeoutów
Najczęstszy i najbardziej bolesny. Klient bez timeoutu na półotwartym połączeniu wisi w nieskończoność. Wątek jest zablokowany, zasób nie jest zwalniany, a ty myślisz, że operacja nadal trwa. Zawsze ustawiaj timeouty jawnie. Brak timeoutu to nie nieskończona cierpliwość, to ukryta bomba.
Błąd 2: poleganie na domyślnym TCP keepalive
Domyślne dwie godziny czynią TCP keepalive bezużytecznym dla sieci komórkowych. Wiele osób włącza SO_KEEPALIVE i spoczywa na laurach, nie podejrzewając, że pierwsza próba pójdzie dopiero po 7200 sekundach, gdy operator dawno wszystko wyczyścił. Ustawiaj idle, interval i count jawnie.
Błąd 3: pobieranie pliku od nowa przy każdym zerwaniu
Zerwanie na 95 procentach i pełny restart od zera to nie tylko stracony czas, ale też dodatkowe zużycie ruchu mobilnego, który u proxy zwykle jest taryfikowany. Wznawianie przez Range jest obowiązkowe przy każdym dużym pobieraniu.
Błąd 4: ignorowanie MTU i MSS
Ludzie miesiącami walczą z zawieszaniem się na dużych odpowiedziach, przerzucając timeouty i proxy, podczas gdy przyczyną jest czarna dziura PMTUD. Jeśli duże odpowiedzi się zawieszają, a małe przechodzą – w pierwszej kolejności sprawdzaj MTU i konfiguruj MSS clamping. To zaoszczędzi ci tygodni.
Błąd 5: mylenie zerwań transportowych z odpowiedziami serwera
Zerwanie transportowe (RST, timeout odczytu, martwy socket) i odpowiedź serwera z kodem błędu to różne sytuacje z różną strategią reakcji. Przy zerwaniach transportowych sprawdzają się szybkie ponowienia z krótką pauzą i wznawianiem. Przy odpowiedziach serwera z kodami takimi jak 429 potrzebny jest wykładniczy backoff – i to temat osobnego artykułu. Nie mieszaj tych dwóch warstw w jednym handlerze.
Błąd 6: zbyt agresywne ponawianie
Nieskończona pętla ponowień bez limitu przy problemie systemowym (na przykład gdy proxy jest w ogóle niedostępne) zamienia się w pasożytnicze obciążenie. Zawsze ustalaj górny limit liczby prób i sensownie kończ operację z diagnostyką.
Błąd 7: niesprawdzanie integralności wyniku
Fakt zakończenia pobierania nie jest równoznaczny z otrzymaniem poprawnych danych. Zerwanie mogło zostawić obcięty plik, który pod każdym formalnym względem wygląda na kompletny. Zawsze porównuj rozmiar, a dla ważnych danych – sumę kontrolną.
Błąd 8: trzymanie jednego połączenia zbyt długo
Im dłużej żyje połączenie przez sieć komórkową, tym większe skumulowane prawdopodobieństwo spotkania handoveru, zmiany IP lub timeoutu NAT. Okresowe ponowne ustanawianie połączenia to nie słabość, tylko rozsądna higiena. Nie bój się odtwarzać połączeń.
Narzędzia i zasoby do diagnostyki i budowania odporności
Odpowiednie narzędzie oszczędza godziny. Oto arsenał, który warto mieć pod ręką.
Diagnostyka sieci i pakietów
- Przechwytywanie i analiza ruchu. Narzędzie do przechwytywania pakietów to twój mikroskop. Pokaże, kto wysłał RST, czy dotarł ICMP fragmentation needed, jakie rozmiary pakietów faktycznie lecą i gdzie urywa się strumień. Bez niego diagnostyka MTU i ustalanie winowajcy zerwania zamieniają się w zgadywanie.
- Narzędzia do sprawdzania trasy. Narzędzia do traceroute i sprawdzania MTU pomagają zrozumieć, gdzie na trasie spada rozmiar pakietu. Są tryby, które celowo szukają maksymalnego przechodzącego rozmiaru pakietu – dokładnie to, czego potrzebujesz do konfiguracji MSS.
- Testowe serwery echo. Prosty serwer, który potrafi utrzymać keep-alive i oddawać dane o zadanym rozmiarze, jest niezastąpiony do mierzenia idle-timeoutów i odtwarzania problemów z dużymi odpowiedziami w kontrolowanych warunkach.
Biblioteki i podejścia dla klienta
- Klienty HTTP z elastyczną konfiguracją socketów. Wybieraj biblioteki, które dają dostęp do parametrów połączenia i pozwalają ustawiać timeouty oraz keepalive na niskim poziomie.
- Mechanizmy strumieniowania. Strumieniowe przetwarzanie treści odpowiedzi jest obowiązkowe przy dużych pobieraniach – nie powinieneś trzymać całej odpowiedzi w pamięci.
- Logowanie postępu. Prosty magazyn stanu (które fragmenty zostały odebrane, ile bajtów jest na dysku) zamienia wznawianie w trywialne zadanie.
Jakość infrastruktury proxy
Osobno podkreślimy: znaczna część problemów transportowych rozwiązuje się po stronie dobrego dostawcy proxy. Poprawnie skonfigurowany MSS clamping, odpowiednie idle-timeouty, stabilne utrzymywanie IP podczas handoveru, przejrzysta praca z keep-alive – to wszystko oznaki dojrzałej usługi. Usługi takie jak MobileProxy.space projektują infrastrukturę z uwzględnieniem tych niuansów transportowych, co zdejmuje z klienta część bólu głowy. Ale nawet z idealnym proxy klient musi być odporny – bo kanał radiowy jest z zasady nieprzewidywalny.
Przypadki i rezultaty: jak to działa w praktyce
Omówimy kilka uogólnionych przypadków odzwierciedlających typowe sytuacje i ich rozwiązania. Liczby są uśrednione, ale oddają rzeczywiste rzędy wielkości.
Przypadek 1: wysyłanie katalogu zawieszało się w połowie
Sytuacja. Zespół wysyłał duży katalog produktów przez mobilny proxy jednym żądaniem. Odpowiedź około 15 megabajtów. Stabilnie zawieszała się w okolicach 6–8 megabajtów, bez błędu, po prostu cisza, dopóki po kilku minutach nie zadziałał ogólny timeout.
Diagnostyka. Małe żądania przechodziły idealnie. Przechwycenie ruchu pokazało, że duże pakiety wychodziły z flagą Don't Fragment, a ICMP fragmentation needed nie wracał. Klasyczna czarna dziura PMTUD.
Rozwiązanie. Skonfigurowaliśmy MSS clamping na wartość gwarantującą pakiet w granicach rzeczywistego MTU sieci komórkowej, a do tego podzieliliśmy wysyłanie na żądania stron po 2 megabajty.
Rezultat. Zawieszania zniknęły całkowicie. Czas wysyłania stał się przewidywalny, a przy rzadkich zerwaniach powtarzana była tylko jedna strona, a nie cały katalog. Odsetek udanych wysyłań wzrósł z około 40 do właściwie 100 procent.
Przypadek 2: połączenie umierało w pauzach oczekiwania
Sytuacja. Klient wysyłał żądanie wygenerowania ciężkiego raportu, serwer myślał 90–120 sekund, a potem miał zwrócić wynik. Ale połączenie w momencie gotowości raportu było już martwe.
Diagnostyka. Pomiar idle-timeoutu pokazał stabilne zerwanie po około 60 sekundach bezczynności. Winowajca – timeout NAT operatora: w czasie oczekiwania nie było żadnego ruchu.
Rozwiązanie. Włączyliśmy TCP keepalive z idle 20 sekund i interval 10. Teraz w czasie oczekiwania połączenie było odświeżane próbami kontrolnymi co 20 sekund.
Rezultat. Połączenie dożywało do gotowości raportu. Zerwania w pauzach ustały. Dodatkowy bonus – klient zaczął w 45–60 sekund dowiadywać się o naprawdę martwych połączeniach zamiast wisieć przez minuty.
Przypadek 3: losowe zerwania podczas aktywnego scrapowania
Sytuacja. Długożyjące sesje scrapowania zrywały się chaotycznie, bez związku z objętością czy czasem. Szczególnie często w określonych godzinach.
Diagnostyka. Objawy wskazywały na zmianę IP podczas handoveru. Po zerwaniu nowe połączenie podnosiło się natychmiast – sieć żyła.
Rozwiązanie. Przeprojektowaliśmy klienta pod filozofię efemerycznych połączeń: krótkie idempotentne transakcje, dziennik postępu, szybkie ponowne ustanawianie połączenia przy zerwaniu, ograniczone ponowienia z krótką pauzą.
Rezultat. Zerwania fizycznie nie zniknęły (zmiany IP nie da się pokonać), ale przestały być problemem. Każde zerwanie kosztowało powtórzenie jednej krótkiej transakcji – ułamek sekundy. Ogólna niezawodność procesu wzrosła dramatycznie, a inżynierowie przestali dyżurować przy logach nocami.
Ogólny wniosek z przypadków
Zwróć uwagę na prawidłowość: w każdym przypadku rozwiązanie nie leżało w wymianie proxy, ale w zrozumieniu warstwy transportowej i poprawnym skonfigurowaniu klienta. Diagnostyka po objawach wskazała konkretnego winowajcę, a leczenie było precyzyjne i szybkie.
FAQ: częste pytania o zrywanie połączeń w sieciach komórkowych
Dlaczego przez mobilny proxy krótkie żądania działają, a długie się zrywają?
Ponieważ długie operacje żyją dłużej i zdążą spotkać każdy z problemów transportowych: idle-timeout w pauzach, limit rozmiaru odpowiedzi, limit czasu życia połączenia, handover ze zmianą IP i czarną dziurę PMTUD przy dużych pakietach. Krótkie żądania kończą się wcześniej, niż cokolwiek z tego zadziała. Rozwiązanie – skonfigurować keepalive, MSS i wdrożyć wznawianie przez Range.
Jak ustalić, kto dokładnie zrywa połączenie – proxy, operator czy serwer?
Patrz na wzorzec. Zerwania w pauzach – timeout NAT operatora lub idle proxy. Zerwanie przy stałej objętości – limit rozmiaru po stronie proxy lub serwera. Zerwanie po stałym czasie – limit czasu życia. Zawieszanie się na dużych odpowiedziach – MTU i PMTUD. Losowe zerwania podczas ruchu – handover. Aby uzyskać dokładną odpowiedź, użyj przechwytywania ruchu: pokaże, z jakiego adresu przyszedł RST i czy ICMP dotarł.
Jaka jest różnica między TCP keepalive a HTTP keep-alive?
HTTP keep-alive działa na poziomie aplikacji i pozwala ponownie wykorzystać jedno połączenie dla kilku żądań, oszczędzając na ustanawianiu. TCP keepalive działa na poziomie jądra systemu operacyjnego i okresowo wysyła próby kontrolne, utrzymując połączenie przy życiu w pauzach i odświeżając NAT. Pierwszy oszczędza czas przy nowych żądaniach, drugi ratuje przed zerwaniem w bezczynności. Potrzebne są oba.
Jakie wartości TCP keepalive ustawiać dla sieci komórkowych?
Punkt wyjścia: idle 15–25 sekund, interval 10–15 sekund, count 3–4. Taka kombinacja wyprzedza agresywne timeouty NAT operatorów i jednocześnie zapewnia szybkie wykrywanie martwych połączeń. Dokładne wartości dobierz, mierząc swój idle-timeout u konkretnego operatora i proxy.
Czym jest czarna dziura PMTUD i jak ją rozpoznać?
To sytuacja, w której węzeł na trasie odrzuca zbyt duże pakiety, a jego powiadomienie ICMP o konieczności zmniejszenia rozmiaru jest blokowane i nie dociera do nadawcy. W efekcie duże pakiety są w nieskończoność tracone, a małe przechodzą. Rozpoznaje się ją po objawie: uzgadnianie i małe odpowiedzi działają, a duże odpowiedzi zawieszają się na amen. Leczy się przez MSS clamping lub zmniejszenie MTU.
Czy można zachować połączenie TCP przy zmianie stacji bazowej?
Jeśli sieć utrzymuje ten sam zewnętrzny IP podczas handoveru – tak, połączenie przetrwa przełączenie, być może z krótkim opóźnieniem. Jeśli IP się zmienia – nie, stare połączenie TCP jest nieodwracalnie martwe, ponieważ TCP jest sztywno przywiązany do pary adresów i portów. Właściwa strategia to nie próbować utrzymać połączenia za wszelką cenę, ale zbudować klienta, który płynnie ponownie ustanawia połączenie i kontynuuje od miejsca zerwania.
Jak poprawnie wznawiać pobieranie pliku po zerwaniu?
Sprawdź, czy serwer obsługuje Range (nagłówek Accept-Ranges: bytes i kod 206 na żądanie Range). Następnie przy zerwaniu ustal rozmiar już pobranej części i poproś nagłówkiem Range o resztę zaczynając od tego bajtu, dopisując do tego samego pliku. Powtarzaj aż do pełnego pobrania, ograniczając liczbę prób. Na koniec koniecznie porównaj końcowy rozmiar z oczekiwanym.
Jakie timeouty ustawiać w kliencie HTTP dla mobilnego proxy?
Rozdzielaj timeout ustanawiania połączenia (około 10 sekund) i timeout bezczynności odczytu (około 30 sekund, jako pauzę między porcjami danych, a nie na całą transmisję). Ogólny budżet operacji kontroluj na poziomie logiki wznawiania, a nie jednym ogromnym timeoutem. Nigdy nie zostawiaj timeoutów nieograniczonych.
Czy trzeba coś zmienić, jeśli strategie ponawiania w zależności od kodów odpowiedzi są już skonfigurowane?
Tak. Ponawianie według kodów odpowiedzi (na przykład obsługa 429 z wykładniczym backoffem) to poziom aplikacji, osobny temat. Zerwania transportowe – RST, timeouty odczytu, martwe sockety – wymagają własnej logiki: szybkie ponowienia z krótką pauzą, wznawianie, keepalive i poprawny MSS. Te dwie warstwy nie zastępują się nawzajem i muszą współistnieć w kliencie.
Czy wybór dostawcy proxy wpływa na odporność transportową?
Zdecydowanie. Dojrzały dostawca konfiguruje MSS clamping, odpowiednie idle-timeouty, stara się utrzymywać IP podczas handoveru i poprawnie współpracuje z keep-alive. To usuwa część problemów jeszcze przed twoim kodem. Ale nawet idealne proxy nie znosi konieczności budowania odpornego klienta, bo kanał radiowy jest z zasady nieprzewidywalny.
Podsumowanie: połączenie jest efemeryczne, ale dane muszą dotrzeć
Przeszliśmy drogę od objawu do odpornego klienta. Ustalmy najważniejsze rzeczy, aby ten artykuł stał się twoją zakładką.
Po pierwsze. Zerwanie długiego połączenia przez mobilny proxy to nie magia, tylko skutek całkiem konkretnych mechanizmów: idle-timeoutów na trzech poziomach, limitów rozmiaru i czasu, problemów z MTU i handoverem. Każdy ma swój wzorzec, a diagnostyka po objawach prawie zawsze wskazuje winowajcę.
Po drugie. Najkrótszy timeout na trasie decyduje o losie połączenia. Zmierz go i skonfiguruj TCP keepalive bardziej agresywnie niż ten limit – idle 15–25 sekund dla sieci komórkowych. Nie zapominaj, że TCP keepalive i HTTP keep-alive to różne mechanizmy i potrzebne są oba.
Po trzecie. Jeśli duże odpowiedzi się zawieszają, a małe przechodzą – to prawie na pewno czarna dziura PMTUD. Leczy się MSS clampingiem i poprawnym MTU. Nie trać tygodni na przerzucanie timeoutów, najpierw sprawdź rozmiar pakietu.
Po czwarte. Zmiany IP przy handoverze nie da się pokonać, ale można sprawić, że nie będzie straszna. Buduj klienta wokół efemeryczności połączenia: krótkie idempotentne transakcje, wznawianie przez Range, dziennik postępu, szybkie ponowne ustanawianie, ograniczone ponowienia i obowiązkowe sprawdzanie integralności.
Twoje następne kroki są proste. Zmierz idle-timeout na swoim proxy i operatorze. Włącz i skonfiguruj TCP keepalive. Sprawdź zachowanie przy dużych odpowiedziach i w razie potrzeby skonfiguruj MSS. Wdróż wznawianie przez Range. I koniecznie przestudiuj pokrewny materiał o strategiach ponawiania na poziomie kodów odpowiedzi – 429 i wykładniczym backoffie – aby zamknąć również warstwę aplikacyjną, a nie tylko transportową.
Sieć komórkowa jest z natury nieprzewidywalna. Ale klient zbudowany z szacunkiem do jej natury zamienia tę nieprzewidywalność ze źródła nocnych dyżurów w rutynowy szum tła. Połączenie jest efemeryczne – i to normalne. Najważniejsze, żeby dane mimo wszystko docierały. Teraz wiesz, jak to osiągnąć.