Рукостискання tcp. Заебісті питання - мережі ЕОМ та телекомунікації

Вступ

TCP це протокол, орієнтований з'єднання. Перед тим, як будь-яка сторона може надіслати дані іншій, між ними має бути встановлене з'єднання. У цьому розділі ми докладно розглянемо, як встановлюється з'єднання TCP і як воно розривається.

Оскільки для роботи TCP необхідно встановити з'єднання між двома кінцями, він відрізняється від протоколів без з'єднання, таких як UDP. Ми бачили, що при використанні UDP кожна сторона просто відсилає датаграми інший, не встановивши перед цим з'єднання.

Встановлення та розрив з'єднання

Для того, щоб подивитися, що відбувається при встановленні та розриві TCP з'єднання, ми виконали на системі svr4 наступну команду:

svr4% telnet bsdi discard
Trying 192.82.148.3 ...
Connected to bsdi.
Escape character is "^]".
^] вводимо Control, праву квадратну дужку,
telnet> quitщоб Telnet клієнт розірвав з'єднання
Connection closed.

Команда telnet встановлює TCP з'єднання з хостом bsdi порт, відповідний discard сервісу (глава 1, розділ ). Це саме тип сервісу, який нам необхідний, щоб подивитися, що відбувається при встановленні та розриві з'єднання, але без обміну даними.

Висновок tcpdump

На малюнку 18.1 показаний висновок tcpdump для сегментів, згенерованих цією командою.

1 0.0 svr4.1037 > bsdi.discard: S 1415531521:1415531521 (0)
win 4096
2 0.002402 (0.0024) bsdi.discard > svr4.1037: S 1823083521:1823083521 (0)
ack 1415531522 win 4096

3 0.007224 (0.0048) svr4.1037 > bsdi.discard: . ack 1823083522 win 4096
4 4.155441 (4.1482) svr4.1037 > bsdi.discard: F 1415531522:1415531522 (0)
ack 1823083522 win 4096
5 4.156747 (0.0013) bsdi.discard > svr4.1037: . ack 1415531523 win 4096
6 4.158144 (0.0014) bsdi.discard > svr4.1037: F 1823083522:1823083522 (0)
ack 1415531523 win 4096
7 4.180662 (0.0225) svr4.1037 > bsdi.discard: . ack 1823083523 win 4096

Рисунок 18.1 Виведення tcpdump для встановлення та розриву TCP з'єднання.

Ці сім сегментів TCP містять тільки TCP заголовки. Обмін даними не здійснювалося.

Для TCP сегментів кожен вихідний рядок починається з

source > destination: flags (джерело > призначення: прапори)

де прапори (flags) є чотири з шести прапорових бітів TCP заголовка (). На малюнку 18.2 показано п'ять різних символів, які відповідають прапорам та можуть з'явитися у виводі.

3-символьне скорочення

Опис

синхронізуючі номери послідовності
відправник закінчив передачу даних
скидання з'єднання
відправка даних приймаючого процесу настільки швидко, наскільки це можливо
жоден із чотирьох прапорів не встановлений

Рисунок 18.2 Символи прапорів, виведені командою tcpdump для прапорових бітів у заголовку TCP.

У даному прикладіми бачимо прапори S, F та точку. Ще два прапори (R та P) з'являться пізніше. Два інших біт прапорів в TCP заголовку - ACK і URG - надруковані командою tcpdump.

В одному сегменті може бути більше одного з чотирьох прапорних бітів, показаних на малюнку 18.2, однак, зазвичай зведений буває тільки один прапор.

RFC 1025 [ Postel 1987] називає сегмент, в якому максимальна комбінація всіх доступних прапорових бітів зведена одночасно (SYN, URG, PSH, FIN та 1 байт даних) пакетом Камікадзе (в англійській мові існує ще кілька визначень такого пакету, а саме - "брудний" пакет", "пакет Новорічної ялинки" і т.п.).

У рядку 1 поле 1415531521:1415531521 (0) означає, що номер послідовності пакета дорівнює 1415531521, а кількість байт даних у сегменті дорівнює 0. Команда tcpdump друкує початковий номер послідовності, двокрапка, передбачуваний заключний номер послідовності. При цьому існує можливість переглянути передбачуваний остаточний номер послідовності, коли кількість байтів більше ніж 0. У полі з'являється (1), якщо сегмент містить один або кілька байт даних, або (2) якщо зведені прапор SYN, FIN або RST. У рядках 1, 2, 4 і 6 малюнку 18.1 це поле з'являється, оскільки зведені прапорові біти - обмін будь-якими даними у цьому прикладі не проводився.

У рядку 2 поле ack 1415531522 містить номер підтвердження. Воно друкується лише тоді, коли прапор ACK зведений. Поле win 4096 у кожному рядку виводу показує розмір вікна, яке було оголошено відправником. У цьому прикладі, де не здійснювався обмін даними, розмір вікна залишався незмінним і використовувалася за замовчуванням величина - 4096. (Ми розглянемо розмір вікна TCP у розділі глави 20.)

І останнє поле у ​​виведенні малюнку 18.1, показує максимальний розмірсегмента (MSS - maximum segment size), опція, яку встановлює відправник. Відправник не хоче отримувати TCP сегменти більше, ніж це значення. Це робиться зазвичай у тому, щоб уникнути фрагментації (глава 11, розділ ). Ми розглянемо максимальний розмір сегмента у розділі цього розділу, а формат різних опцій TCP покажемо розділ цього розділу.

Тимчасові діаграми

На малюнку 18.3 показана часова діаграма, що відповідає цьому обміну пакетами. (Ми описали деякі основні характеристики часових діаграм, коли вперше звернулися до .) На цьому малюнку показано, яка сторона надсилає пакети. Також наведено висновок команди tcpdump (на друк виводилося SYN замість S). У цій часовій діаграмі видалено значення розміру вікна, оскільки це не суттєво для нашого обговорення.

Протокол встановлення з'єднання

А тепер давайте повернемося до деталей протоколу TCP, які показані на малюнку 18.3. Щоб встановити з'єднання TCP, необхідно:

  1. Сторона, що запитує (яка, як правило, називається клієнт) відправляє SYN сегмент, вказуючи номер порту сервера, до якого клієнт хоче приєднатися, і вихідний номер послідовності клієнта (в даному прикладі ISN, 1415531521). Це сегмент №1.
  2. Сервер відповідає своїм сегментом SYN, що містить вихідний номер серії послідовності (сегмент 2). Сервер також підтверджує прихід клієнта SYN з використанням ACK (ISN клієнта плюс один). На SYN використовується один номер послідовності.
  3. Клієнт повинен підтвердити надходження SYN від сервера з використанням ACK (ISN сервера плюс один, сегмент 3).

Цих трьох сегментів достатньо встановлення з'єднання. Часто це називається триразовим рукостисканням (three-way handshake).

Рисунок 18.3 Тимчасова діаграма встановлення та розриву з'єднання.

Вважається, що сторона, яка надсилає перший SYN, активізує з'єднання (активне відкриття). Інша сторона, яка отримує перший SYN і надсилає наступний SYN, бере пасивну участь у відкритті з'єднання (пасивне відкриття). (У розділі цього розділу ми докладно опишемо процедуру відкриття з'єднання, де обидві сторони вважаються активними під час встановлення з'єднання.)

Коли кожна сторона надіслала свій SYN щоб встановити з'єднання, вона вибирає вихідний номер послідовності (ISN) для цього з'єднання. ISN повинен змінюватися кожного разу, тому кожне з'єднання має свій, відмінний від інших ISN. RFC 793 [ Postel 1981c] вказує, що ISN є 32-бітним лічильником, який збільшується на одиницю кожні 4 мікросекунди. Завдяки номерам послідовностей, пакети, що затрималися в мережі та доставлені пізніше, не сприймаються як частина існуючого з'єднання.

Як вибирається номер послідовності? У 4.4BSD (і у більшості Berkeley реалізацій) при ініціалізації системи вихідний номер послідовності встановлюється в 1. Подібна практика засуджується вимогою до хостів Host Requirements RFC. Потім ця величина збільшується на 64000 кожні півсекунди і повертається значення 0 через кожні 9,5 годин. (Це відповідає лічильнику, який збільшується на одиницю кожні 8 мікросекунд, а не кожні 4 мікросекунди.) Крім того, щоразу, коли встановлюється з'єднання, ця змінна збільшується на 64000.

Проміжок в 4,1 секунд між сегментами 3 і 4 відповідає часу між встановленням з'єднання і введенням команди quit для telnet, щоб розірвати з'єднання.

Протокол розриву з'єднання

Для того щоб встановити з'єднання, необхідно 3 сегменти, а для того, щоб розірвати - 4. Це пояснюється тим, що TCP з'єднання може бути наполовину закритому стані. Так як TCP з'єднання повнодуплексне (дані можуть пересуватися в кожному напрямку незалежно від іншого напрямку), кожен напрямок має бути закрито незалежно від іншого. Правило полягає в тому, що кожна сторона повинна надіслати FIN, коли передача даних завершена. Коли TCP приймає FIN, він повинен повідомити, що віддалена сторона розриває з'єднання і припиняє передачу даних у цьому напрямку. FIN зазвичай відправляється в результаті того, що програма була закрита.

Можна сказати, що сторона, яка першою закриває з'єднання (відправляє перший FIN), здійснює активне закриття, а інша сторона (яка прийняла цей FIN) здійснює пасивне закриття. Зазвичай одна сторона здійснює активне закриття, а інша пасивне, однак у розділі цього розділу ми побачимо, що обидві сторони можуть здійснити активне закриття.

Сегмент номер 4 на малюнку 18.3 призводить до закриття з'єднання та надсилається, коли Telnet клієнт припиняє роботу. Це відбувається коли ми вводимо quit. При цьому TCP клієнт змушений надіслати FIN, закриваючи потік даних від клієнта до сервера.

Коли сервер отримує FIN, він відправляє назад ACK із прийнятим номером послідовності плюс один (сегмент 5). На FIN витрачається один номер послідовності, як і на SYN. У цей момент TCP сервер також доставляє додатку ознаку кінця файлу (end-of-file) (щоб вимкнути сервер). Потім сервер закриває своє з'єднання, що змушує TCP послати FIN (сегмент 6), який клієнт повинен підтвердити (ACK), збільшивши на одиницю номер прийнятої послідовності (сегмент 7).

На малюнку 18.4 показаний типовий обмін сегментами під час закриття з'єднання. Номери послідовності опушені. На цьому малюнку FIN посилаються через те, що програми закривають свої з'єднання, тоді як ACK для цих FIN генерується автоматично програмним забезпеченням TCP.

З'єднання зазвичай встановлюються клієнтом, тобто перший SYN рухається від клієнта до сервера. Проте будь-яка сторона може активно закрити з'єднання (надіслати перший FIN). Часто, однак, саме клієнт визначає, коли з'єднання має бути розірвано, оскільки процес клієнта в основному управляється користувачем, який вводить щось подібне "quit", щоб закрити з'єднання. На малюнку 18.4 ми можемо поміняти місцями мітки, наведені зверху малюнка, назвавши ліву сторону сервером, а праву сторону клієнтом. Однак навіть у цьому випадку все працюватиме саме так, як показано на малюнку. (Перший приклад розділу 14, наприклад, показував, як сервер часу закриває з'єднання.)

Рисунок 18.4 Звичайний обмін сегментами під час закриття з'єднання.

Звичайний висновок tcpdump

Так як завдання відсортувати величезну кількість номерів послідовності досить складна, у виведенні програми tcpdump містяться повні номери послідовності тільки для сегментів SYN, а всі наступні номери послідовностей показані як відносне зміщення від вихідних номерів послідовності. (Для того щоб отримати висновок, наведений на малюнку 18.1, ми повинні були вказати опцію -S.) Звичайний висновок tcpdump, який відповідає малюнку 18.1, показаний на малюнку 18.5.

1 0.0 svr4.1037 > bsdi.discard: S 1415531521:1415531521(0)
win 4096
2 0.002402 (0.0024) bsdi.discard > svr4.1037: S 1823083521:1823083521(0)
ack 1415531522
win 4096
3 0.007224 (0.0048) svr4.1037 > bsdi.discard: . ack 1 win 4096
4 4.155441 (4.1482) svr4.1037 > bsdi.discard: F 1:1 (0) ack 1 win 4096
5 4.156747 (0.0013) bsdi.discard > svr4.1037: . ack 2 win 4096
6 4.158144 (0.0014) bsdi.discard > svr4.1037: F 1:1 (0) ack 2 win 4096
7 4.180662 (0.0225) svr4.1037 > bsdi.discard: . ack 2 win 4096

Рисунок 18.5 Звичайний висновок команди tcpdump, що відповідає встановленню та розриву з'єднання.

Якщо у нас не буде необхідності показувати повні номери послідовності, ми будемо використовувати цю форму виведення у всіх наступних прикладах.

Тайм-аут під час встановлення з'єднання

Існує кілька причин, через які не може бути встановлено з'єднання. Наприклад, хост (сервер) вимкнено. Щоб зімітувати подібну ситуацію, ми виконали команду telnet після того, як від'єднали Ethernet кабель від сервера. На малюнку 18.6 показано виведення команди tcpdump.

1 0.0 bsdi.1024 >
win 4096
2 5.814797 (5.8148) bsdi.1024 > svr4.discard: S 291008001:291008001(0)
win 4096
3 29.815436 (24.0006) bsdi.1024 > svr4.discard: S 291008001:291008001(0)
win 4096

Рисунок 18.6 Виведення команди tcpdump для встановлення з'єднання, яке було припинено за тайм-аутом.

У цьому висновку необхідно звернути увагу, як часто TCP клієнт відправляє SYN, намагаючись встановити з'єднання. Другий сегмент посилається через 5,8 секунд після першого, а третій посилається через 24 секунд після другого.

Слід зазначити, що цей приклад був запущений приблизно через 38 хвилин після того, як клієнт був перезавантажений. Тому відповідний вихідний номер послідовності дорівнює 291008001 (приблизно 38х60х6400х2). На початку глави ми сказали, що типові системи Berkeley встановлюють вихідний номер послідовності в 1, а потім збільшують його на 64 000 кожні півсекунди.

Також необхідно зазначити, що це перше TCP з'єднання з того моменту, як система була перезавантажена, оскільки номер порту клієнта дорівнює 1024.

Однак на малюнку 18.6 не показано, скільки часу TCP клієнт здійснював повторні передачі, перш ніж відмовитися від спроби. Для того, щоб подивитися це тимчасові значення, ми повинні виконати команду telnet наступним чином:

bsdi% date; telnet svr4 discard; date
Thu Sep 24 16:24:11 MST 1992
Trying 192.82.148.2...
telnet: Необхідний підключення до remote host: Connection timed out
Thu Sep 24 16:25:27 MST 1992

Час складає 76 секунд. Більшість систем Berkeley встановлюють межу часу 75 секунд, за цей час має бути встановлене нове з'єднання. У розділі глави 21 ми побачимо, що третій пакет, надісланий клієнтом, буде відкинуто за тайм-аутом приблизно о 16:25:29, тобто через 48 секунд після того, як його було відправлено, при цьому клієнт не припинить свої спроби через 75 секунд. .

Перший тайм-аут

На малюнку 18.6 слід звернути увагу на те, що перший тайм-аут, 5,8 секунд, близький до 6 секунд, однак не дорівнює 6 секунд, тоді як другий тайм-аут практично точно дорівнює 24 секунд. Було виконано ще десять подібних тестів, причому у кожному їх значення першого тайм-ауту коливалося в діапазоні від 5,59 секунди до 5,93 секунди. Другий тайм-аут, однак, завжди був 24,00 секунд.

Це пояснюється тим, що BSD реалізації TCP запускають таймер кожні 500 мілісекунд. Цей 500 мілісекундний таймер використовується для різних TCP тайм-аутів, всі вони будуть описані в наступних розділах. Коли ми вводимо команду telnet, встановлюється вихідний 6-секундний таймер (12 тиків годин), однак він може закінчитися в будь-якому місці між 5,5 та 6 секундами. На малюнку 18.7 показано, як це відбувається.

Малюнок 18.7 500-мілісекундний таймер TCP.

Так як таймер встановлений у 12 тиків, перше зменшення таймера може відбутися між 0 та 500 мілісекунд після його встановлення. З цього моменту таймер зменшується приблизно кожні 500 мілісекунд, проте перший період може бути різним. (Ми використовуємо слово "приблизно", тому що час, коли TCP отримує управління кожні 500 мілісекунд, зразкове, тому що може пройти інше переривання, яке оброблятиметься ядром.)

Коли цей 6-секундний таймер закінчиться на тику поміченому 0 на малюнку 18.7, таймер встановлюється в 24 секунди (48 тиків). Цей наступний таймер дорівнює 24 секундам, оскільки він був встановлений в той момент часу, коли 500-мілісекундний таймер TCP був викликаний ядром, а не користувачем.

Поле типу сервісу

На малюнку 18.6 бачимо вираз . Це поле типу сервісу (TOS - type-of-service) в IP-датаграмі (). Telnet клієнт у BSD/386 встановлює це поле таким чином, щоб отримати мінімальну затримку.

Максимальний розмір сегмента

Максимальний розмір сегмента (MSS) – це найбільша порція даних, яку TCP надішле на віддалений кінець. Коли встановлюється з'єднання, кожна сторона може оголосити свій MSS. Значення, які ми бачили, були 1024. IP датаграма, яка вийде в результаті, зазвичай на 40 байт більше: 20 байт відводиться під заголовок TCP і 20 байт під IP заголовок.

У деяких публікаціях йдеться, що ця опція встановлюється "за домовленістю". Насправді домовленість у цьому випадку не використовується. Коли з'єднання встановлюється, кожна сторона повідомляє MSS, якою вона збирається приймати. ( Опція MSS може бути використана тільки в сегменті SYN.) Якщо одна сторона не приймає опцію MSS від іншої сторони, використовується розмір за замовчуванням в 536 байт. (У цьому випадку, при 20-байтному IP заголовку та 20-байтному TCP заголовку, розмір IP датаграми становитиме 576 байт.)

У загальному випадку, чим більше MSS тим краще, поки не відбувається фрагментація. (Це не завжди вірно. Зверніться до і , щоб переконатися в цьому.) Великі розміри сегмента дозволяють надіслати більше даних у кожному сегменті, що зменшує відносну вартість IP і TCP заголовків. Коли TCP відправляє SYN сегмент, або коли локальна програма хоче встановити з'єднання, або коли прийнятий запит на з'єднання від віддаленого хоста, може бути встановлено значення MSS, що дорівнює MTU вихідного інтерфейсу мінус розмір фіксованих TCP і IP заголовків. Для Ethernet MSS може сягати 1460 байт. При використанні інкапсуляції IEEE 802.3 (розділ 2, розділ) MSS може бути до 1452 байт.

Значення 1024, яке ми бачимо в цьому розділі, відповідає з'єднанням, у яких беруть участь BSD/386 і SVR4, тому що більшість BSD реалізацій вимагає, щоб MSS було кратно 512. Інші системи, такі як SunOS 4.1.3, Solaris 2.2 та AIX 3.2 .2, оголошують MSS, що дорівнює 1460, коли обидві сторони знаходяться на одному Ethernet. Розрахунки, наведені [Mogul 1993], показують, що MSS рівний 1460 забезпечують кращу продуктивність на Ethernet, ніж MSS рівний 1024.

Якщо IP адреса призначення "не локальна", MSS зазвичай встановлюється за замовчуванням - 536. Чи є локальним або нелокальним кінцевий пункт призначення можна наступним чином. Пункт призначення, IP адреса якого має той самий ідентифікатор мережі і ту саму маску підмережі, що і у відправника є локальним; пункт призначення, IP-адреса якого повністю відрізняється від ідентифікатора мережі, є нелокальною; пункт призначення з тим самим ідентифікатором мережі, однак з іншою маскою підмережі, може бути як локальним, так і нелокальним. Більшість реалізацій надають опцію конфігурації ( і ), що дозволяє системному адміністратору вказати, які підмережі є локальними, які нелокальними. Установка цієї опції визначає максимальний анонсований MSS (який за величиною може досягати MTU вихідного інтерфейсу), інакше використовується значення за промовчанням 536.

MSS дозволяє хосту встановлювати розмір датаграм, який надсилатиметься віддаленою стороною. Якщо взяти до уваги той факт, що хост також обмежує розмір датаграм, які він надсилає, це дозволяє уникнути фрагментації, коли хост підключений до мережі з меншим MTU.

Уявіть наш хост slip, який має SLIP канал із MTU рівним 296, підключеним до маршрутизатора bsdi. На малюнку 18.8 показані ці системи та хост sun.

Рисунок 18.8 TCP з'єднання від sun до slip та значення MSS.

Ми встановили TCP з'єднання від sun до slip та переглянули сегменти з використанням tcpdump. На малюнку 18.9 показано лише встановлення з'єднання (оголошення розміру вікна видалено).

1 0.0 sun.1093 > slip.discard: S 517312000:517312000(0)

2 0.10 (0.00) slip.discard > sun.1093: S 509556225:509556225(0)
ack 517312001
3 0,10 (0,00) sun.1093 > slip.discard: . ack 1

Рисунок 18.9 Виведення tcpdump для встановлення з'єднання від sun до slip.

Тут важливо звернути увагу на те, що Sun не може послати сегмент з порцією даних більше ніж 256 байт, так як він отримав MSS рівний 256 (рядок 2). Більше того, тому що slip знає що MTU вихідного інтерфейсу дорівнює 296, навіть якщо sun оголосить MSS рівний 1460, він ніколи не зможе послати більше ніж 256 байт даних, щоб уникнути фрагментації. Проте, система може надіслати даних менше, ніж MSS оголошений віддаленою стороною.

Уникнути фрагментації таким чином можна тільки якщо хост безпосередньо підключений до мережі з MTU менше ніж 576. Якщо обидва хости підключені до Ethernet і обидва анонсують MSS 536, однак проміжна мережа має MTU 296, буде здійснена фрагментація. Єдиний спосіб уникнути цього – скористатися механізмом визначення транспортного MTU (глава 24, розділ).

Наполовину закритий TCP

TCP надає можливість одному учаснику з'єднання припинити передачу даних, але все ще отримувати дані від віддаленої сторони. Це називається наполовину закритий TCP. Як ми вже згадували раніше, деякі програми можуть користуватися цією можливістю.

Щоб використовувати цю характеристику програмного інтерфейсу, необхідно надати можливість додатку сказати: "Я закінчило передачу даних, тому посилаю ознаку кінця файлу (end-of-file) (FIN) на віддалений кінець, проте я все ще хочу отримувати дані з віддаленого кінця до тих доки він мені не надішле ознаку кінця файлу (end-of-file) (FIN)."

Сокети API підтримують напівзакритий режим, якщо програма викличе shutdown з другим аргументом, рівним 1 замість виклику close. Більшість програм, однак, розривають з'єднання в обох напрямках викликом close.

На малюнку 18.10 показано стандартний сценарій для напівзакритого TCP. Ми показали клієнта з лівого боку, він ініціює напівзакритий режим, але це може зробити будь-яка сторона. Перші два сегменти однакові: FIN від ініціатора, за ним слідує ACK і FIN від приймаючого. Однак далі сценарій відрізнятиметься від того, що наведено на малюнку 18.4, тому що сторона, яка прийняла наказ "напівзакрити", може все ще надсилати дані. Ми показали лише один сегмент даних, за яким слідує ACK, проте в цьому випадку може бути надіслана будь-яка кількість сегментів даних. (Ми розповімо більш детально про обмін сегментами даних та підтвердженнями в .) Коли кінець, який отримав наказ "напівзакрити", здійснив передачу даних, він закриває свою частину з'єднання, в результаті чого посилається FIN, при цьому ознака кінця файлу доставляється додатку, який ініціював "напівзакритий" режим. Коли другий FIN підтверджено, з'єднання вважається повністю закритим.

Малюнок 18.10 TCP у напівзакритому режимі.

Для чого можна використовувати напівзакритий режим? Одним із прикладів може бути команда Unix rsh(1), яка виконує команду на іншій системі. Команда

sun % rsh bsdi sort< datafile

запустить команду sort на хості bsdi, причому стандартне введення команди rsh читатиметься з файлу з ім'ям datafile. Команда rsh створює TCP з'єднання між собою та програмою, яка буде виконана на віддаленому хості. Потім rsh функціонує досить просто: команда копіює стандартне введення (datafile) в з'єднання і копіює зі з'єднання стандартний висновок(Наш термінал). На малюнку 18.11 показано, як це відбувається. (Ми пам'ятаємо, що TCP з'єднання повнодуплексне.)

Рисунок 18.11 Команда: rsh bsdi sort< datafile.

На віддаленому хості bsdi сервер rshd виконує програму sort таким чином, що її стандартне введення та стандартне виведення направлені в TCP з'єднання. У розділі 14 наводиться докладний описструктури процесу Unix, який бере участь тут, проте нас цікавить, як використовується TCP з'єднання та напівзакритий режим TCP.

Програма sort не може почати генерацію виведення до тих пір, поки все її введення не буде прочитане. Усі вихідні дані, що надходять з'єднання від клієнта rsh на сервер sort, надсилаються у файл, який має бути відсортований. Коли досягається позначка кінця файлу у введенні (datafile), клієнт rsh здійснює напівзакриття TCP з'єднання. Потім сервер sort приймає мітку кінця файлу зі свого стандартного введення (з'єднання TCP), сортує файл і пише результат у свій стандартний висновок (з'єднання TCP). Клієнт rsh продовжує читати TCP з'єднання на своєму кінці, копіюючи відсортований файл у свій стандартний висновок.

Без використання наполовину закритого режиму потрібна будь-яка додаткова техніка, яка дозволить клієнту повідомити сервер, що він закінчив посилку даних, проте клієнту все ще дозволено отримувати дані від сервера. Альтернативно необхідно використовувати дві сполуки, проте переважно використання напівзакритого режиму.

Діаграма станів передачі TCP

Ми описали кілька правил встановлення та розриву TCP з'єднання. Ці правила зібрані у діаграму станів передачі, що наведено малюнку 18.12.

Слід зазначити, що це діаграма - діаграма стандартних станів. Ми помітили звичайну передачу клієнта суцільними жирними стрілками, а звичайну передачу сервера - жирними стрілками.

Дві передачі, які ведуть стану ВСТАНОВЛЕНО ( ESTABLISHED), відповідають відкриттю з'єднання, а дві передачі, які ведуть стану ВСТАНОВЛЕНО (ESTABLISHED), відповідають розриву з'єднання. Стан ВСТАНОВЛЕНО (ESTABLISHED) настає у той момент, коли з'являється можливість здійснити передачу даних між двома сторонами в обох напрямках. У наступних розділах буде описано, що відбувається у цьому стані.

Ми об'єднали чотири квадратики в лівій нижній частині діаграми всередині пунктирної рамки і помітили їхнє "активне закриття" (active close). Два інших квадратики (ЧЕКАННЯ_ЗАКРИТТЯ - CLOSE_WAIT і ОСТАННЕ_ПІДТВЕРДЖЕННЯ - LAST_ACK) об'єднані пунктирною рамкою і позначені як "пасивне закриття" (passive close).

Назви 11-ти станів (ЗАКРИТО - CLOSED, СЛУХАЄ - LISTEN, SYN_ВІДПРАВЛЕНИЙ - SYN_SENT, і так далі) на цьому малюнку обрані таким чином, щоб відповідати станам, які виводить команда netstat. Імена ж netstat, своєю чергою, практично ідентичні іменам, описаним у RFC 793. Стан ЗАКРИТО (CLOSED) насправді перестав бути станом, проте є стартової і кінцевою точкою для діаграми.

Зміна стану від СЛУХАЄ (LISTEN) до SYN_ВІДПРАВЛЕНИЙ (SYN_SENT) теоретично можлива, проте не підтримується в реалізаціях Berkeley.

А зміна стану від ОТРИМАНИЙ_SYN ( SYN_RCVD) назад до СЛУХАЄ (LISTEN) можлива тільки в тому випадку, якщо в стан ОТРИМАНИЙ_SYN (SYN_RCVD) увійшли зі стану СЛУХАЄ (LISTEN) (це звичайний сценарій), а не зі стану SYN відкриття). Це означає, що якщо ми здійснили пасивне відкриття (увійшли в стан СЛУХАЄ - LISTEN), отримали SYN, послали SYN з ACK (увійшли в стан ОТРИМАНИЙ_SYN - SYN_RCVD) і потім отримали скидання замість ACK, кінцева точка повертається в стан СЛУХАЄ ( очікує на прибуття іншого запиту на з'єднання.

Рисунок 18.12 Діаграма змін стану TCP.

На малюнку 18.13 показано звичайне встановлення та закриття TCP з'єднання. Також докладно описані різні стани, якими проходять клієнт і сервер.

Малюнок 18.13 Стани TCP, що відповідають звичайному відкриттю та розриву з'єднання.

На малюнку 18.13 ми припустили, що клієнт, що знаходиться з лівого боку, здійснює активне відкриття, а сервер, що знаходиться праворуч, здійснює пасивне відкриття. Також ми показали, що клієнт здійснює активне закриття (як ми згадували раніше, кожна сторона може здійснити активне закриття).

Вам слід простежити зміни станів на малюнку 18.13 з використанням датаграми зміни станів, наведеної на малюнку 18.12, що дозволить зрозуміти, чому здійснюється та чи інша зміна стану.

Стан очікування 2MSL

Стан ЧАС_ОЧІКУВАННЯ (TIME_WAIT) також іноді називається станом очікування 2MSL. У кожному реалізації вибирається значення максимального часу життя сегмента ( MSL - maximum segment lifetime) . Це максимальний час, протягом якого сегмент може існувати в мережі перед тим, як він буде відкинутий. Ми знаємо, що цей час обмежений, оскільки TCP сегменти передаються за допомогою IP-датаграм, а кожна IP-датаграма має поле TTL, яке обмежує час її життя.

RFC 793 [ Postel 1981c] вказує, що MSL має дорівнювати 2 хвилинам. У різних реалізаціях ця величина має значення 30 секунд, 1 або 2 хвилини.

У говорилося, що життя IP датаграми обмежується кількістю пересилок, а чи не таймером.

При використанні MSL діють такі правила: коли TCP здійснює активне закриття і посилає останній сегмент, що містить підтвердження (ACK), з'єднання має залишитися в стані TIME_WAIT на час, що дорівнює двом MSL. Це дозволяє TCP повторно надіслати останній ACK у разі, якщо перший ACK втрачено (у разі віддалена сторона відпрацює тайм-аут і повторно передасть свій кінцевий FIN).

Інше призначення очікування 2MSL полягає в тому, що поки TCP з'єднання знаходиться в очікуванні 2MSL, пара сокетів, виділена для цього з'єднання (IP-адреса клієнта, номер порту клієнта, IP-адреса сервера і номер порту сервера), не може бути повторно використана. Це з'єднання може бути використане повторно лише коли закінчиться час очікування 2MSL.

На жаль, більшість реалізацій (Berkeley одна з них) підкоряються більш жорстким вимогам. За замовчуванням локальний номер порту не може бути повторно використаний, доки цей номер порту є локальним номером порту пари сокетів, який перебуває у стані очікування 2MSL. Нижче розглянемо приклади загальних вимог.

Деякі реалізації та API надають засоби, які дозволяють обійти ці обмеження. З використанням API сокет може бути вказано опцію сокету SO_REUSEADDR. Вона дозволяє призначати номер локального порту, який знаходиться в стані 2MSL, проте ми побачимо, що правила TCP не дозволяють цьому номеру порту бути використаним в з'єднанні, яке знаходиться в стані очікування 2MSL.

Кожен затриманий сегмент, що прибуває по з'єднанню, яке перебуває в стані очікування 2MSL, відкидається. Так як з'єднання визначається парою сокет у стані 2MSL, це з'єднання не може бути повторно використано доти, поки ми не зможемо встановити нове з'єднання. Це робиться для того, щоб пакети, що запізнилися, не були сприйняті як частина нового з'єднання. (З'єднання визначається парою сокет. Нова сполука називається відновленням або пожвавленням цієї сполуки.)

Як ми вже показали малюнку 18.13, зазвичай клієнт здійснює активне закриття і входить у режим TIME_WAIT. Сервер зазвичай здійснює пасивне закриття та не проходить через режим TIME_WAIT. Можна зробити висновок, що якщо ми вимкнемо клієнта і негайно його перестартуємо, цей новий клієнт не зможе використовувати той самий локальний номер порту. У цьому немає жодної проблеми, оскільки клієнти зазвичай використовують порти, що динамічно призначаються, і не піклуються, який динамічно призначається порт використовується в даний час.

Однак, з точки зору сервера все інакше, оскільки сервер використовують заздалегідь відомі порти. Якщо ми вимкнемо сервер, який має встановлене з'єднання, і постараємося негайно перестартувати його, сервер не може використовувати свій заздалегідь відомий номер порту як кінцева точка з'єднання, оскільки цей номер порту є частиною з'єднання, що знаходиться в стані очікування 2MSL. Тому може знадобитися від 1 до 4 хвилин, перш ніж сервер буде перестартовано.

Спостерігати подібний сценарій можна з використанням програми sock. Ми стартували сервер, приєднали до нього клієнта, а потім вимкнули сервер:

sun % sock-v-s 6666
(запускаємо клієнта на bsdi, який приєднається до цього порту)
connection on 140.252.13.33.6666 from140.252.13.35.1081
^? вводимо символ переривання, щоб вимкнути сервер
sun % sock-s 6666і намагаємося негайно перестартувати сервер на той самий порт
can't bind local address: Address already in use
sun % netstatспробуємо перевірити стан з'єднання
Active Internet сonnections
Proto Recv-Q Send-Q Local Address Foreign Address (state)
tcp 0 0 sun.6666 bsdi.1081 TIME_WAIT
безліч рядків видалено

Коли ми намагаємося перестартувати сервер, програма видає повідомлення про помилку, що вказує на те, що вона не може захопити свій відомий номер порту, тому що він вже використовується (перебуває в стані очікування 2MSL).

Потім ми негайно виконуємо netstat, щоб переглянути стан з'єднання і перевірити, чи воно дійсно знаходиться в стані TIME_WAIT.

Якщо ми будемо продовжувати спроби перестартувати сервер і подивимося час, коли це вдасться, можемо обчислити значення 2MSL. Для SunOS 4.1.3, SVR4, BSD/386 і AIX 3.2.2 перестартування сервера займе 1 хвилину, що означає, що MSL дорівнює 30 секунд. У Solaris 2.2 це перестартування сервера займає 4 хвилини, це означає, що MSL дорівнює 2 хвилинам.

Ми можемо побачити ту ж помилку, згенеровану клієнтом, якщо клієнт намагається захопити порт, який є частиною з'єднання, що знаходиться в режимі очікування 2MSL (зазвичай клієнт цього не робить):

sun % sock-v bsdi echoстартуємо клієнт, який приєднується до сервера echo
connected on 140.252.13.33.1162 to 140.252.13.35.7
hello thereдрукуємо цей рядок
hello there вона відбивається луною від сервера
^Dвводимо символ кінця файлу, щоб вимкнути клієнта
sun % sock -b1162 bsdi echo
can't bind local address: Address already in use

При першому запуску клієнта була вказана опція -v, яка дозволяє переглянути, який використовується локальний номер порту (1162). При другому запуску клієнта була вказана опція -b, яка повідомляє клієнту про необхідність призначити самому собі номер локального порту 1162. Як ми й очікували, клієнт не може цього зробити, тому що цей номер порту є частиною з'єднання, яке знаходиться в стані 2MSL.

Тут необхідно згадати про одну особливість стану очікування 2MSL, до якої ми повернемося в , коли розповідатимемо про протокол передачі файлів (FTP - File Transfer Protocol). Як згадувалося раніше, може очікування 2MSL залишається пара сокетів (що складається з локального IP адреси, локального порту, віддаленого IP адреси і віддаленого порту). Однак, безліч реалізацій дозволяють процесу повторно використовувати номер порту, який є частиною з'єднання, що знаходиться в режимі 2MSL (зазвичай з використанням опції SO_REUSEADDR) TCP не може дозволити створити нове з'єднання з тією ж парою сокет. Це можна довести за допомогою наступного експерименту:

sun % sock-v-s 6666старт сервера, що слухає порт 6666
(запускаємо клієнта на bsdi, який приєднується до цього порту)
connection on 140.252.13.33.6666 від 140.252.13.35.1098
^? вводимо символ переривання, щоб вимкнути сервер
sun % sock -b6666 bsdi 1098стартуємо клієнта з локальним портом 6666
can't bind local address: Address already in use
sun % sock -A -b6666 bsdi 1098намагаємося знову, на цей раз з опцією -A
active open error: Address already in use

Вперше ми запустили нашу програму sock як сервер на порт 6666 та приєднали до нього клієнта з хоста bsdi. Номер порту клієнта 1098, що динамічно призначається. Ми вимкнули сервер, таким чином, він здійснив активне закриття. При цьому 4 параметри - 140.252.13.33 (локальна IP адреса), 6666 (локальний номер порту), 140.252.13.35 (віддалена IP адреса) та 1098 (віддалений номер порту) на сервері потрапляють у стан 2MSL.

Вдруге ми запустили цю програму в якості клієнта, вказавши локальний номер порту 6666, при цьому була спроба приєднатися до хоста bsdi на порт 1098. При спробі повторно використовувати локальний порт 6666 була згенерована помилка, оскільки цей порт знаходиться в стані 2MSL.

Щоб уникнути цієї помилки, ми запустили програму знову, вказавши опцію -A, яка активізує опцію SO_REUSEADDR. Це дозволило програмі призначити собі номер порту 6666, однак було отримано помилку, коли програма спробувала здійснити активне відкриття. Навіть якщо програма зможе призначити собі номер порту 6666, вона зможе створити з'єднання з портом 1098 на хості bsdi, оскільки пара сокетів, визначальна це з'єднання, перебуває у стані очікування 2MSL.

Що, якщо ми спробуємо встановити з'єднання з іншого хоста? По-перше, ми повинні перестартувати сервер на sun з прапором -A, тому що порт, який йому необхідний (6666), є частиною з'єднання, що знаходиться в стані очікування 2MSL:

sun % sock-A-s 6666стартуємо сервер, що слухає порт 6666

Потім, перед тим, як стан очікування 2MSL закінчиться на sun, ми стартуємо клієнта на bsdi:

bsdi% sock -b1098 sun 6666
connected on 140.252.13.35.1098 to 140.252.13.33.6666

На жаль, це працює! Це недолік TCP специфікації, проте підтримується більшістю реалізацій Berkeley. Ці реалізації сприймають прибуття запиту на нове з'єднання для з'єднання, яке знаходиться в стані TIME_WAIT, якщо новий номер послідовності більший за останній номер послідовності, використаний в попередньому з'єднанні. У цьому випадку ISN для нового з'єднання встановлюється рівним останнім номером послідовності для попереднього з'єднання плюс 128000. Додаток до RFC 1185 показує можливі недоліки подібної технології.

Ця характеристика реалізації дозволяє клієнту і серверу повторно використовувати ті самі номери порту для успішного відновлення того ж з'єднання, в тому випадку, однак, якщо сервер не здійснив активне закриття. Ми побачимо інший приклад стану очікування 2MSL на , коли обговорюватимемо FTP. Також зверніться до цього розділу.

Тихий час концепції

Стан очікування 2MSL надає захист від запізнілих пакетів, що належать раннім з'єднанням, при цьому вони не будуть інтерпретуватися як частина нового з'єднання, яке використовує ті ж локальні і віддалені IP адреси і номери портів. Однак це працює тільки в тому випадку, якщо хост зі з'єднанням у стані 2MSL не вийшов з ладу.

Що якщо хост з портами в стані 2MSL вийшов з ладу, перезавантажився під час MSL і негайно встановив нові з'єднання з використанням тих самих локальних і віддалених IP адрес і номерів портів, що відповідають локальним портамякі були в стані 2MSL перед поломкою? У цьому випадку сегменти, що запізнилися, із сполуки, яка існувала перед поломкою, можуть бути помилково інтерпретовані як належать новому з'єднанню, створеному після перезавантаження. Це може статися незалежно від того, який вихідний номер послідовності вибрано після перезавантаження.

Для захисту від таких небажаних сценаріїв RFC 793 вказує, що TCP не повинен створювати нові з'єднання до закінчення MSL після завантаження. Це називається тихий час (quiet time).

У деяких реалізаціях хости очікують навіть довше, ніж час MSL після перезавантаження.

Стан ОЧІКУВАННЯ_І_ПІДТВЕРДЖЕННЯ_FIN (FIN_WAIT_2)

У стані FIN_WAIT_2 ми надсилаємо наш FIN, а віддалена сторона підтверджує його. Якщо ми не знаходимося в стані напівзакритого з'єднання, то очікуємо від програми на віддаленому кінці, що воно впізнає прийом ознаки кінця файлу і закриє свою сторону з'єднання, причому надішле нам FIN. Тільки коли процес на віддаленому кінці здійснить закриття, наша сторона перейде з режиму FIN_WAIT_2 в режим TIME_WAIT.

Це означає, що наша сторона з'єднання може залишитися у цьому режимі назавжди. Віддалена сторона все ще може CLOSE_WAIT і може залишатися в цьому стані завжди, доки програма не вирішить здійснити закриття.

Більшість Berkeley реалізацій запобігають такому вічному очікуванню в стані FIN_WAIT_2 наступним чином. Якщо програма, яка здійснила активне закриття, здійснила повне закриття, а не напівзакриття, що вказує на те, що воно очікує на прийом даних, в цьому випадку встановлюється таймер. Якщо з'єднання не використовується протягом 10 хвилин плюс 75 секунд, TCP переводить з'єднання в режим ЗАКРИТО ( CLOSED). У коментарях йдеться про те, що подібна характеристика суперечить специфікації протоколу.

Сегменти скидання (Reset)

Ми згадували, що у TCP заголовку існує біт, званий RST, що означає " скидання " (reset). У загальному випадку сигнал "скидання" (reset) посилається TCP у тому випадку, якщо сегменти, що прибувають, не належать зазначеному з'єднанню. (Ми використовуємо термін "вказане з'єднання" (referenced connection), який позначає з'єднання, що ідентифікується IP адресою призначення та номером порту призначення, а також IP адресою джерела та номером порту джерела. У RFC 793 це називається "сокет".)

Запит про з'єднання на неіснуючий порт

Самий загальний випадок, При якому генерується скидання (reset), це коли запит про з'єднання прибуває і при цьому немає процесу, який слухає порт призначення. У випадку UDP, як ми бачили в розділі глави 6, якщо датаграма прибуває на порт призначення, що не використовується - генерується помилка ICMP про недоступність порту. TCP натомість використовує скидання.

Ми наведемо простий приклад з використанням клієнта Telnet, вказавши номер порту, який не використовується на пункті призначення:

bsdi% telnet svr4 20000порт 20000 не використовується
Trying 140.252.13.34...
telnet: Зовнішній зв'язок remote host: Connection refused

Повідомлення про помилку видається клієнту Telnet негайно. На малюнку 18.14 показаний обмін пакетами, що відповідає цій команді.

1 0.0 bsdi.1087 > svr4.20000: S 297416193:297416193(0)
win 4096
2 0.003771 (0.0038) svr4.20000 > bsdi.1087: R 0:0 (0) ack 297416194 win 0

Малюнок 18.14 Генерація скидання під час спроби відкрити з'єднання на неіснуючий порт.

Значення, які нам необхідно детальніше розглянути на цьому малюнку, це поле номера послідовності та поле номера підтвердження у скиданні. Так як біт підтвердження (ACK) не був встановлений в сегменті, що прибув, номер послідовності скидання встановлено в 0, а номер підтвердження встановлений у вхідний вихідний номер послідовності (ISN) плюс кількість байт даних в сегменті. Незважаючи на те, що в сегменті, що прибув, не присутній реальних даних, біт SYN логічно займає 1 байт в просторі номера послідовності; таким чином, у цьому прикладі номер підтвердження в скиданні встановлюється ISN плюс довжина даних (0) плюс один SYN біт.

Розрив з'єднання

У розділі цього розділу ми бачили, що звичайний метод, який використовується для розриву з'єднання, полягає в тому, що одна із сторін посилає FIN. Іноді це називається правильним звільненням (orderly release), оскільки FIN надсилається після того, як усі дані, раніше поставлені в чергу, були відправлені, і зазвичай при цьому не відбувається втрата даних. Однак існує можливість перервати з'єднання, надіславши скидання (reset) замість FIN. Іноді це називається перерваючим звільненням (abortive release).

Подібний розрив з'єднання надає додатку дві можливості: (1) будь-які дані, що стоять у черзі - губляться, і скидання відправляється негайно, і (2) сторона, яка прийняла RST, може сказати, що віддалена сторона розірвала з'єднання замість того, щоб закрити його звичайним чином . Програмний інтерфейс (API), який використовується програмою, повинен надавати спосіб згенерувати подібне скидання замість нормального закриття.

Ми можемо подивитися, що відбувається у разі подібного розриву, використовуючи нашу програму sock. Сокети API надають цю можливість за допомогою опції сокету "затримки закриття" (linger on close) ( SO_LINGER). Ми вказали опцію -L з часом затримки, що дорівнює 0. Це означає, що замість звичайного FIN буде надіслано скидання, щоб закрити з'єднання. Ми підключимося до версії програми sock, яка виступає в ролі сервера, на svr4:

bsdi% sock-L0 svr4 8888це клієнт; сервер показано далі
hello, worldвводимо один рядок, який буде відправлений на віддалений кінець
^Dвводимо символ кінця файлу, щоб вимкнути клієнта

На малюнку 18.15 показано виведення команди tcpdump для цього прикладу. (Ми видалили всі оголошення вікон у цьому малюнку, оскільки вони не впливають на наші міркування.)

1 0.0 bsdi.1099 > svr4.8888: S 671112193:671112193(0)

2 0.004975 (0.0050) svr4.8888 > bsdi.1099: S 3224959489:3224959489(0)
ack 671112194
3 0.006656 (0.0017) bsdi.1099 > svr4.8888: . ack 1
4 4.833073 (4.8264) bsdi.1099 > svr4.8888: P 1:14 (13) ack 1
5 5.026224 (0.1932) svr4.8888 > bsdi.1099: . ack 14
6 9.527634 (4.5014) bsdi.1099 > svr4.8888: R 14:14 (0) ack 1

Рисунок 18.15 Розрив з'єднання із використанням скидання (RST) замість FIN.

У рядках 1-3 показано нормальне встановлення з'єднання. У рядку 4 надсилається рядок даних, який ми надрукували (12 символів плюс Unix символ нового рядка), і в рядку 5 надходить підтвердження про прийом даних.

Рядок 6 відповідає введеному символу кінця файлу (Control-D), за допомогою якого ми вимкнули клієнта. Оскільки ми вказали розрив замість звичайного закриття (опція командного рядка -L0), TCP на bsdi надішле RST замість звичайного FIN. RST сегмент містить номер послідовності та номер підтвердження. Також зверніть увагу на те, що сегмент RST не очікує відповіді з віддаленого кінця - він не містить підтвердження взагалі. Отримувач скидання перериває з'єднання та повідомляє додатку, що з'єднання було перервано.

Ми отримаємо таку помилку від сервера при такому обміні:

svr4% sock-s 8888запускаємо як сервер, слухаємо порт 8888
hello, world це те, що відправив клієнт
read error: Connection reset by peer

Цей сервер читає з мережі та копіює у стандартний висновок все що отримав. Зазвичай він завершує свою роботу, отримавши ознаку кінця файлу від свого TCP, проте тут бачимо, що він отримав помилку при прибутті RST. Помилка це саме те, що ми очікували: з'єднання було розірвано одним із учасників з'єднання.

Визначення напіввідкритої сполуки

Вважається, що TCP з'єднання напіввідкрите, якщо одна сторона закрила або перервала з'єднання без попередження іншої сторони. Це може статися у будь-який час, якщо один із двох хостів вийде з ладу. Так як якийсь час не буде спроб передати дані по напіввідкритому з'єднанню, одна зі сторін буде працювати, допоки не визначить, що віддалена сторона виходила з ладу.

Ще одна причина, через яку може виникнути напіввідкрите з'єднання, полягає в тому, що на хості клієнта було вимкнено живлення, замість того, щоб погасити програму клієнта, а потім вимкнути комп'ютер. Це відбувається тоді, наприклад, клієнт Telnet запускається на PC, і користувачі вимикають комп'ютер в кінці робочого дня. Якщо під час вимкнення PC не здійснювалася передача даних, сервер будь-коли дізнається, що клієнт зник. Коли користувач приходить наступного ранку, включає свій PC і стартує новий клієнт Telnet, на хості сервера стартує новий сервер. Через це на хості сервера може з'явитися багато відкритих TCP з'єднань. (У ми побачимо спосіб, за допомогою якого один кінець TCP з'єднання може визначити, що інший зник. Це робиться з використанням TCP опції "залишайся в живих" (keepalive)).

Ми можемо легко створити напіввідкрите з'єднання. Запускаємо клієнта Telnet на bsdi і приєднуємося до сервера discard на svr4. Вводимо один рядок і дивимося з використанням tcpdump, як він проходить, а потім від'єднуємо Ethernet кабель від сервера хоста і перезапускаємо його. Цим самим ми імітували вихід з ладу сервера. (Ми від'єднали Ethernet кабель перед перезавантаженням сервера, щоб той не послав FIN у відкрите з'єднання, що роблять деякі TCP модулі при вимкненні.) Після того як сервер перезавантажився, ми під'єднали кабель і спробували надіслати ще один рядок від клієнта на сервер. Так як сервер був перезавантажений і втратив усі дані про з'єднання, які існували до перезавантаження, він нічого не знає про з'єднання і не підозрює про те, якому з'єднанню належать сегменти, що прибули. У цьому випадку сторона TCP відповідає скиданням (reset).

bsdi% telnet svr4 discardзапуск клієнта
Trying 140.252.13.34...
Connected to svr4.
Escape character is "^]".
hi thereцей рядок надіслано нормально
тут ми перезавантажили хост сервера
another lineу цьому місці було здійснено скидання (reset)
Connection closed by foreign host.

На малюнку 18.16 показано висновок tcpdump для цього прикладу. (Ми видалили з виведення оголошення вікон, інформацію про тип сервісу та оголошення MSS, оскільки вони не впливають на наші міркування.)

1 0.0 bsdi.1102 > svr4.discard: S 1591752193:1591752193(0)
2 0.004811 (0.0048) svr4.discard > bsdi.1102: S 26368001:26368001(0)
ack 1591752194
3 0.006516 (0.0017) bsdi.1102 > svr4.discard: . ack 1

4 5.167679 (5.1612) bsdi.1102 > svr4.discard: P 1:11 (10) ack 1
5 5.201662 (0.0340) svr4.discard > bsdi.1102: . ack 11

6 194.909929 (189.7083) bsdi.1102 > svr4.discard: P 11:25 (14) ack 1
7 194.914957 (0.0050) arp who-has bsdi tell svr4
8 194.915678 (0.0007) arp reply bsdi is-at 0:0:c0:6f:2d:40
9 194.918225 (0.0025) svr4.discard > bsdi.1102: R 26368002:26368002(0)

Рисунок 18.16 Скидання у відповідь на прихід сегмента даних при напіввідкритому з'єднанні.

У рядках 1-3 здійснюється нормальне встановлення з'єднання. У рядку 4 відправляється рядок "hi there" (це можна приблизно перекласти як "Гей ви, там") на discard сервер, у рядку 5 приходить підтвердження.

У цьому місці ми від'єднали кабель Ethernet від svr4, перезавантажили його і під'єднали кабель знову. Уся процедура зайняла приблизно 190 секунд. Потім ми надрукували наступний рядоквведення на клієнта ("another line"), і коли ми натиснули клавішу Return, рядок було відправлено на сервер (рядок 6 малюнку 18.16). При цьому була отримана відповідь від сервера, проте сервер був перезавантажений, його ARP кеш порожній, тому в рядках 7 і 8 ми бачимо ARP запит і відгук. Потім у рядку 9 було надіслано скидання (reset). Клієнт отримав скидання і видав, що з'єднання було перервано віддаленим хостом. (Останнє повідомлення виведення від клієнта Telnet не так інформативно, як могло б бути.)

Одночасне відкриття

Для двох додатків існує можливість здійснити активне відкриття одночасно. З кожної сторони має бути переданий SYN, і ці SYN повинні пройти мережею назустріч один одному. Також потрібно, щоб кожна сторона мала номер порту, який відомий іншій стороні. Це називається одночасним відкриттям (simultaneous open).

Наприклад, додаток на хості А має локальний порт 7777 здійснює активне відкриття на порт 8888 хоста В. Додаток на хості має локальний порт 8888 здійснює активне відкриття на порт 7777 хоста А.

Це не те саме що приєднання Telnet клієнта з хоста А на Telnet сервер на хості В, в той час коли Telnet клієнт з хоста В приєднується до Telnet сервера на хості А. У подібному сценарії обидва Telnet сервери здійснюють пасивне відкриття, а не активне, тоді як Telnet клієнти призначають собі номери портів, що динамічно призначаються, а не порти, які заздалегідь відомі для віддалених Telnet серверів.

TCP спеціально розроблений таким чином, щоб обробляти одночасне відкриття, при цьому результатом є одне з'єднання, а не два. (В інших сімействах протоколів, наприклад, у транспортному рівні OSI, у подібному випадку створюється дві сполуки, а не одна.)

Коли здійснюється одночасне відкриття, зміни станів протоколу відрізняються від тих, що показані на малюнку 18.13. Обидва кінці надсилають SYN одночасно, переходячи в стан SYN_SENT. Коли кожна сторона приймає SYN, стан змінюється на SYN_ПРИНЯТИЙ ( SYN_RCVD) (див. малюнок 18.12), і кожен кінець повторно відправляє SYN з підтвердженням того, що SYN прийнятий. Коли кожен кінець отримує SYN плюс ACK, стан змінюється на ВСТАНОВЛЕНО (ESTABLISHED). Зміни станів наведено малюнку 18.17.

Рисунок 18.17 Обмін сегментами у процесі одночасного відкриття.

Одночасне відкриття вимагає обміну чотирма сегментами, на один більше, ніж при "триразовому рукостисканні". Також зверніть увагу, що ми не називаємо один з кінців клієнтом, а інший сервером, тому що в даному випадку обидва виступають і як клієнт і як сервер.

Здійснити одночасне відкриття можливе, проте досить складно. Обидві сторони повинні бути стартовані приблизно в той самий час, таким чином, щоб SYN перетнулися один з одним. У цьому випадку може допомогти великий час повернення між двома учасниками з'єднання, що дозволяє SYN перетнутися. Щоб отримати це, ми використовуємо як один учасник з'єднання хост bsdi, а інший хост vangogh.cs.berkeley.edu. Оскільки між ними знаходиться SLIP канал із додзвоном, час повернення має бути досить великим (кілька сотень мілісекунд), що дозволяє SYN перетнутися.

Один кінець (bsdi) призначає локальний порт 8888 (опція командного рядка -b) і здійснює активне відкриття на порт 7777 іншого хоста:

bsdi% sock-v-b8888 vangogh.cs.berkeley.edu 7777
connected on 140.252.13.35.8888 to 128.32.130.2.7777
TCP_MAXSEG = 512
hello, worldвводимо цей рядок
and hi there цей рядок був надрукований на іншому кінці
connection closed by peer це висновок, коли було отримано FIN

Інший кінець був стартований приблизно в цей же час, він призначив собі номер локального порту 7777 і здійснив активне відкриття на порт 8888:

vangogh % sock -v -b7777 bsdi.tuc.noao.edu 8888
connected on 128.32.130.2.7777 to 140.252.13.35.8888
TCP_MAXSEG = 512
hello, world це введено на іншому кінці
and hi thereми надрукували цей рядок
^Dі потім ввели символ кінця файлу EOF

Ми вказали прапор -v в командному рядкупрограми sock, щоб перевірити IP адреси та номери портів для кожного кінця з'єднань. Цей прапор також друкує MSS, який використовується на кожному кінці з'єднання. Ми також надрукували як введення по одному рядку на кожному кінці, які були відправлені на віддалений кінець і надруковані там, щоб переконатися, що обидва хости "бачать" один одного.

На малюнку 18.18 показано обмін сегментами для цієї сполуки. (Ми видалили деякі нові опції TCP, що з'явилися у вихідних SYN, що прийшли від vangogh, яка працює під керуванням 4.4BSD. Ми опишемо ці нові опції в розділі цього розділу.) Зверніть увагу, що за двома SYN (рядки 1 і 2) йдуть два SYN з ACK (рядки 3 та 4). У цьому відбувається одночасне відкриття.

У рядку 5 показано введений рядок "hello, world", що йде від bsdi до vangogh з підтвердженням у рядку 6. Рядки 7 та 8 відповідають рядку "and hi there", що йде в іншому напрямку. У рядках 9-12 показано звичайне закриття з'єднання.

Більшість реалізацій Berkeley не підтримує одночасне відкриття коректно. У цих системах, якщо Ви можете досягти того, що SYN перетнуться, все закінчиться обміном сегментів, кожен з SYN і ACK, в обох напрямках. У більшості реалізацій який завжди здійснюється перехід стану SYN_SENT в стан SYN_RCVD, показаний малюнку 18.12.

1 0.0 bsdi.8888 > vangogh.7777: S 91904001:91904001(0)
win 4096
2 0.213782 (0.2138) vangogh.7777 > bsdi.8888: S 1058199041:1058199041(0)
win 8192
3 0,215399 (0,0016) bsdi.8888 > vangogh.7777: S 91904001:91904001(0)
ack 1058199042 win 4096

4 0.340405 (0.1250) vangogh.7777 > bsdi.8888: S 1058199041:1058199041(0)
ack 91904002 win 8192

5 5.633142 (5.2927) bsdi.8888 > vangogh.7777: P 1:14 (13) ack 1 win 4096
6 6.100366 (0.4672) vangogh.7777 > bsdi.8888: . ack 14 win 8192

7 9.640214 (3.5398) vangogh.7777 > bsdi.8888: P 1:14 (13) ack 14 win 8192
8 9.796417 (0.1562) bsdi.8888 > vangogh.7777: Додати замітку Редагувати zametkу | ack 14 win 4096

9 13.060395 (3.2640) vangogh.7777 > bsdi.8888: F 14:14 (0) ack 14 win 8192
10 13.061828 (0.0014) bsdi.8888 > vangogh.7777: Додати замітку Редагувати zametkу | ack 15 win 4096
11 13.079769 (0.0179) bsdi.8888 > vangogh.7777: F 14:14 (0) ack 15 win 4096
12 13.299940 (0.2202) vangogh.7777 > bsdi.8888: . ack 15 win 8192

Малюнок 18.18 Обмін сегментами при одночасному відкритті.

Одночасне закриття

Як ми сказали раніше, з одного боку (часто, але не завжди з боку клієнта) здійснюється активне закриття, при цьому посилається перший FIN. Також можливо для обох сторін здійснити активне закриття, оскільки протокол TCP дозволяє здійснити одночасне закриття (simultaneous close).

У термінах, наведених на малюнку 18.12, обидва кінці переходять від стану ВСТАНОВЛЕНО ( ESTABLISHED) до стану ОЧЕКАННЯ_FIN_1 ( FIN_WAIT_1) , коли програма видає сигнал до закриття. При цьому обидва посилають FIN, які можливо зустрінуться десь у мережі. Коли FIN прийнято, на кожному кінці відбувається перехід зі стану FIN_WAIT_1 в стан ЗАКРИВАЮ ( CLOSING) і з кожного боку надсилається завершальний ACK. Коли кожен кінець отримує завершальний ACK, стан змінюється TIME_WAIT. На малюнку 18.19 показано зміни станів.

Малюнок 18.19 Обмін сегментами у процесі одночасного закриття.

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

TCP заголовок може містити опції (). Єдині опції, визначені в оригінальній специфікації TCP, такі: кінець списку опцій, немає операції та максимальний розмір сегмента. Ми бачили в наших прикладах опцію MSS практично в кожному SYN сегменті.

Більш нові RFC, наприклад RFC 1323 визначають додаткові опції TCP, більшість з яких можна виявити тільки в пізніших реалізаціях. (Ми опишемо нові опції в .) На малюнку 18.20 показаний формат поточних опцій TCP - тих, які описані в RFC 793 та RFC 1323.

Рисунок 18.20 TCP опції.

Кожна опція починається з 1-байтового типу (kind), який свідчить про тип опції. Опції, тип яких дорівнює 0 та 1, займають 1 байт. Інші опції мають довжину (len) байт, який слідує за байтом типу. Довжина - це повна довжина, що включає байти типу та довжини.

Опція "ні операції" (NOP) додана, щоб відправник міг заповнити поля, які мають бути кратні 4 байтам. Якщо ми встановимо TCP з'єднання від системи 4.4BSD, у початковому сегменті SYN за допомогою tcpdump можна побачити наступні опції:

Опція MSS встановлена ​​в 512, за нею слідує NOP, за нею слідує опція розміру вікна. Перша опція NOP використовується для того, щоб доповнити 3-байтову опцію розміру вікна до 4 байт. Так само, 10-байтова опція тимчасової марки передує двома NOP, щоб займати 12 байт.

Чотири інші опції, яким відповідає тип рівний 4, 5, 6 та 7, називаються опціями селективного ACK та луна опціями. Ми не показали їх на малюнку 18.20, тому що відлуння опції замінені опцією тимчасової марки, а селективні ACK, як визначено в даний час, все ще знаходяться в обговоренні і не були включені в RFC 1323. Треба зазначити, що пропозиція T/TCP для TCP транзакцій (розділ розділу 24) вказує ще три опції з типами рівними 11, 12 та 13.

Реалізація TCP сервера

У розділі глави 1 ми сказали, більшість TCP серверів - конкурентні. Коли на сервер надходить запит про встановлення нового з'єднання, він приймає з'єднання та запускає новий процес, який обслуговуватиме нового клієнта. Залежно від операційної системи використовуються різні способистворення нового сервера. У Unix системах новий процес створюється з допомогою функції fork.

Нам необхідно обговорити, як TCP взаємодіє із конкурентними серверами. Хочеться відповісти на наступне запитання: як обробляються номери портів, коли сервер отримує запит на нове з'єднання від клієнта, і що станеться, якщо одночасно прибуде кілька запитів на з'єднання?

Номери портів сервера TCP

Ми можемо сказати, як TCP обробляє номери портів, розглянувши будь-який TCP сервер. Розглянемо сервер Telnet з використанням команди netstat. Наступний висновок наведено для системи, які не мають активних Telnet з'єднань. (Ми видалили всі рядки за винятком одного, який показує сервер Telnet.)

sun % netstat -a -n -f inet
Active Internet connections (including servers)
Proto Recv-Q Send-Q Local Address Foreign Address (state)
tcp 0 0 *.23 *.* LISTEN

Прапор -a повідомляє про всі кінцеві точки мережі, а не тільки про які знаходяться в стані встановлено (ESTABLISHED). Прапор -n друкує IP-адреси в цифровому десятковому поданні замість того, щоб використовувати DNS для конвертування адрес в імена, і друкує цифрові номери портів (наприклад, 23) замість друку імен сервісів (в даному випадку Telnet). Опція -f inet повідомляє лише про кінцеві точки TCP і UDP.

Локальна адреса виводиться як *.23, де зірочка зазвичай називається символом підстановки чи метасимволом. Це означає, що вхідний запит про з'єднання (SYN) буде прийнято з будь-якого локального інтерфейсу. Якщо хост має кілька інтерфейсів, ми могли б вказати одну конкретну IP-адресу як локальну IP-адресу (один з IP-адрес хоста), і тільки запити на з'єднання, прийняті з цього інтерфейсу, будуть обслужені. (Ми побачимо, як це робиться, пізніше у цьому розділі.) Локальний порт дорівнює 23, це заздалегідь відомий порт для Telnet.

Віддалена адреса показана як *.*, це означає, що віддалена IP-адреса і віддалений номер порту поки не відомі, тому що кінцева точка знаходиться в стані СЛУХАЄ (LISTEN), очікуючи прибуття запиту на з'єднання.

Зараз ми стартуємо Telnet клієнта на хості slip (140.252.13.65), який приєднається до цього сервера. Тут наведено відповідні рядки виведення команди netstat:


tcp 0 0 140.252.13.33.23 140.252.13.65.1029 ESTABLISHED
tcp 0 0 *.23 *.* LISTEN

Перший рядок для порту 23 це встановлене з'єднання(ESTABLISHED). Для цього з'єднання заповнені всі чотири елементи локальної та віддаленої адрес: локальна IP адреса і номер порту, і віддалена IP адреса і номер порту. Локальна IP-адреса відповідає інтерфейсу, на який прибув запит про з'єднання ( Ethernet інтерфейс, 140.252.13.33).

Кінцева точка залишилася у стані LISTEN. Це кінцева точка, яку конкурентний сервер використовує для того, щоб приймати запити на з'єднання, які прийдуть в майбутньому. В даному випадку TCP модуль, що знаходиться в ядрі, створив нову кінцеву точку в стані ESTABLISHED, в момент, коли запит про з'єднання прибув і був прийнятий. Також зверніть увагу на те, що номер порту для з'єднання, яке знаходиться в стані ESTABLISHED, не змінився: він дорівнює 23, як і кінцевої точки, яка знаходиться в стані LISTEN.

Зараз ми стартуємо ще одного Telnet клієнта з того самого клієнта (slip) на цей сервер. Відповідний висновок команди netstat виглядатиме так:

Proto Recv-Q Send-Q Local Address Foreign Address (state)
tcp 0 0 140.252.13.33.23 140.252.13.65.1030 ESTABLISHED
tcp 0 0 140.252.13.33.23 140.252.13.65.1029 ESTABLISHED
tcp 0 0 *.23 *.* LISTEN

Зараз ми бачимо два встановлених (ESTABLISHED) з'єднання з того самого хоста на той же сервер. Обидва мають локальний номер порту 23. Це не проблема для TCP, так як номери віддалених портів різні. Вони повинні бути різні, тому що кожен Telnet клієнт використовує порт, що динамічно призначається, а з визначення динамічно призначається порту ми знаємо, що динамічно призначеним може бути тільки той порт, який не використовується в даний час на хості (slip).

Цей приклад показує, що TCP демультиплексує вхідні сегменти з використанням усіх чотирьох значень, які порівнюються з локальною та віддаленою адресами: IP адреса призначення, номер порту призначення, IP адреса джерела та номер порту джерела. TCP не може визначити, який процес отримав вхідний сегмент, переглядаючи лише номер порту призначення. Також лише одна з трьох кінцевих точокна порту 23, яка знаходиться в стані LISTEN, приймає запити на з'єднання. Кінцеві точки, які перебувають у стані ESTABLISHED, що неспроможні приймати сегменти SYN, а кінцева точка, що у стані LISTEN, неспроможна приймати сегменти даних.

Зараз ми стартуємо ще одного клієнта Telnet із хоста solaris, який пройде через SLIP канал від sun, а не через Ethernet.

Proto Recv-Q Send-Q Local Address Foreign Address (state)
tcp 0 0 140.252.1.29.23 140.252.1.32.34603 ESTABLISHED
tcp 0 0 140.252.13.33.23 140.252.13.65.1030 ESTABLISHED
tcp 0 0 140.252.13.33.23 140.252.13.65.1029 ESTABLISHED
tcp 0 0 *.23 *.* LISTEN

Локальна IP-адреса для першого встановленого (ESTABLISHED) з'єднання тепер відповідає адресі інтерфейсу SLIP каналу на багатоінтерфейсному хості sun (140.252.1.29).

Обмеження локальних IP-адрес

Ми можемо подивитися, що станеться, коли сервер не використовує символи підстановки як свої локальні IP адреси, встановлюючи натомість одну конкретну адресу локального інтерфейсу. Якщо ми вкажемо IP адресу (або ім'я хоста) нашій програмі sock, коли використовуємо її як сервер, ця IP адреса стане локальною IP адресою кінцевої точки, що слухає. Наприклад

sun % sock-s 140.252.1.29 8888

обмежує цей сервер лише для з'єднань, що прибувають із SLIP інтерфейсу (140.252.1.29). Висновок команди netstat покаже наступне:

Proto Recv-Q Send-Q Local Address Foreign Address (state)

Якщо ми приєднаємося до цього сервера через SLIP канал із хоста solaris, це спрацює.

Proto Recv-Q Send-Q Local Address Foreign Address (state)
tcp 0 0 140.252.1.29.8888 140.252.1.32.34614 ESTABLISHED
tcp 0 0 140.252.1.29.8888 *.* LISTEN

Однак, якщо ми намагатимемося приєднатися до цього сервера з хоста через Ethernet (140.252.13), запит на з'єднання не буде прийнято TCP модулем. Якщо ми подивимося за допомогою tcpdump, то побачимо, що на SYN отримано відгук RST, як показано на малюнку 18.21.

1 0.0 bsdi.1026 > sun.8888: S 3657920001:3657920001(0)
win 4096
2 0.000859 (0.0009) sun.8888 > bsdi.1026: R 0:0 (0) ack 3657920002 win 0

Рисунок 18.21 Обмеження запитів на з'єднання, що базується на локальній IP-адресі сервера.

Програма, що працює на сервері, ніколи не побачить запит на з'єднання - обмеження здійснюється TCP модулем в ядрі на основі локальної IP-адреси, вказаної додатком.

Обмеження віддаленої IP-адреси

У розділі глави 11 ми бачили, що UDP сервер може визначити віддалену IP-адресу і номер порту, на додаток до вказаних локальної IP-адреси і номера порту. Функції інтерфейсу, наведені в RFC 793, дозволяють серверу здійснювати пасивне відкриття на основі повністю описаного віддаленого сокету (у цьому випадку очікується запит на активне відкриття від конкретного клієнта) або не зазначеного віддаленого сокету (у цьому випадку очікується запит на з'єднання від будь-якого клієнта).

На жаль, більшість API не надають таких можливостей. Сервер повинен залишати віддалений сокет неконкретизованим, чекаючи на прибуття з'єднання, а потім перевіряючи IP адресу та номер порту клієнта.

На малюнку 18.22 показано три типи адрес та взаємозв'язку адрес з портами, які TCP сервер може встановити для себе. У всіх випадках lport це наперед відомий порт сервера, а localIP має бути IP адресою локального інтерфейсу. Порядок, в якому розташовані три рядки в таблиці, відповідає порядку, в якому модуль TCP намагається визначити, яка локальна кінцева точка прийме вхідний запит на з'єднання. Спочатку здійснюється спроба, що відповідає першому рядку таблиці (якщо підтримується), а потім інші специфікації (останній рядок з IP адресами, вказаними у вигляді символів підстановки) пробуються останнім.

Рисунок 18.22 Вказівка ​​локальної та віддаленої IP-адрес та номерів порту для TCP сервера.

Вхідна черга запитів на з'єднання

Конкурентний сервер запускає новий процес, який обслуговує кожного клієнта, тому сервер, що слухає, повинен бути завжди готовий обробити наступний вхідний запит на з'єднання. Це основна причина, через яку використовуються конкурентні сервери. Однак, існує ймовірність того, що кілька запитів на з'єднання прибудуть саме в той момент, коли сервер, що слухає, створює новий процес, або коли операційна система зайнята обробкою іншого процесу з вищим пріоритетом. Як TCP обробляє ці вхідні запити на з'єднання, поки слухаюча програма зайнята?

Реалізації Berkeley використовують такі правила.

  1. Кожна кінцева точка, що слухає, має фіксовану довжину черги з'єднань, які можуть бути прийняті TCP ("триразове рукостискання" завершено), проте ще не прийняті додатком. Будьте уважні, проводячи різницю між прийняттям з'єднання TCP і розміщенням його в чергу, і додатком, що приймає з'єднання з цієї черги.
  2. Програма вказує обмеження або межу для цієї черги, яка зазвичай називається backlog. Це обмеження має бути в діапазоні від 0 до 5. (Більшість додатків вказують максимальне значеннярівне 5.)
  3. Коли надходить запит на з'єднання (SYN сегмент), TCP переглядає поточну кількість з'єднань, поставлених у теперішній моментв чергу для цієї кінцевої точки, що слухає, при цьому він з'ясовує, чи можна прийняти з'єднання. Ми очікуємо, що значення backlog, вказане додатком, буде максимальним, тобто дозволено поставити в чергу максимальну кількість з'єднань для цієї точки, хоча це не дуже просто. На малюнку 18.23 показано взаємини між значенням backlog та реальною максимальною кількістю сполук, які можна поставити в чергу у традиційних Berkeley системах та Solaris 2.2.

    значення backlog

    Максимальна кількість сполук, поставлених у чергу

    Традиційний BSD

    Малюнок 18.23 Максимальна кількість прийнятих з'єднань для кінцевої точки, що слухає.

    Запам'ятайте, що це значення backlog вказує тільки на максимальну кількість з'єднань, поставлених в чергу для однієї кінцевої точки, що слухає, всі з яких вже прийняті TCP і очікують того, щоб бути прийнятими додатком. Значення backlog не впливає на максимальну кількість з'єднань, яка може бути встановлена ​​системою, або на кількість клієнтів, яка може обслуговувати конкурентний сервер.

    Значення для Solaris на цьому малюнку саме такі, як ми очікували. Традиційні значення для BSD (з якихось незрозумілих причин) дорівнюють значенню backlog, помноженому на 3, поділеному на 2, плюс 1.

  4. Якщо в черзі для даної слухаючої кінцевої точки є місце для нового з'єднання (див. малюнок 18.23), TCP модуль підтверджує (ACK) SYN, що прийшов, і встановлює з'єднання. Додаток сервера зі слухаючою кінцевою точкою не побачить цього нового з'єднання доти, доки не буде прийнято третій сегмент з "триразового рукостискання". Також клієнт може вважати, що сервер готовий прийняти дані, коли активне відкриття клієнта завершено успішно, перш ніж програма сервера буде повідомлено про нове з'єднання. (Якщо це станеться, сервер TCP просто поставить у чергу вхідні дані.)
  5. Якщо не вистачає місця для того, щоб поставити в чергу нове з'єднання, TCP просто ігнорує прийнятий SYN. У відповідь нічого не посилається (не посилається навіть сегмент RST). Якщо сервер, що слухає, не може відмовитися від прийому деяких вже прийнятих з'єднань, які заповнили собою чергу до межі, активне відкриття клієнта буде перервано по тайм-ауту.

Ми можемо переглянути цей сценарій з використанням програми sock. Запустимо її з новою опцією (-O), яка повідомляє про необхідність зробити паузу після створення кінцевої точки, що слухає, перед прийомом будь-якого запиту на з'єднання. Якщо потім ми запустимо кілька клієнтів протягом цієї паузи, сервер буде змушений поставити в чергу прийняті з'єднання, а те, що станеться, ми побачимо з допомогою команди tcpdump.

bsdi% sock-s-v-q1-O30 5555

Опція -q1 встановлює backlog слухаючої кінцевої точки значення 1, для традиційної BSD системи це буде відповідати двом запитам на з'єднання (рисунок 18.23). Опція -O30 змушує програму "проспати" 30 секунд перед прийомом будь-якого з'єднання клієнта. Це дає нам 30 секунд, щоб стартувати кілька клієнтів, які заповнять чергу. Ми стартуємо чотирьох клієнтів на хості sun.

На малюнку 18.24 показаний висновок програми tcpdump, цей висновок починається з першого SYN першого клієнта. (Ми видалили оголошення розміру вікна та оголошення MSS. Також ми виділили номери портів клієнта жирним шрифтом, коли TCP з'єднання встановлюється - "триразове рукостискання".)

Перший запит на з'єднання від клієнта, що прийшов з порту 1090, приймається модулем TCP (сегменти 1-3). Другий запит на з'єднання від клієнта з порту 1091 також приймається модулем TCP (сегменти 4-6). Програма сервера все ще "спить" і не прийняла жодного з'єднання. Все зроблене було здійснено модулем TCP в ядрі. Також слід зазначити, що два клієнти успішно здійснили активне відкриття, тобто "триразове рукостискання" було успішно завершено.

1 0.0 sun. 1090 > bsdi.7777: S 1617152000:1617152000(0)
2 0.002310 (0.0023) bsdi.7777 > sun. 1090 : S 4164096001:4164096001(0)
3 0.003098 (0.0008) сон. 1090 > bsdi.7777: . ack 1617152001
ack 1
4 4.291007 (4.2879) sun. 1091 > bsdi.7777: S 1617792000:1617792000(0)
5 4.293349 (0.0023) bsdi.7777 > sun. 1091 : S 4164672001:4164672001(0)
ack 1617792001
6 4.294167 (0.0008) sun. 1091 > bsdi.7777: . ack 1
7 7.131981 (2.8378) sun.1092 >
8 10.556787 (3.4248) sun.1093 > bsdi.7777: S 1618688000:1618688000(0)
9 12.695916 (2.1391) sun.1092 > bsdi.7777: S 1618176000:1618176000(0)
10 16.195772 (3.4999) sun.1093 >
11 24.695571 (8.4998) sun.1092 > bsdi.7777: S 1618176000:1618176000(0)
12 28.195454 (3.4999) sun. 1093 > bsdi.7777: S 1618688000:1618688000(0)
13 28.197810 (0.0024) bsdi.7777 > sun. 1093 : S 4167808001:4167808001(0)
14 28.198639 (0.0008) sun. 1093 > bsdi.7777: . ack 1618688001
ack 1
15 48.694931 (20.4963) sun. 1092 > bsdi.7777: S 1618176000:1618176000(0)
16 48.697292 (0.0024) bsdi.7777 > sun. 1092 : S 4170496001:4170496001(0)
ack 1618176001
17 48.698145 (0.0009) sun. 1092 > bsdi.7777: . ack 1

Рисунок 18.24 Виведення програми tcpdump для прикладу використання backlog.

Ми спробували стартувати третього клієнта у сегменті 7 (порт 1092) та четвертого у сегменті 8 (порт 1093). TCP ігнорувало обидва SYN, так як черга для цієї кінцевої точки, що слухає, заповнена. Обидва клієнти повторно передали свої SYN у сегментах 9, 10, 11, 12 та 15. Третя повторна передача четвертого клієнта прийнята (сегменти 12-14), тому що 30-секундна пауза сервера закінчилася, і сервер видалив два з'єднання, які були прийняті, очистивши чергу. (Причина, через яку це сталося, полягає в тому, що це з'єднання було прийнято сервером у момент часу 28.19, а не в момент часу, який більше 30; це сталося тому, що знадобилося кілька секунд, щоб стартувати першого клієнта [сегмент 1 , час старту у виведенні] після старту сервера.) Четверта повторна передача третього клієнта також прийнята (сегменти 15-17). З'єднання четвертого клієнта (порт 1093) прийнято сервером перед з'єднанням третього клієнта (порт 1092) через збіг часу між закінченням 30-секундної паузи і повторною передачею клієнта.

Ми могли очікувати, що черга прийнятих сполук буде оброблена додатком відповідно до принципу FIFO (перший увійшов, перший вийшов). Таким чином, після того, як TCP прийняв додаток на порти 1090 і 1091, ми очікували, що програма отримає з'єднання спочатку на порт 1090, а потім з'єднання на порт 1091. Однак, у більшості реалізацій Berkeley існує помилка (bug), в результаті чого використовується порядок LIFO (останній увійшов, перший вийшов). Виробники багато разів намагалися виправити цю помилку, проте вона досі існує у таких системах, як SunOS 4.1.3.

TCP ігнорує вхідний SYN, коли черга заповнена, і не відповідає з використанням RST через помилку. Зазвичай черга заповнена, тому що програма або операційна система зайняті, тому програма не може обробити вхідні з'єднання. Такий стан може змінитися за короткий проміжок часу. Однак, якщо TCP сервер відповів скиданням (reset), активне відкриття клієнта буде перервано (якраз це станеться, якщо сервер не стартував). Так як SYN ігнорований, TCP клієнт буде змушений повторно передати SYN пізніше, сподіваючись, що в черзі з'явиться місце для нового з'єднання.

Тут необхідно обговорити ще одну дуже важливу деталь, яка є практично у всіх реалізаціях TCP/IP. Вона у тому, що TCP приймає вхідний запит на з'єднання (SYN) у разі, якщо у черзі є місце. При цьому програма не може подивитися, від кого надійшов запит (IP адреса джерела та номер порту джерела). Це не потрібно TCP, це лише загальна техніка, що використовується в реалізаціях. Якщо API, такий як TLI (розділ глави 1), повідомляє додаток про прибуття запиту на з'єднання і дозволяє додатку вибрати, прийняти це з'єднання чи ні, то при використанні TCP виходить так, що коли додатку повідомляється, що з'єднання щойно прибуло, насправді TCP вже завершив "триразове рукостискання"! В інших транспортних рівнях існує можливість розмежувати прибуле і прийняте з'єднання (OSI транспортний рівень), проте TCP такої можливості не надає.

Solaris 2.2 надає опцію, яка не дозволяє TCP приймати вхідний запит на з'єднання, доки йому це не дозволить програму (tcp_eager_listeners у розділі програми E).

Ця поведінка також означає, що TCP сервер не може зробити так що активне відкриття клієнта буде перервано. Коли з'єднання від нового клієнта потрапляє до програми сервера, "тристороннє рукостискання" TCP вже закінчено і активне відкриття клієнта завершено успішно. Якщо сервер потім дивиться на IP-адресу клієнта і номер порту і вирішує, що він не хоче обслуговувати цього клієнта, всі сервери можуть просто закрити з'єднання (при цьому буде надіслано FIN) або скинути з'єднання (буде послано RST). У будь-якому випадку клієнт вважатиме, що з сервером все нормально, оскільки завершилося активне відкриття, і, цілком можливо, вже надіслав серверу запит.

Короткі висновки

Перед тим, як два процеси зможуть обмінюватися даними з використанням TCP, вони повинні встановити з'єднання між собою. Коли роботу між ними закінчено, з'єднання має бути розірвано. У цьому розділі детально розглянуто, як встановлюється з'єднання з використанням "триразового рукостискання" і як воно розривається з використанням чотирьох сегментів.

Ми використовували tcpdump, щоб показати всі поля у заголовку TCP. Ми також переглянули, як встановлене з'єднання може бути перервано по тайм-ауту, як скидається з'єднання, що відбувається з напіввідкритим з'єднанням і як TCP надає напівзакритий режим, одночасне відкриття та одночасне закриття.

Щоб зрозуміти функціонування TCP, необхідно розглянути фундаментальну діаграму зміни станів TCP. Ми розглянули пункти, як встановлюється та розривається з'єднання, і які при цьому відбуваються зміни в стані. Також ми розглянули, як сервери TCP здійснюють встановлення з'єднань TCP.

TCP з'єднання унікально ідентифікуються 4 параметрами: локальною IP адресою, локальним номером порту, віддаленою IP адресою та віддаленим номером порту. Якщо з'єднання розривається, одна сторона все одно повинна пам'ятати про це з'єднання, в цьому випадку ми говоримо, що працює режим TIME_WAIT. Правило говорить, що ця сторона може здійснити активне відкриття, увійшовши в цей режим, після того як минув подвоєний час MSL, прийнятий для цієї реалізації.

Вправи

  1. У розділі ми сказали, що вихідний номер послідовності (ISN) зазвичай встановлюється в 1 і збільшується на 64000 кожні півсекунди і щоразу, коли здійснюється активне відкриття. Це означає, що молодші три цифри в ISN завжди будуть 001. Однак на малюнку 18.3 ці молодші три цифри для кожного напряму дорівнюють 521. Як це сталося?
  2. На малюнку 18.15 ми надрукували 12 символів, але бачили, що TCP надіслав 13 байт. На малюнку 18.16 ми надрукували 8 символів, проте TCP надіслав 10 байт. Чому в першому випадку було додано 1 байт, а в другому 2 байти?
  3. У чому полягає відмінність між напіввідкритим з'єднанням та напівзакритим з'єднанням?
  4. Якщо ми стартуємо програму sock як сервер, а потім перервемо її роботу (при цьому до неї не було підключено жодного клієнта), ми можемо негайно перестартувати сервер. Це означає, що він не перебуватиме в стані очікування 2MSL. Поясніть це у термінах діаграми зміни станів.
  5. У розділі ми показали, що клієнт не може повторно використовувати той самий локальний номер порту, поки порт є частиною з'єднання в стані очікування 2MSL. Однак, якщо ми запустимо програму sock двічі поспіль як клієнт, приєднуючись до сервера часу, ми можемо використовувати той самий локальний номер порту. Крім того, ми можемо створити нове з'єднання, яке буде в стані очікування 2MSL. Як це відбувається?

    sun % sock -bsdi daytime

    Wed Jul 7 07:54:51 1993
    connection closed by peer

    sun % sock-v-b1163 bsdi daytimeповторне використання того ж номера локального порту
    connected on 140.252.13.33.1163 to 140.252.13.35.13
    Wed Jul 7 07:55:01 1993
    connection closed by peer

  6. В кінці розділу , коли ми описували стан FIN_WAIT_2, ми вказали, що більшість реалізацій переводить з'єднання з цього стану в стан CLOSED, якщо програма здійснила повне закриття (не наполовину закритий) приблизно через 11 хвилин. Якщо інша сторона (в стані CLOSE_WAIT) чекає 12 хвилин перед закриттям (відправлення свого FIN), що його TCP отримає у відповідь на FIN?
  7. Яка сторона в телефонній розмовіздійснює активне відкриття, а яка здійснює пасивне відкриття? Чи можливе одночасне відкриття? Чи можливе одночасне закриття?
  8. На малюнку 18.6 ми не бачили запит ARP або ARP відгук. Однак апаратна адреса хоста svr4 має бути в ARP кеші bsdi. Що зміниться на цьому малюнку, якщо цього пункту в ARP кеші немає?
  9. Поясніть наступний висновок команди tcpdump. Порівняйте його з малюнком 18.13.

    1 0.0 solaris.32990 > bsdi.discard: S 40140288:40140288 (0)
    win 8760
    2 0.003295 (0.0033) bsdi.discard > solaris.32990: S 4208081409:4208081409 (0)
    ack 40140289 win 4096

    3 0.419991 (0.4167) solaris.32990 > bsdi.discard: P 1:257 (256) ack 1 win 9216
    4 0.449852 (0.0299) solaris.32990 > bsdi.discard: F 257:257 (0) ack 1 win 9216
    5 0.451965 (0.0021) bsdi.discard > solaris.32990: . ack 258 win 3840
    6 0.464569 (0.0126) bsdi.discard > solaris.32990: F 1:1 (0) ack 258 win 4096
    7 0.720031 (0.2555) solaris.32990 > bsdi.discard: . ack 2 win 9216

  10. Чому серверу на малюнку 18.4 не скомбінувати ACK на FIN клієнта зі своїм власним FIN, зменшивши тим самим кількість сегментів до трьох?
  11. На малюнку 18.16 чому номер послідовності RST дорівнює 26368002?
  12. Скажіть, чи базується запит TCP до канального рівня про його MTU на принципі розбиття на рівні?
  13. демультиплексуються з урахуванням номера порту призначення TCP. Чи правильно це?
Встановлення TCP-з'єднання

У протоколі TCP-з'єднання встановлюються за допомогою "потрійного рукостискання", описаного в розділі "Встановлення з'єднання". Щоб встановити з'єднання, одна сторона (наприклад, сервер) пасивно очікує на вхідне з'єднання, виконуючи примітиви LISTEN і ACCEPT, або вказуючи конкретне джерело, або не вказуючи його.

Інша сторона (наприклад, клієнт) виконує примітив CONNECT, вказуючи IP-адресу та порт, з яким він хоче встановити з'єднання, максимальний розмір TCP-сегменту і, за бажанням, деякі дані користувача (наприклад, пароль). Примітив CONNECT посилає TCP-сегмент із встановленим бітом SYN і скинутим бітом АСК і чекає на відповідь.

Коли цей сегмент прибуває в пункт призначення, TCP-сутність перевіряє, чи виконав якийсь процес примітив LISTEN, вказавши як параметр той самий порт, який міститься в полі Порт одержувача. Якщо такого процесу немає, вона відповідає відправленням сегмента із встановленим бітом RST для відмови від з'єднання.

Якщо якийсь процес прослуховує якийсь порт, то вхідний ТСР-сегмент передається цьому процесу. Останній може прийняти з'єднання або відмовитись від нього. Якщо процес приймає з'єднання, він посилає у відповідь підтвердження. Послідовність TCP-сегментів, що посилаються в нормальному випадку (рис. а) Зверніть увагу на те, що сегмент із встановленим бітом SYN займає 1 байт простору порядкових номерів, що дозволяє уникнути неоднозначності в їх підтвердженнях.

Якщо два хоста одночасно спробують встановити з'єднання один з одним, то послідовність подій, що відбуваються при цьому, буде відповідати рис. б. В результаті буде встановлено лише одне з'єднання, а не два, оскільки пара кінцевих точок однозначно визначає з'єднання. Тобто якщо обидві з'єднання намагаються ідентифікувати себе за допомогою пари (х, у), робиться лише один табличний запис для (х, у).

Початкове значення порядкового номера з'єднання не дорівнює нулю з обговорюваних вище причин. Використовується схема, заснована на таймері, що змінює стан кожні 4 мкс. Для більшої надійності хосту після збою забороняється перезавантажуватися раніше, ніж після максимального часу життя пакета. Це дозволяє гарантувати, що жоден пакет від колишніх з'єднань не блукає десь в Інтернеті.

Розрив з'єднання TCP

Хоча TCP-з'єднання є повнодуплексними, щоб зрозуміти, як відбувається їхнє роз'єднання, краще вважати їх парами симплексних з'єднань. Кожне симплексне з'єднання розривається незалежно від свого партнера. Щоб розірвати з'єднання, будь-яка із сторін може надіслати TCP-сегмент із встановленим в одиницю бітом FIN, що означає, що у нього більше немає даних для передачі. Коли цей TCP-сегмент отримує підтвердження, цей напрямок передачі закривається. Проте дані можуть продовжувати передаватися невизначено довго у протилежному напрямку. З'єднання розривається, коли обидва напрямки закриваються. Зазвичай для розриву з'єднання потрібні чотири TCP-сегменти: по одному з бітом FIN і по одному з бітом АСК у кожному напрямку. Перший біт АСК та другий біт FIN можуть також утримуватися в одному ТСР-сегменті, що зменшить кількість сегментів до трьох.

Як при телефонній розмові, коли обидва учасники можуть одночасно попрощатися та повісити трубки, обидва кінці TCP-з'єднання можуть надіслати FIN-cerменти в один і той же час. Вони обидва одержують звичайні підтвердження, і з'єднання закривається. По суті, між одночасним та послідовним роз'єднаннями немає жодної різниці.

Щоб уникнути проблеми двох армій, використовуються таймери. Якщо відповідь на надісланий FIN-сегмент не надходить протягом двох максимальних інтервалів часу життя пакета, відправник розриває з'єднання. Інша сторона врешті-решт помітить, що їй ніхто не відповідає і також розірве з'єднання. Хоча таке рішення і не ідеальне, але з огляду на недосяжність ідеалу доводиться користуватися тим, що є. Насправді проблеми виникають досить рідко.

Управління передачею в TCP

Як було зазначено раніше, управління вікном у TCP не прив'язано безпосередньо до підтверджень, як це зроблено більшості протоколів передачі. Наприклад, припустимо, що одержувач має 4096-байтовий буфер. Якщо відправник передає 2048-байтовий сегмент, який успішно приймається одержувачем, одержувач підтверджує його отримання. Однак при цьому в одержувача залишається лише 2048 байт вільного буферного простору (поки додаток не забере скільки-небудь даних з буфера), про що він і повідомляє відправнику, вказуючи відповідний розмір вікна (2048) і номер очікуваного очікуваного байта.

Після цього відправник посилає ще 2048 байт, отримання яких підтверджується, але розмір вікна оголошується рівним 0. Відправник повинен припинити передачу до тих пір, поки хост, що отримує, не звільнить місце в буфері і не збільшить розмір вікна.

При нульовому розмірі вікна відправник може посилати сегменти, крім двох випадків. По-перше, дозволяється надсилати термінові дані, наприклад, щоб користувач міг знищити процес, що виконується на віддаленій машині. По-друге, відправник може послати 1-байтовий сегмент, просячи одержувача повторити інформацію про розмір вікна і очікуваний наступний байт. Стандарт TCP явно передбачає цю можливість для запобігання тупиковим ситуаціям у разі втрати оголошення про розмір вікна.

Відправники не зобов'язані передавати дані відразу, як тільки вони надходять від додатка. Також ніхто не вимагає від одержувачів надсилати підтвердження якнайшвидше. Наприклад TCP-сутність, отримавши від програми перші 2 Кбайт даних і знаючи, що доступний розмір вікна дорівнює 4 Кбайт, була б цілком права, якби просто зберегла отримані дані в буфері доти, доки не прибудуть ще 2 Кбайт даних, щоб передати Одночасно сегмент з 4 Кбайт корисного навантаження. Ця свобода дій може бути використана для поліпшення продуктивності.

Розглянемо TELNET-з'єднання з інтерактивним редактором, що реагує на кожне натискання кнопки. У гіршому випадку, коли символ приходить до передавальної TCP-сутності, вона створює 21-байтовий TCP-сегмент і передає його IP-рівню, який, своєю чергою, посилає 41-байтову IP-дейтаграму.

На стороні TCP-сутність негайно відповідає 40-байтовим підтвердженням (20 байт TCP-заголовка і 20 байт IP-заголовка). Потім, коли редактор прочитає цей байт з буфера, TCP-сутність надішле оновлену інформацію про розмір буфера, пересунувши вікно на 1 байт вправо. Розмір цього пакета складає 40 байт. Нарешті, коли редактор обробить цей символ, він відправляє назад відлуння, що передається 41-байтовим пакетом. Разом для кожного введеного з клавіатури символу пересилається чотири пакети загальним розміром 162 байти. У разі дефіциту пропускної спроможності ліній цей спосіб роботи небажаний.

Для покращення ситуації багато реалізації TCP використовують затримку підтверджень та оновлень розміру вікна на 500 мс в надії отримати додаткові дані, разом з якими можна буде надіслати підтвердження одним пакетом. Якщо редактор встигне видати луну протягом 500 мс, віддаленому користувачеві потрібно буде вислати лише один 41-байтовий пакет, таким чином, навантаження на мережу знизиться вдвічі.

Хоча такий метод затримки і знижує навантаження на мережу, проте ефективність використання мережі відправником продовжує залишатися невисокою, оскільки кожен байт пересилається в окремому 41-байтовому пакеті. Метод, що дозволяє підвищити ефективність, відомий як алгоритм Нагля (Nagle, 1984). Пропозиція Нагля звучить досить просто: якщо дані надходять відправнику по одному байту, відправник просто передає перший байт, а інші поміщає в буфер, доки не буде отримано підтвердження прийому першого байта. Після цього можна переслати усі накопичені в буфері символи у вигляді одного TCP-сегменту і знову почати буферизацію до отримання підтвердження надісланих символів. Якщо користувач вводить символи швидко, а мережа повільна, то в кожному сегменті буде передаватися значна кількість символів, таким чином навантаження на мережу буде істотно знижено. Крім того, цей алгоритм дозволяє надсилати новий пакет, навіть якщо кількість символів у буфері перевищує половину розміру вікна або максимальний розмір сегмента.

Алгоритм Нагля широко застосовується різними реалізаціями протоколу TCP, проте іноді бувають ситуації, у яких краще відключити. Зокрема, під час роботи програми X-WindowsВ Інтернеті інформація про переміщення миші пересилається на віддалений комп'ютер. (X-Window - це система управління вікнами у більшості ОС типу UNIX). Якщо буферизувати ці дані для пакетного пересилання, курсор переміщатиметься ривками з великими паузами, у результаті користуватися програмою буде дуже складно, майже неможливо.

Ще одна проблема, здатна значно знизити продуктивність протоколу TCP, відома під назвою синдрому дурного вікна (Clark, 1982). Суть проблеми полягає в тому, що дані пересилаються TCP-сутністю великими блоками, але сторона, що приймає інтерактивного додатка, зчитує їх посимвольно.

Розглянемо з прикладу - початковий стан таке: TCP-буфер приймальної сторони повний, і відправнику це відомо (тобто розмір вікна дорівнює 0). Потім інтерактивна програма читає один символ з TCP-потоку. TCP-сутність, що приймає, радісно повідомляє відправнику, що розмір вікна збільшився, і що він тепер може послати 1 байт. Відправник кориться і посилає 1 байт. Буфер знову виявляється повним, про що одержувач і повідомляє, посилаючи підтвердження для 1-байтового сегмента з нульовим розміром вікна. І так може продовжуватися вічно.

Девід Кларк (David Clark) запропонував заборонити стороні, що приймає, відправляти інформацію про однобайтовий розмір вікна. Натомість одержувач повинен почекати, поки в буфері не накопичиться значна кількість вільного місця. Зокрема, одержувач не повинен надсилати відомості про новий розмір вікна доти, доки він не зможе прийняти сегмент максимального розміру, який він оголошував під час встановлення з'єднання, або його буфер не звільнився хоча б наполовину.

Крім того, збільшенню ефективності відправки може сприяти сам відправник, відмовляючись від надсилання надто маленьких сегментів. Натомість він повинен почекати, поки розмір вікна не стане достатньо великим для того, щоб можна було послати повний сегмент або щонайменше рівний половині розміру буфера одержувача. (Відправник може оцінити цей розмір за послідовністю повідомлень розмір вікна, отриманих ним раніше.)

У задачі рятування від синдрому дурного вікна алгоритм Нагля і рішення Кларка доповнюють один одного. Нагль намагався вирішити проблему програми, що надає дані TCP-сутності посимвольно. Кларк намагався вирішити проблему програми, яка символічно отримує дані у TCP. Обидва рішення хороші і можуть працювати одночасно. Суть їх у тому, ніж посилати і просити передавати дані занадто малими порціями.

TCP-сутність, що приймає, може піти ще далі в справі підвищення продуктивності, просто оновлюючи інформацію про розмір вікна великими порціями. Як і відправляюча TCP-сутність, вона також може буферизувати дані і блокувати запит на читання READ, що надходить від програми, доки у неї не накопичиться великого обсягу даних. Таким чином, знижується кількість звернень до TCP-сутності та, отже, знижуються накладні витрати. Звичайно, такий підхід збільшує час очікування відповіді, але для неінтерактивних програм, наприклад при передачі файлу, скорочення часу, витраченого на всю операцію, значно важливіше збільшення часу очікування відповіді на окремі запити.

Ще одна проблема одержувача полягає у сегментах, отриманих у неправильному порядку. Вони можуть зберігатися або відкидатися на розсуд одержувача. Зрозуміло, підтвердження може бути надіслано, тільки якщо всі дані аж до підтвердженого байта отримані. Якщо до одержувача доходять сегменти О, 1, 2, 4, 5, 6 і 7, він може підтвердити отримання даних до останнього байта сегмента 2. Коли у відправника закінчиться час очікування, він передасть сегмент 3 ще раз. Якщо на момент прибуття сегмента 3 одержувач збереже в буфері сегменти з 4-го по 7-й, він зможе підтвердити отримання всіх байтів, аж до останнього байта сегмента 7.

- 1 Потрійне рукостискання TCP

Процес початку сеансу TCP (також званий «рукостискання», складається з трьох кроків).

1. Клієнт, який має намір встановити з'єднання, надсилає серверу сегмент із номером послідовності та прапором SYN.

  • Сервер отримує сегмент, запам'ятовує номер послідовності та намагається створити сокет (буфери та керуючі структури пам'яті) для обслуговування нового клієнта.
    • У разі успіху сервер посилає клієнту сегмент із номером послідовності та прапорами SYN та ACK, і переходить у стан SYN-RECEIVED.
    • У разі невдачі сервер посилає клієнту сегмент із прапором RST.

2. Якщо клієнт отримує сегмент з прапором SYN, він запам'ятовує номер послідовності і посилає сегмент з прапором ACK.

  • Якщо він одночасно отримує і прапор ACK (що зазвичай і відбувається), він переходить у стан ESTABLISHED.
  • Якщо клієнт отримує сегмент з прапором RST, він припиняє спроби з'єднатися.
  • Якщо клієнт не отримує відповіді протягом 10 секунд, він повторює процес з'єднання заново.

3. Якщо сервер може SYN-RECEIVED отримує сегмент з прапором ACK, він перетворюється на стан ESTABLISHED.

  • В іншому випадку після тайм-ауту він закриває сокет і переходить у стан CLOSED.

Процес називається «трьохетапним узгодженням», оскільки незважаючи на те, що можливий процес встановлення з'єднання з використанням чотирьох сегментів (SYN у бік сервера, ACK у бік клієнта, SYN у бік клієнта, ACK у бік сервера), на практиці для економії часу використовується три сегмента.

TCP - вікно

У цьому полі міститься число, що визначає в байтах розмір даних, які відправник може відправити без отримання підтвердження.

TCP вікно – алгоритм управління інтенсивністю потоку даних, заснований на зміні максимальної кількості даних, яку одержувач готовий прийняти і підтвердити одним сегментом у відповідь
Розмір вікна завжди призначає одержувач, виходячи зі статистики кількості помилок

Домени колізій

Домен колізій- це область мережіEthernet, всі вузли якої розпізнають колізію незалежно від того, в якій частині цієї галузі колізія виникла

  • Колізія, що виникла, не поширюється за рамкивідповідного домену колізій
  • Чим більша кількість доменів колізій, тим менш помітні наслідки кожної колізії.
  • Для розбиття мережі на доміни колізій застосовують комутатори

Режими роботи VTP на комутаторах

VTP - VLAN Trunking Protocol, що дозволяє полегшити адміністрування комутаторів, а саме управління VLAN-ами на комутаторах Cisco. З VTP можна створювати, змінювати або видаляти VLAN на VTP сервері і всі ці зміни автоматично перенесуться на всі комутатори в одному домені VTP. Що позбавляє адміністратора конфігурування VLAN на кожному комутаторі.
Існує три режими роботи VTP (VTP mode):

1. VTP Server - серверний режим. У цьому режимі можна створювати, видаляти та змінювати VLAN-и, а також задавати різні параметри, такі як версію протоколу (vtp version), vtp фільтрацію (vtp pruning) для всього VTP домену. VTP сервер сповіщає про свою конфігурацію VLAN-ів інші комутатори, що знаходяться в тому ж домені VTP, і синхронізує їх конфігурацію VLAN. Також VTP сервер може синхронізувати свою конфігурацію з конфігурацією VTP клієнта, якщо у клієнта вищий номер ревізії конфігурації. Обмін VTP інформацією відбувається через ТРАНКВІ порти.

2. VTP Client – ​​режим клієнта. На комутаторі з VTP Client не можна створювати, видаляти або змінювати VLAN. Все налаштування VLAN-ів комутатор бере від сервера VTP.

3. VTP Transparent – ​​прозорий режим. У цьому режимі комутатор не застосовує конфігурацію VLAN від VTP сервера і не сповіщає про свою конфігурацію інші комутатори, але дозволяє пропускати через свої транкові порти VTP сповіщення від інших комутаторів.

Квітування

В рамках з'єднання правильність передачі кожного сегмента має підтверджуватись квитанцією одержувача. Квітування- це один із традиційних методів забезпечення надійного зв'язку. Ідея квитування полягає в наступному.

Для того, щоб можна було організувати повторну передачу спотворених даних відправник нумерує одиниці передаваних даних, що відправляються (далі для простоти звані кадрами). Для кожного кадру відправник очікує від приймача так звану позитивну квитанцію - службове повідомлення, що повідомляє, що вихідний кадр був отриманий і дані в ньому виявилися коректними. Час цього очікування обмежений - при відправленні кожного кадру передавач запускає таймер, і якщо після його закінчення позитивна квитанція на отримана, то кадр вважається загубленим. У деяких протоколах приймач, у разі отримання кадру зі спотвореними даними, повинен відправити негативну квитанцію - явна вказівка ​​того, що цей кадр потрібно передати повторно.


Функції транспортного рівня

  • забезпечує логічне з'єднання між додатками;
  • реалізує надійну передачу даних;
  • забезпечує контроль швидкості передачі.

Сокети

Сокет(Socket, гніздо) - це структура даних, що ідентифікує мережне з'єднання.

Навіщо потрібні сокети? Сервер (програма) може одночасно підтримувати кілька TCP-з'єднань з іншими комп'ютерами, використовуючи той самий стандартний номер порту. Як це реалізувати? Можна покласти це завдання на програміста. Нехай він вибирає з буфера прийому мережного рівня пакети, дивиться від когось вони відправлені і відповідає відповідним чином. Але можна зробити це зручніше.

З кожним з'єднанням повинен бути пов'язаний свій потік, в який можна писати інформацію і з якого її можна зчитувати. Кожному потоку відповідає свою IP-адресу віддаленого комп'ютерата свій порт віддаленого комп'ютера. Будемо назвати структуру даних, яка відповідає кожному такому потоку, сокетом (розеткою). Таким чином сервер можна порівняти з розгалужувачем з купою розеток, до яких підключені клієнти.

Якщо зробити так, то замість того, щоб розбиратися в купі розносортних пакетів з буфера прийому мережного рівня, сервер читатиме з потоків, кожен з яких відповідає своєму клієнту. Дані від клієнтів не звалюватимуться в купу, а розподілятимуться по потоках-сокетах. Відповідальність за такий розподіл лягає не так на програміста, але в драйвер транспортного рівня операційної системи.

Сокети були розроблені в університеті Каліфорнії у місті Berkeley, стали стандартом де-факто на противагу OSI TLI (Transport Layer Interface).

Історична довідка. Розкол UNIX

З 1978 починає свою історію BSD UNIX, створений в університеті Берклі. Автором BSD був Білл Джой. На початку 1980-х компанія AT&T, якій належали Bell Labs, усвідомила цінність UNIX та розпочала створення комерційної версії UNIX. Важливою причиною розколу UNIX стала реалізація 1980 р. стеку протоколів TCP/IP. До цього міжмашинна взаємодія в UNIX перебувала в зародковому стані - найбільш суттєвим способом зв'язку був UUCP (засіб копіювання файлів з однієї UNIX-системи в іншу, що спочатку працювало по телефонним мережамза допомогою модемів).

Ці дві Операційні системиреалізували 2 різні інтерфейси програмування мережевих додатків: Berkley sockets (TCP/IP) та інтерфейс транспортного рівня TLI (OSI ISO) (англ. Transport Layer Interface). Інтерфейс Berkley sockets був розроблений в університеті Берклі та використовував стек протоколів TCP/IP, розроблений там же. TLI був створений AT&T відповідно до визначення транспортного рівня моделі OSI. Спочатку в ній не було реалізації TCP/IP чи інших мережевих протоколів, але такі реалізації надавалися сторонніми фірмами. Це, як і інші міркування (переважно ринкові), викликало остаточне розмежування між двома гілками UNIX - BSD (університету Берклі) та System V (комерційна версія від AT&T). Згодом багато компаній, ліцензувавши System V у AT&T, розробили власні комерційні різновиди UNIX, такі як AIX, HP-UX, IRIX, Solaris.

Примітиви сокетів

SOCKET створити новий (порожній) сокет
BIND сервер пов'язує свою локальну адресу (порт) із сокетом
LISTEN сервер виділяє пам'ять під чергу підключень клієнтів (TCP)
ACCEPT сервер чекає на підключення клієнта або приймає перше підключення з черги (TCP). Щоб заблокувати очікування вхідних з'єднань, сервер виконує примітив ACCEPT. Отримавши запит з'єднання, транспортний модуль ОС створює новий сокет з тими самими властивостями, що у вихідного сокету, і повертає описувач файлу для нього. Після цього сервер може розгалужити процес або потік, щоб обробити з'єднання для нового сокету та паралельно чекати наступного з'єднання для оригінального сокету
CONNECT клієнт запитує з'єднання (TCP)
SEND / SEND_TO надіслати дані (TCP/UDP)
RECEIVE / RECEIVE_FROM отримати дані (TCP/UDP)
DISCONNECT запит роз'єднання (TCP)

Мультиплексування та демультиплексування

Мультиплексування- Збір повідомлень від сокетів всіх додатків і додавання заголовків.

Демультиплексування- розподіл даних по сокетам.

Для UDP потрібний сокет визначається номером порту одержувача, для TCP - номером порту одержувача, IP-адресою та номером порту відправника.

Протоколи транспортного рівня

На транспортному рівні діє два протоколи: TCP (надійний) та UDP (ненадійний).

Протокол UDP

UDP (User Datagram Protocol) виконує мінімум дій, дозволяючи програмі майже безпосередньо працювати з мережевим рівнем. Працює набагато швидше за TCP, тому що не потрібно встановлювати з'єднання і чекати підтвердження доставки. Можливі втрати сегментів. Здійснює контроль коректності даних, що передаються (контрольна сума).

Структура UDP-сегменту

Заголовок всього 8 байт.

Принципи надійної передачі

Спроектуємо протокол myTCP, послідовно його ускладнюючи.

  • Стан протоколу myTCP 1.0. Передача абсолютно надійним каналом
  • Стан протоколу myTCP 1.0 (відправник) (передача абсолютно надійним каналом).JPG

    Відправник

    Стан протоколу myTCP 1.0 (одержувач) (передача абсолютно надійним каналом).JPG

    Одержувач

  • Стан протоколу myTCP 2.0. Передача каналом, що допускає спотворення бітів. Втрати пакетів неможливі
  • Стан протоколу myTCP 2.0 (відправник) (передача каналом, що допускає спотворення бітів. Втрати пакетів неможливі).JPG

    Відправник

    Стан протоколу myTCP 2.0 (одержувач) (передача каналом, що допускає спотворення бітів. Втрати пакетів неможливі).JPG

    Одержувач

Але квитанції також можуть губитися. Якщо квитанція спотворена відправник знову надсилає пакет. Отримувач повинен думати як обробляти повторні пакети (потрібно запровадити новий стан - передали минулий пакет додатку чи ні).

Роль ідентифікаторів «повторний» і «новий» у TCP/IP відіграють номери пакетів (бо пакети ще можуть губитися).

  • Стан протоколу myTCP 2.1. Передача каналом, що допускає спотворення бітів. Втрати пакетів неможливі
  • Стан протоколу myTCP 2.1 (відправник) (передача каналом, що допускає спотворення бітів. Втрати пакетів неможливі).JPG

    Відправник

    Стан протоколу myTCP 2.1 (одержувач) (передача каналом, що допускає спотворення бітів. Втрати пакетів неможливі).JPG

    Одержувач

Головна різниця між станами одержувача полягає в тому, як обробляються повторні пакети. У стані «Минулий пакет передано додатку» ми викидаємо повторні пакети, а в стані «Минулий пакет не був переданий додатком» ми їх приймаємо та передаємо додатку.

Тепер настав час згадати, що пакети можуть губитися.

  • Потрібно вміти визначати факт втрати пакета, наприклад, засікати час після відправлення пакета.
  • Потрібно нумерувати пакети.
  • У квитанціях потрібно вказувати номер пакета, на який її відправлено.

Таким чином, ми приходимо до потреби таймера. У випадку, якщо пройшло якесь визначений часі підтвердження не надійшло, то здійснюється повторне надсилання повідомлення. Інтервал часу - невеликий т.к. Можливість втрати приймається близькою до 1 (це дійсно так навіть для хорошого WiFi-з'єднання).

Недоліки протоколів з очікуванням на підтвердження

Розглянемо приклад. Нехай є 1Гб-канал Ростов – Москва. Порахуємо час відправки 1000 байт (або 8000 біт):

8000 біт/1 Гб/с = 8 мкс

Час розповсюдження сигналу:

1000 км/300 000 км/с = 3333 мкс

Разом: наступні 1000 байт будуть відправлені більш ніж через 6674 мкс.

Висновок: 99,9% часу канал не використовує.

Шлях рішення – збільшити розмір пакету. Але якщо хоча б 1 біт спотвориться, то весь пакет викинуть. Що тоді?

Протоколи ковзного вікна

Вирішення проблеми: дозволити відправнику посилати не один кадр, а кілька, перш ніж зупинитися і перейти в режим очікування підтверджень (квитанцій). Така техніка називається конвеєрною обробкою.

На малюнку зеленим позначені ті квитанції, які вже отримані, жовтим – відправлені, але не отримані, блакитні підготовлені до відправки, а білі не можна відправляти, доки не отримаємо квитанції на жовті. Вікно: жовті та блакитні – це пакети, які можуть бути передані без очікування квитанцій. Перший білий пакет може бути відправлений лише після того, як отримали підтвердження першого жовтого. Тоді вікно рухається на 1 праворуч.

Може виникнути питання: навіщо обмежувати розмір вікна, давайте всі пакети передамо, а потім чекатимемо на підтвердження. Але так робити не можна: легко отримати навантаження в мережі.

Є два способи вирішити проблеми виникнення помилок під час конвеєризації кадрів:

  • GBN (Go Back N – повернення на N пакетів назад);
  • SR (Selective Repeat – вибіркове повторення).
GBN

Одержувач надсилає лише позитивні квитанції і лише про одержання тих пакетів, для яких виконується умова: всі пакети з меншими номерами вже отримані. Таким чином, тут використовується групове квитування: одержання відправником квитанції з номером i означає, що всі пакети до i включно доставлені успішно. Якщо через деякий час відправник не отримує квитанції, він повторює відправлення всіх N пакетів, починаючи з наступного за останнім квитованим.

Метод GBN неефективний при великому вікні та тривалому розповсюдженні пакетів по мережі, в якій трапляються втрати. Приклад: відправили 1000 пакетів, другий не прийшов, доводиться повторювати надсилання всіх, починаючи з другого. Ми засмічуємо мережу марним трафіком.

SR

Цей підхід передбачає надсилання квитанції на кожен пакет. Одержувач зберігає у своєму буфері всі правильні кадри, прийняті після невірного чи втраченого. При цьому неправильний кадр відкидається. Якщо час очікування квитанції на якийсь кадр спливає, відправник знову відправляє цей кадр, не повторюючи відправлення всіх наступних. Якщо друга спроба буде успішною, пакети, що накопичилися в одержувача, можуть бути передані на мережевий рівень, після чого буде вислано підтвердження отримання кадру з найбільшим номером.

Часто вибірковий метод комбінують з відправкою одержувачем "негативного підтвердження" (NAK - Negative Acknowledgement) при виявленні помилки (наприклад, при неправильній контрольній сумі). У цьому ефективність роботи підвищується.

При великому вікні підхід SR може вимагати значного розміру буфера.

Протокол TCP

Формат TCP-сегменту

TCP-сегмент складається з поля даних та кількох полів заголовка. Поле данихмістить фрагмент даних, що передаються між процесами. Розмір поля даних обмежується величиною MSS(Максимальний розмір сегмента). Коли протокол здійснює передачу великого файлу, він, як правило, розбиває дані на фрагменти розміром MSS (крім останнього фрагмента, який зазвичай має менший розмір). Інтерактивні програми, навпаки, часто обмінюються даними, обсяг яких значно менший за MSS. Наприклад, програми віддаленого доступудо мережі, подібні до Telnet, можуть передати транспортному рівню 1 байт даних. Оскільки зазвичай довжина заголовка ТСР-сегмента становить 20 байт (що на 12 байт більше, ніж у UDP), повний розмір сегмента в цьому випадку дорівнює 21 байт.

Як і в протоколі UDP, заголовок включає номери портів відправника та одержувача, призначені для процедур мультиплексування та демультиплексування даних, а також поле контрольної суми. Крім того, до складу TCP-сегменту входять деякі поля.

  • 32-розрядні поля порядкового номера та номера підтвердження. Потрібні для надійної передачі даних.
  • 4-розрядне поле довжини заголовка визначає довжину TCP-заголовка у 32-розрядних словах. Мінімальний розмір становить 5 слів, а максимальний – 15, що становить 20 та 60 байт відповідно. TCP-заголовок може мати змінну довжину завдяки полю параметрів, описаному нижче (зазвичай поле параметрів є порожнім; це означає, що довжина заголовка становить 20 байт).
  • Поле прапори складається з 6 біт. Біт підтвердження (АБК) вказує на те, що значення, що міститься в квитанції, є коректним. Біти RST, SYN та FIN використовуються для встановлення та завершення з'єднання. Встановлений біт PSH інструктує одержувача проштовхнути дані, що накопичилися в приймальному буфері, додаток користувача. Біт URG показує, що в сегменті знаходяться дані, розміщені верхнім рівнем як термінові. Розташування останнього байта термінових даних вказано у 16-розрядному полі покажчика термінових даних. На стороні, що приймає, протокол TCP повинен повідомити верхній рівень про наявність термінових даних у сегменті і передати йому покажчик на кінець цих даних. На практиці прапори PSH, URG та поле покажчика термінових даних не використовуються. Ми згадали про них лише для повноти опису.
  • 16-розрядне вікно прийому використовується управління потоком даних. Воно містить кількість байтів, яке здатна прийняти сторона, що приймає.
  • Вказівник важливості - 16-бітове значення позитивного зміщення від порядкового номера даному сегменті. Це поле вказує порядковий номер октету, яким закінчуються важливі (urgent) дані. Поле береться до уваги лише для пакетів із встановленим прапором URG.
  • Необов'язкове поле параметрів використовується у випадках, коли сторона, що передає і приймає, «домовляються» про максимальний розмір сегмента, або для масштабування вікна в високошвидкісних мереж. Також у цьому полі визначається параметр тимчасових міток. Додаткову інформаціюможна знайти в документах RFC 854 та RFC 1323 .
Порядкові номери та номери підтвердження

Порядковий номер сегмента- Це номер першого байта цього сегмента.

Номер підтвердження- це порядковий номер наступного очікуваного байта.

Поля порядкового номера та номера підтвердження є найважливішими у заголовку TCP-сегмента, оскільки відіграють ключову роль у функціонуванні служби надійної передачі даних. Однак перед тим, як розглядати роль цих полів у механізмі надійної передачі, звернемося до величин, які протокол TCP містить у ці поля.

Протокол TCP розглядає дані як неструктурований впорядкований потік байтів. Такий підхід проявляється в тому, що TCP призначає порядкові номери не сегментам, а кожному байту, що передається. Виходячи з цього порядковий номер сегмента визначається як порядковий номер першого байта цього сегмента. Розглянемо наступний приклад. Нехай хост А хоче переслати потік даних хосту через TCP-з'єднання. Протокол TCP на стороні, що передає, неявно нумерує кожен байт потоку. Нехай розмір переданого файлустановить 500 000 байт, величина MSS дорівнює 1000 байт і перший байт потоку має порядковий номер 0. TCP розбиває потік даних на 500 сегментів. Першому сегменту надається порядковий номер 0, другому сегменту - номер 1000, третьому сегменту - номер 2000, і т. д. Порядкові номери заносяться в поля порядкових номерів кожного TCP-сегмента.

Тепер розглянемо номери підтвердження. Згадаймо про те, що протокол TCP забезпечує дуплексну передачу даних, тобто через єдине TCP-з'єднання дані між хостами А і можуть передаватися одночасно в обидві сторони. Кожен сегмент, що виходить з хоста, містить порядковий номер даних, що передаються від хоста В до хоста А. Номер підтвердження, який хост А поміщає в свій сегмент, - це порядковий номер наступного байта, очікуваного хостом А від хоста В. Розглянемо наступний приклад. Припустимо, що хост А отримав усі байти з номерами від 0 до 535, надіслані хостом В, і формує сегмент для передачі хосту В. 536 у полі номера підтвердження свого сегмента.

Розглянемо іншу ситуацію. Нехай хост А отримав від хоста У два сегменти, у першому з яких містяться байти з номерами від 0 до 535, а в другому - байти з номерами від 900 до 1000. Це означає, що з будь-якої причини байти з номерами від 536 до 899 були отримані хостом А. У разі хост А очікує появи відсутніх байтів й у полі номера підтвердження свого сегмента поміщає число 536. Оскільки TCP квитує прийняті дані до першого відсутнього байта, кажуть, що він підтримує загальне квитування.

Останній приклад показує дуже важливий аспект функціонування протоколу TCP. Третій сегмент (що містить байти 900-1000) прийнятий хостом А раніше, ніж другий (що містить байти 536-899), тобто з порушенням порядку прямування даних. Виникає питання: як протокол TCP реагує порушення порядку? Якщо отриманий сегмент містить номер послідовності більший, ніж очікуваний, дані з сегмента буферизується, але номер підтвердженої послідовності не змінюється. Якщо згодом буде прийнятий сегмент, що відноситься до очікуваного номера послідовності, порядок даних буде автоматично відновлено виходячи з номерів послідовностей в сегментах. Таким чином TCP відноситься до протоколів SR, але в нього використовується загальне квитування як GBN. Хоча SR – не зовсім чисте. Якщо сторона, що посилається отримує кілька (3) негативних квитанцій на один і той же сегмент x, то вона здогадується, що відбулося перевантаження мережі і сегменти x +1, x +2, x +3, ... теж не були доставлені. Тоді надсилається вся серія з x – як у протоколах GBN.

Проблеми з максимальним розміром сегмента

TCP вимагає явного вказівки максимального розміру сегмента у разі, якщо віртуальне з'єднання здійснюється через сегмент мережі, де максимальний розмір блоку (MTU) менш ніж стандартний MTU Ethernet (1500 байт). У протоколах тунелювання, таких як GRE, IPIP, а також PPPoE MTU тунелю менше стандартного, тому сегмент TCP максимального розміру має довжину пакета більше, ніж MTU. Оскільки фрагментація в переважній більшості випадків заборонена, такі пакети відкидаються.

Прояв цієї проблеми виглядає як «зависання» з'єднань. При цьому «зависання» може відбуватися в довільні моменти часу, а саме тоді, коли відправник використовував сегменти довші за допустимий розмір. Для вирішення цієї проблеми на маршрутизаторах застосовуються правила Firewall-а, що додають параметр MSS у всі пакети, що ініціюють з'єднання, щоб відправник використовував сегменти допустимого розміру. MSS може також управлятися параметрами операційної системи.

Потрійне рукостискання

Щоб встановити з'єднання, хост 2 пасивно очікує вхідного з'єднання, виконуючи примітив ACCEPT.

Хост 2 виконує примітив CONNECT, вказуючи IP-адресу і порт, з яким він хоче встановити з'єднання, максимальний розмір TCP-сегменту і т.п. АСК=0 і чекає на відповідь. Таким чином, хост повідомляє порядковий номер x послідовності бітів від хоста 1 до 2.

Хост 2 посилає у відповідь підтвердження «Connection accepted» (функція accept). Послідовність TCP-сегментів, що посилаються в нормальному випадку, показана на рис: SYN=1 ASK=1, хост 2 повідомляє порядковий номер x послідовності бітів від хоста 2 до 1 і повідомляє, що він очікує продовження даних починаючи з байта № x+1.

Хост 1 (Connect) надсилає підтвердження про отримання згоди встановлення з'єднання.

Боротьба з перевантаженням у TCP

Коли в будь-яку мережу надходить більше даних, ніж вона здатна обробити, у мережі утворюються затори. Інтернет у цьому сенсі не є винятком. Хоча мережний рівень також намагається боротися з перевантаженням, основний внесок у вирішення цієї проблеми, що полягає у зниженні швидкості передачі даних, здійснюється протоколом TCP.

Теоретично з перевантаженням можна боротися за допомогою принципу, запозиченого з фізики, - закону збереження пакетів. Ідея полягає в тому, щоб не передавати в мережу нові пакети, доки її не покинуть (тобто не будуть доставлені) старі. Протокол TCP намагається досягти цієї мети за допомогою динамічного керування розміром вікна.

Перший крок у боротьбі з навантаженням полягає в тому, щоб виявити її. Кілька десятиліть тому виявити перевантаження в мережі було складно. Важко було зрозуміти чому пакет не доставлений вчасно. Крім можливості перевантаження мережі була також велика можливість втратити пакет внаслідок високого рівняперешкоди на лінії.

В даний час втрати пакетів при передачі трапляються відносно рідко, так як більшість міжміських ліній зв'язку є оптоволоконними (хоча в бездротових мережахвідсоток пакетів, що втрачаються через перешкоди, досить високий). Відповідно більшість втрачених пакетів в Інтернеті викликані заторами. Всі TCP-алгоритми Інтернету припускають, що втрати пакетів викликаються навантаженням мережі, і стежать за тайм-аутами як за провісниками проблем.

Перш ніж перейти до обговорення того, як TCP реагує на перевантаження, опишемо спочатку способи її запобігання протоколу. При виявленні навантаження має бути обраний відповідний розмір вікна. Одержувач може вказати розмір вікна, виходячи із кількості вільного місцяу буфері. Якщо відправник матиме на увазі розмір відведеного вікна, переповнення буфера у одержувача не зможе стати причиною проблеми, проте вона все одно може виникнути через перевантаження на якійсь ділянці мережі між відправником і одержувачем.

Боротьба з перевантаженням у TCP

Проілюструємо цю проблему з прикладу водопроводу. На малюнку ми бачимо товсту трубу, що веде до одержувача з невеликою ємністю. Доки відправник не посилає води більше, ніж може поміститися у відро, вода не буде проливатися, на малюнку б обмежувальним фактором є не ємність відра, а пропускна здатність мережі. Якщо з крана у вирву вода буде литися надто швидко, то рівень води у вирві почне підніматися і, зрештою, частина води може перелитися через край вирви.

Рішення, що застосовується в Інтернеті, полягає у визнанні існування двох потенційних проблем: низької пропускної спроможності мережі та низької ємності одержувача – та у роздільному вирішенні обох проблем. Для цього кожен відправник має два вікна: вікно, надане одержувачем, і вікно перевантаження. Розмір кожного їх відповідає кількості байтів, яке відправник має право передати. Відправник керується мінімальним із цих двох значень. Наприклад, одержувач каже: «Посилайте 8 Кбайт», але відправник знає, що якщо він надішле більше 4 Кбайт, то в мережі утворюється затор, тому він посилає все ж 4 Кбайт. Якщо ж відправник знає, що мережа здатна пропустити і більше даних, наприклад 32 Кбайт, він передасть стільки, скільки просить одержувач (тобто 8 Кбайт).

При встановленні з'єднання відправник встановлює розмір вікна перевантаження рівним розміру максимального використовуваного в даному з'єднаннісегмента. Потім він передає один максимальний сегмент. Якщо підтвердження отримання цього сегмента прибуває до закінчення періоду очікування, до розміру вікна додається розмір сегмента, тобто розмір вікна перевантаження подвоюється, і посилаються вже два сегменти. У відповідь на підтвердження одержання кожного з сегментів проводиться розширення вікна навантаження на величину одного максимального сегмента. Допустимо, розмір вікна дорівнює n сегментам. Якщо підтвердження всім сегментів приходять вчасно, вікно збільшується число байтів, відповідне n сегментам. По суті підтвердження кожної послідовності сегментів призводить до подвоєння вікна перевантаження.

Цей процес експоненційного зростання триває доти, доки не буде досягнуто розміру вікна одержувача або не буде вироблено ознаку тайм-ауту, що сигналізує про перевантаження в мережі. Наприклад, якщо пакети розміром 1024, 2048 та 4096 байт дійшли до одержувача успішно, а у відповідь на передачу пакета розміром 8192 байта підтвердження не надійшло у встановлений термін, вікно навантаження встановлюється рівним 4096 байтам. Поки розмір вікна перевантаження залишається рівним 4096 байтам, довші пакети не надсилаються, незалежно від розміру вікна, що надається одержувачем. Цей алгоритм називається затяжним пуском, або повільним пуском. Однак він не такий уже й повільний (Jacobson, 1988). Він експонентний. Усі реалізації протоколу TCP повинні його підтримувати.

Розглянемо тепер механізм боротьби з навантаженням, який використовується в Інтернеті. Крім вікон одержувача та перевантаження, як третій параметр у ньому використовується порогове значення, яке спочатку встановлюється рівним 64 Кбайт. Коли виникає ситуація тайм-ауту (підтвердження не повертається вчасно), нове значення порога встановлюється рівним половині поточного розміру вікна навантаження, а вікно навантаження зменшується до одного максимального сегмента. Потім, як і у попередньому випадку, використовується алгоритм затяжного пуску, що дозволяє швидко виявити межу пропускної спроможності мережі. Однак цього разу експоненційне зростання розміру вікна зупиняється після досягнення ним порогового значення, після чого вікно збільшується лінійно, на один сегмент кожної наступної передачі. По суті, передбачається, що можна спокійно урізати вдвічі розмір вікна навантаження, після чого поступово нарощувати його.

Механізми надійної передачі. Узагальнення

Контрольна сума Виявлення спотворень бітів у прийнятому пакеті
Таймер Відлік інтервалу очікування та вказівка ​​на його закінчення. Останнє означає, що з високим ступенемймовірності пакет або його квитанція втрачено під час передачі. У разі, якщо пакет доставляється із затримкою, але не втрачається (передчасне закінчення інтервалу очікування), або відбувається втрата квитанції, повторна передача призводить до дублювання пакета на стороні, що приймає.
Порядкові номери Послідовна нумерація пакетів, що посилаються стороною, що передає. "Пробіли" в номерах одержуваних пакетів дозволяють зробити висновок про втрати даних. Одноманітні порядкові номери пакетів означають, що пакети дублюють один одного
«+» та «-» квитанції Генерується стороною, що приймає, і вказує стороні, що передає, на те, що відповідний пакет або група пакетів були або не були прийняті. Зазвичай, підтвердження містить порядкові номери успішно прийнятих пакетів. Залежно від протоколу розрізняють індивідуальні та групові підтвердження
Вікно/конвеєр Обмежують діапазон порядкових номерів, які можна використовувати передачі пакетів. Групова передача та квитування дозволяють значно збільшити пропускну спроможністьпротоколів, порівняно з режимом очікування підтверджень. Розмір вікна може бути розрахований на основі можливостей прийому та буферизації приймаючої сторони, а також рівня завантаження мережі

Особливості програмування

  1. Потокове з'єднання TCP
    • ситуація а) можлива за поганого зв'язку, якщо проміжок часу між приходами груп дейтаграм мережного рівня великий:
      • комп'ютер1 один раз використовує функцію send;
      • комп'ютер2 не отримує всю інформацію за один виклик recv (потрібно кілька дзвінків).
    • ситуація б) можлива, якщо інтервал часу між викликами функції send малий та розмір даних малий:
      • комп'ютер1 використовує функцію send кілька разів;
      • комп'ютер2 отримує всю інформацію за один виклик recv.
  2. За протоколом UDP
    • ситуація а) - неможлива
      • комп'ютер1 один раз використовує функцію send, на мережевому рівні UDP-сегмент розбивається кілька пакетів;
      • комп'ютер2 отримує сегмент завжди одним викликом recv і тільки якщо прийшли всі IP-дейтаграми.
    • ситуація б) - неможлива
      • різні виклики функції sendto на комп'ютері1 відповідають різним UDP-датаграмам і різним викликам recvfrom на комп'ютері2.
  3. Якщо буфер у функціях recv і recvfrom менший, ніж розмір надісланих даних, то у випадку UDP частина даних втрачається, а у випадку TCP – залишок зберігається для подальшого виклику recv.
  4. UDP-сервер має 1 сокет, а TCP-сервер має багато різних сокетів (за кількістю одночасно підключених клієнтів) і кожному передається своя інформація.