Жодна з існуючих відповідей не розповідає людям, як shutdown
і як close
працює на рівні протоколу TCP, тому варто додати це.
Стандартне з'єднання TCP припиняється шляхом чотиристоронньої фіналізації:
- Після того, як учасник не має більше даних для надсилання, він надсилає пакет FIN іншому
- Інша сторона повертає ACK для FIN.
- Коли інша сторона також закінчила передачу даних, вона надсилає ще один FIN-пакет
- Початковий учасник повертає ACK і завершує передачу.
Однак є ще один "швидкий" спосіб закрити TCP-з'єднання:
- Учасник надсилає пакет RST і відмовляється від з'єднання
- Інша сторона отримує RST, а потім також відмовляється від з'єднання
У моєму тесті з Wireshark, з параметрами сокета за замовчуванням, shutdown
надсилає пакет FIN на інший кінець, але це все, що він робить. Поки інша сторона не надішле вам пакет FIN, ви все ще можете отримувати дані. Як тільки це сталося, ви Receive
отримаєте результат розміру 0. Отже, якщо ви першим відключили "надіслати", вам слід закрити сокет, як тільки ви закінчите отримувати дані.
З іншого боку, якщо ви зателефонуєте, close
поки з'єднання все ще активне (інша сторона все ще активна, і у вас може бути відсутні дані також у системному буфері), пакет RST буде відправлений на іншу сторону. Це добре для помилок. Наприклад, якщо ви думаєте, що інша сторона надала неправильні дані або вона відмовилася надавати дані (DOS-атака?), Ви можете негайно закрити сокет.
Моя думка щодо правил буде такою:
- Розглянемо
shutdown
раніше, close
коли це можливо
- Якщо ви закінчили отримувати (отримано 0 даних про розмір) до того, як ви вирішили відключитись, перервіть з'єднання після завершення останнього надсилання (якщо таке є).
- Якщо ви хочете нормально закрити з'єднання, відключіть з'єднання (за допомогою SHUT_WR, і якщо ви не переймаєтесь отриманням даних після цього пункту, також із SHUT_RD), і дочекайтеся отримання даних про розмір 0, а потім закрийте розетка.
- У будь-якому випадку, якщо виникла якась інша помилка (наприклад, час очікування), просто закрийте сокет.
Ідеальні реалізації для SHUT_RD та SHUT_WR
Наступне не перевірено, довіряйте на свій страх і ризик. Однак я вважаю, що це розумний і практичний спосіб робити речі.
Якщо стек TCP отримує відключення лише з SHUT_RD, він повинен позначати це з'єднання як більше не очікуваних даних. Будь-які очікувані та наступні read
запити (незалежно від того, в якому потоці вони перебувають) повертаються з нульовим результатом. Однак з’єднання все ще активне та корисне - наприклад, ви можете отримувати дані OOB, наприклад. Також ОС видалить будь-які дані, отримані для цього з'єднання. Але це все, жодні пакунки не будуть відправлені в іншу сторону.
Якщо стек TCP отримує відключення лише з SHUT_WR, він повинен позначати це з'єднання, оскільки більше даних не можна надсилати. Всі запити на очікування запису будуть завершені, але наступні запити на запит закінчуються. Крім того, пакет FIN буде відправлений на іншу сторону, щоб повідомити, що у нас немає більше даних для надсилання.