закрити проти вимкнення розетки?


214

В C я зрозумів, що якщо ми закриємо сокет, це означає, що сокет буде знищений і може бути використаний пізніше.

Як щодо відключення? В описі сказано, що він закриває половину дуплексного з'єднання з цією розеткою. Але чи буде цей сокет знищений як closeсистемний виклик?


Пізніше його не можна повторно використовувати. Це закрито. Готово. Зроблено.
Маркіз Лорн

Відповіді:


191

Це пояснюється у посібнику з мереж Beej. shutdownє гнучким способом блокування зв'язку в одному або обох напрямках. Коли є другий параметр SHUT_RDWR, він блокує як відправлення, так і отримання (як close). Однак, closeце спосіб насправді знищити розетку.

З цим shutdownви все одно зможете отримувати очікувані дані, які вже надсилав колега (спасибі Джої Адамсу за те, що помітив це).


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

91
Велика різниця між вимкненням та закриттям у сокеті полягає у поведінці, коли сокет поділяється іншими процесами. Вимкнення () впливає на всі копії сокета, в той час як close () впливає лише на дескриптор файлу в одному процесі.
Зан Лінкс

5
Однією з причин ви можете скористатися shutdownобома напрямками, але ні close, якщо ви зробили FILEпосилання на сокет, використовуючи fdopen. Якщо ви closeє сокетом, щойно відкритому файлу може бути призначений той самий fd, і подальше використання FILEволі буде читати / записувати неправильне місце, що може бути дуже погано. Якщо ви просто shutdown, подальше використання FILEзаповіту просто помилиться, поки не fcloseбуде викликано.
R .. GitHub СТОП ДОПОМОГАЄТЬСЯ

25
Коментар про TIME_WAIT невірний. Це стосується портів, а не сокетів. Ви не можете повторно використовувати розетки.
Маркіз Лорн

48
-1. І цей пост, і посилання опускають важливу концептуальну причину, яку потрібно використовувати shutdown: передавати EOF однорангові і все ще мати можливість приймати очікувані дані, що надсилається одноранговим.
Joey Adams

144

Жодна з існуючих відповідей не розповідає людям, як shutdownі як closeпрацює на рівні протоколу TCP, тому варто додати це.

Стандартне з'єднання TCP припиняється шляхом чотиристоронньої фіналізації:

  1. Після того, як учасник не має більше даних для надсилання, він надсилає пакет FIN іншому
  2. Інша сторона повертає ACK для FIN.
  3. Коли інша сторона також закінчила передачу даних, вона надсилає ще один FIN-пакет
  4. Початковий учасник повертає ACK і завершує передачу.

Однак є ще один "швидкий" спосіб закрити TCP-з'єднання:

  1. Учасник надсилає пакет RST і відмовляється від з'єднання
  2. Інша сторона отримує RST, а потім також відмовляється від з'єднання

У моєму тесті з Wireshark, з параметрами сокета за замовчуванням, shutdownнадсилає пакет FIN на інший кінець, але це все, що він робить. Поки інша сторона не надішле вам пакет FIN, ви все ще можете отримувати дані. Як тільки це сталося, ви Receiveотримаєте результат розміру 0. Отже, якщо ви першим відключили "надіслати", вам слід закрити сокет, як тільки ви закінчите отримувати дані.

З іншого боку, якщо ви зателефонуєте, closeпоки з'єднання все ще активне (інша сторона все ще активна, і у вас може бути відсутні дані також у системному буфері), пакет RST буде відправлений на іншу сторону. Це добре для помилок. Наприклад, якщо ви думаєте, що інша сторона надала неправильні дані або вона відмовилася надавати дані (DOS-атака?), Ви можете негайно закрити сокет.

Моя думка щодо правил буде такою:

  1. Розглянемо shutdownраніше, closeколи це можливо
  2. Якщо ви закінчили отримувати (отримано 0 даних про розмір) до того, як ви вирішили відключитись, перервіть з'єднання після завершення останнього надсилання (якщо таке є).
  3. Якщо ви хочете нормально закрити з'єднання, відключіть з'єднання (за допомогою SHUT_WR, і якщо ви не переймаєтесь отриманням даних після цього пункту, також із SHUT_RD), і дочекайтеся отримання даних про розмір 0, а потім закрийте розетка.
  4. У будь-якому випадку, якщо виникла якась інша помилка (наприклад, час очікування), просто закрийте сокет.

Ідеальні реалізації для SHUT_RD та SHUT_WR

Наступне не перевірено, довіряйте на свій страх і ризик. Однак я вважаю, що це розумний і практичний спосіб робити речі.

Якщо стек TCP отримує відключення лише з SHUT_RD, він повинен позначати це з'єднання як більше не очікуваних даних. Будь-які очікувані та наступні readзапити (незалежно від того, в якому потоці вони перебувають) повертаються з нульовим результатом. Однак з’єднання все ще активне та корисне - наприклад, ви можете отримувати дані OOB, наприклад. Також ОС видалить будь-які дані, отримані для цього з'єднання. Але це все, жодні пакунки не будуть відправлені в іншу сторону.

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


2
"Якщо ви дзвоните близько, поки з'єднання все ще живе" є тавтологічним, і це не викликає відправлення RST. (1) не потрібно. У (4) тайм-аути не обов'язково є фатальними для з'єднання і не незмінно вказують на те, що ви можете його закрити.
Маркіз Лорнський

4
@EJP Ні, це не тавтологічно. Ви можете shutdown()підключитися, і тоді його вже немає в живих. Ви все ще маєте дескриптор файлу. Ще можна recv()з буфера прийому. І вам ще потрібно зателефонувати, close()щоб розпоряджатися дескриптором файлів.
Павло Шимерда

Це найкраща відповідь щодо формату проводів. Мені буде цікаво більше деталей щодо вимкнення програми SHUT_RD. Немає сигналу TCP, щоб не очікувати більше даних, правда? Чи не існує лише FIN для сигналізації, що не надсилає більше даних?
Павло Шімерда,

3
@ PavelŠimerda Так. TCP не сигналізує про не очікування більшої кількості даних. Це слід враховувати в протоколах високого рівня. На мою думку, це взагалі не обов’язково. Ви можете закрити двері, але ви не можете зупинити людей, які кладуть подарунки перед дверима. Це їх рішення, а не ваше.
Земляний двигун

1
@EJP У вас є фактичні аргументи? Інакше мені це не цікаво.
Павло Шімерда

35

Є деякі обмеження, close()яких можна уникнути, якщо shutdown()замість цього використовувати .

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

close()декрементує кількість посилань дескрипторів (підтримується у записі таблиці файлів і нараховує кількість відкритих в даний час дескрипторів, які посилаються на файл / сокет) і не закриває сокет / файл, якщо дескриптор не дорівнює 0. Це означає, що якщо ви розгалужуєте, очищення відбувається лише після того, як число відліку зменшиться до 0. З shutdown()одним можна ініціювати нормальну послідовність закриття TCP, ігноруючи кількість відліку.

Параметри такі:

int shutdown(int s, int how); // s is socket descriptor

int how може бути:

SHUT_RDабо 0 Далі надходження заборонені

SHUT_WRабо 1 Подальше надсилання заборонено

SHUT_RDWRабо 2 Далі надсилання та отримання заборонені


8
Дві функції призначені для абсолютно різних цілей. Той факт, що остаточне закриття в сокеті ініціює послідовність відключення, якщо вона ще не була ініційована, не змінює факту, що закриття призначене для очищення структур даних сокета, а завершення - для ініціювання послідовності відключення рівня tcp.
Лен Холгейт

25
Ви не можете замість цього "використовувати відключення". Ви також можете використовувати його . але ви повинні закрити розетку деякий час.
Маркіз Лорн

15

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

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


9

Я також мав успіх під Linux, використовуючи shutdown()один pthread, щоб змусити інший pthread, який зараз заблокований, connect()щоб припинити раннє переривання.

В інших ОС (принаймні, OSX), я виявив, що дзвінки close()були досить, щоб connect()не вдатися.


7

"shutdown () насправді не закриває дескриптор файлу - він просто змінює його зручність використання. Щоб звільнити дескриптор сокета, потрібно використовувати close ()." 1


3

Закрити

Закінчивши використовувати сокет, ви можете просто закрити його дескриптор файлів із закриттям; Якщо ще є дані, які очікують на передачу через з'єднання, зазвичай закриття намагається завершити цю передачу. Ви можете керувати такою поведінкою за допомогою параметра SO_LINGER socket, щоб вказати період очікування; див. Параметри розетки.

Закрити

Ви також можете відключити лише прийом або передачу на з'єднанні, зателефонувавши до вимкнення.

Функція відключення відключає підключення розетки. Його аргумент як визначає, яку дію виконувати: 0 Зупиніть отримувати дані для цього сокета. Якщо надійдуть подальші дані, відхиліть їх. 1 Перестаньте намагатися передавати дані з цієї розетки. Відкиньте будь-які дані, які чекають надсилання. Перестань шукати підтвердження вже надісланих даних; не повторно передавати її, якщо вона втрачена. 2 Зупиніть і прийом, і передачу.

Повертається значення 0 на успіх і -1 на провал.


2

в моєму тесті.

close відправить fin пакет і знищить fd негайно, коли socket не надається спільним з іншими процесами

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

shutdown SHUT_WR надішле плавковий пакет, щоб вказати, що подальше надсилання заборонено. одноранговий може recv дані, але він буде recv 0, якщо його буфер TCP порожній

shutdown SHUT_RDWR (рівне використання як SHUT_RD, так і SHUT_WR ) відправить перший пакет, якщо однорангові надсилають більше даних.


Я просто спробував це і close()надіслав RST на Linux замість FIN.
Павло Шімерда

Також ви дійсно хотіли заявити, що після відключення програми читання програма отримає більше даних?
Павло Шімерда

@ PavelŠimerda Я думаю, це може залежати від стану tcp для надсилання RST або FIN? я не впевнений.
simpx

1. "Після того, як одноранговий надішле більше даних, recv()поверне дані знову", невірно. 2. Поведінка, якщо рівноправний надсилає більше даних після того, SHUT_RDяк залежить від платформи.
Маркіз Лорн

@EJP Я спробував себе, вибачте, я забув, на якій платформі я роблю цей тест, centos або Mac. і ось що я отримав
simpx
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.