Перш ніж передати жодного байта даних
TCP є зв’язним протоколом, що базується на IP, який не має пам’яті про попередні або наступні пакети. TCP створює ілюзію надійного, впорядкованого потоку даних поверх цієї безпам'ятної мережевої підкладки, починаючи з узгодження місця початку потоку – це трихетапний handshake, який моделюється у вашій симуляції крок за кроком перед переходом до частини, яку часто пропускають пояснення: що відбувається з пропускною здатністю з’єднання після фактичного надсилання даних та виникнення мережевого збої.
SYN, SYN-ACK, ACK
Кожна сторона з’єднання TCP відстежує власне порядковий номер — лічильник, що ідентифікує зміщення байт потоку, який вона планує надсилати, а головна мета рукоздавання полягає в обміні та підтвердженні цих початкових значень:
клієнт -> сервер: SYN, seq = x (Я планую почати з байта x) сервер -> клієнт: SYN-ACK, seq = y, ack = x + 1 (Я планую почати з байта y; Я підтверджую ваш x) kлієнт -> сервер: ACK, ack = y + 1 (Я підтверджую ваш y) Після цього обміну обидві сторони знають порядковий номер іншої сторони та підтвердили його, що дозволяє відстежувати кожний наступний сегмент даних, розміщувати їх у правильному порядку та підтверджувати отримання навіть якщо IP може доставляти основні пакети в неправильній послідовності або взагалі втрачати їх. Рукоздавання також узгоджує параметри з’єднання — розмір вікна, максимальний розмір сегменту, підтримку вибіркового підтвердження — за допомогою тих самих трьох пакетів, що пояснює, чому повільне або нестабільне рукоздавання не лише відкладає початок з’єднання, але й узгоджує можливості, які формують все після нього.
client -> server: SYN, seq = x (I plan to start at byte x)
server -> client: SYN-ACK, seq = y, ack = x + 1 (I plan to start at y;
I confirm your x)
client -> server: ACK, ack = y + 1 (I confirm your y)
Після встановлення зв’язку: вікно завантаження”, “paragraphs”: [
Живий TCP-з'єднання не може просто надсилати дані з максимальною швидкістю, яку дозволяє канал відправника — десь на шляху може бути повільніший канал або перевантажений маршрутизатор, і надсилання даних швидше, ніж мережа може їх витримати, лише заповнює буфери до їх переповнення та втрати пакетів. Відповіддю TCP є вікно завантаження (cwnd): обмеження на стороні відправника, яке визначає максимальну кількість непідтверджених байт, які можуть перебувати в трансіті одночасно, і яке постійно коригується залежно від того, чи проходять пакети або їх втрачаються — це AIMD (додатно-множинне збільшення), та
Повільний старт: експоненційний, незважаючи на назву
Відразу після завершення рукодарства TCP не має жодної інформації про ємність шляху, тому починає обережно — але агресивно збільшує швидкість. Повільний старт подвоює cwnd приблизно кожну час між передачами (RTT), оскільки кожен підтверджений сегмент збільшує cwnd на один сегмент, і протягом одного RTT підтверджується весь обсяг вікна сегментів:
повільний старт (коли cwnd cwnd приблизно подвоюється за RTT // це експоненційний ріст незважаючи на назву "slow start" // він є "повільним" лише відносно надсилання всього вікна одночасно Це триває до тих пір, поки cwnd досягає порогу (ssthresh) — зазвичай встановленого з попереднього вимірювання втрат — або доки не буде виявлено втрату, в цьому випадку TCP перемикається у режим.
slow start (while cwnd < ssthresh): on each ACK received: cwnd += 1 segment // an entire cwnd of segments ACKed within ~1 RTT => cwnd roughly doubles per RTT // this is exponential growth despite the name "slow start" - // it's "slow" only relative to sending the whole window at once
Уникнення перевантаження: додавальне збільшення
Після того, як ssthresh було досягнуто, TCP припускає, що знаходиться близько до фактичної ємності шляху та переходить у обережний лінійний ріст – збільшуючи cwnd приблизно на один сегмент за час однієї мандрівної паузи, а не за ACK, делікатно досліджуючи будь-яку доступну вільну ємність:
congestion avoidance (cwnd >= ssthresh): on each full window of ACKs (~1 RTT): cwnd += 1 segment // linear growth - the "additive increase" half of AIMD
Втрата: множинне зменшення
Втрачений пакет (виявлений або через час очікування повторної передачі, або частіше за все, через дублікати ACK, що сигналізують про прогалину в отриманій потоці) є неявним сигналом для TCP, що мережа перевищила свій фактичний обсяг – немає жодного явного повідомлення про збій, TCP виводить збиток лише на основі втрат. Відповіддю є агресивне множинне зменшення, а не м’яке коригування:
у разі виявлення втрати: ssthresh = cwnd / 2 cwnd = ssthresh (або cwnd = 1 сегмент, для часу очікування – повний перезапуск до повільного старту) -> відновлення в режимі уникнення збою від половини значення Асимметрія — лінійне збільшення, множинне зменшення — є свідомою і саме вона забезпечує відносно стабільну збіжність AIMD навіть коли багато незалежних з’єднань TCP використовують один вузький канал без координації між ними: лінійний ріст достатньо обережний, щоб не перевищити обсяг набагато раніше, ніж прийде наступний сигнал про втрату, і зменшення вдвічі у разі втрат швидко виводить збурену чергу, а не повторно її перевищує. Повторюваний цикл лінійного підйому, що супроводжується зменшенням вдвічі у разі втрати, створює характерний графік
sawtooth
пропускної здатності, який є другою найважливішою частиною алгоритмічного дизайну інтернету — це причина того, що перевантажений канал поступово деградує до спільного, справедливого діапазону пропускної здатності замість того, щоб перейти в стан застиглого задуху.
on loss detected:
ssthresh = cwnd / 2
cwnd = ssthresh (or cwnd = 1 segment, for a timeout -
a full restart back to slow start)
-> resume in congestion avoidance from the halved value
Часті запитання
Чому TCP потребує три пакети для початку з’єднання замість одного?
Оскільки обидва напрямки потребують узгодженого початкового номера послідовності, і кожна сторона потребує доказів того, що інша дійсно отримала його, а не просто відправила. SYN пропонує початковий номер послідовності клієнта, SYN-ACK пропонує номер послідовності сервера та підтверджує номер послідовності клієнта, а остаточний ACK підтверджує номер послідовності сервера — після трьох пакетів обидві сторони мають взаємно підтверджений, а не просто спільно заявлений початковий пункт.
Якщо slow start експоненційно збільшує вікно, чому його називають ‘slow’?
Воно повільне лише відносно альтернативи негайного надсилання всього обсягу даних з великого вікна без будь-якої розігрівання, що робило б раніше, більш примітивні реалізації TCP і які надійно перевантажували проміжні маршрутизатори. Порівняно з цим, початок із одного сегменту та подвоєння кожного раунду-подорожі є консервативним варіантом — хоча в абсолютних термінах зростання є експоненційним.
Чому TCP обрізає швидкість відправлення вдвічі при втраті пакетів замість меншого коригування?
Оскільки TCP не має явного сигналу щодо того, наскільки перевантажений мережевий канал — втрачений пакет є єдиним зворотним зв’язком, який він отримує, і до того, як втрата буде виявлена, фактичне перевантаження може вже бути значним. Велике, швидке обрізання (мультиплікативна зменшення) швидко зливає перевантажений чергу, а наступне повільне, лінійне підняття назад (додавне збільшення) є тим, що робить алгоритм стабільним і справедливим розподілом пропускної здатності без повторного перевищення та повторного спровокування втрат.
Спробуйте наживо
Усе, що вище, працює прямо у вашому браузері — відкрийте TCP/IP Handshake і змінюйте параметри під час роботи. Нічого не встановлюється, нічого не завантажується на сервер, уся модель живе в одній вкладці.
▶ Відкрити симуляцію TCP/IP Handshake