ซ็อกเก็ตปิด vs ปิด?


214

ใน C ฉันเข้าใจว่าถ้าเราปิดซ็อกเก็ตหมายความว่าซ็อกเก็ตจะถูกทำลายและสามารถนำกลับมาใช้ใหม่ได้ในภายหลัง

วิธีการปิดระบบ? คำอธิบายกล่าวว่าปิดครึ่งหนึ่งของการเชื่อมต่อเพล็กซ์กับซ็อกเก็ตนั้น แต่ซ็อกเก็ตนั้นจะถูกทำลายเหมือนการcloseเรียกของระบบหรือไม่


ไม่สามารถนำกลับมาใช้ใหม่ในภายหลัง มันปิด เสร็จ เสร็จสิ้น
มาร์ควิสแห่ง Lorne

คำตอบ:


191

นี่คือคำอธิบายในคู่มือเครือข่ายของ Beej shutdownเป็นวิธีที่ยืดหยุ่นในการบล็อกการสื่อสารในทิศทางเดียวหรือทั้งสอง เมื่อพารามิเตอร์ที่สองคือSHUT_RDWRมันจะบล็อกทั้งการส่งและรับ (เช่นclose) อย่างไรก็ตามcloseเป็นวิธีการทำลายซ็อกเก็ตจริง

ด้วยshutdownคุณจะยังสามารถรับข้อมูลที่อยู่ระหว่างดำเนินการที่เพียร์ส่งไปแล้วได้ด้วย (ขอบคุณ Joey Adams สำหรับการสังเกตสิ่งนี้)


23
โปรดจำไว้ว่าแม้ว่าคุณจะปิด () ซ็อกเก็ต TCP ก็ไม่จำเป็นต้องใช้ซ้ำได้ทันทีเนื่องจากจะอยู่ในสถานะ TIME_WAIT ในขณะที่ระบบปฏิบัติการทำให้แน่ใจว่าไม่มีแพ็กเก็ตที่โดดเด่นที่อาจสับสนเป็นข้อมูลใหม่หากคุณ เพื่อนำซ็อกเก็ตนั้นไปใช้อย่างอื่นทันที
alesplin

91
ความแตกต่างใหญ่ระหว่างการปิดและการปิดซ็อกเก็ตเป็นพฤติกรรมเมื่อซ็อกเก็ตที่ใช้ร่วมกันโดยกระบวนการอื่น ๆ การปิดระบบ () ส่งผลต่อสำเนาทั้งหมดของซ็อกเก็ตในขณะที่ปิด () มีผลเฉพาะไฟล์ descriptor ในกระบวนการเดียว
Zan Lynx

5
เหตุผลหนึ่งที่คุณอาจต้องการที่จะใช้shutdownทั้งสองทิศทาง แต่ไม่closeคือถ้าคุณทำอ้างอิงถึงซ็อกเก็ตที่ใช้FILE fdopenหากคุณcloseซ็อกเก็ตไฟล์ที่เพิ่งเปิดใหม่สามารถกำหนด fd เดียวกันและการใช้ในภายหลังFILEจะอ่าน / เขียนสถานที่ผิดซึ่งอาจไม่ดีมาก หากคุณเพียงแค่shutdownใช้งานในภายหลังFILEจะเพียงแค่ให้ข้อผิดพลาดจนกว่าfcloseจะมีการเรียก
. GitHub หยุดช่วยน้ำแข็ง

25
ความคิดเห็นเกี่ยวกับ TIME_WAIT ไม่ถูกต้อง ที่ใช้กับพอร์ตไม่ใช่ซ็อกเก็ต คุณไม่สามารถใช้ซ็อกเก็ตซ้ำได้
มาร์ควิสแห่ง Lorne

48
-1 ทั้งโพสต์นี้และลิงก์ละเว้นเหตุผลทางความคิดที่สำคัญที่ต้องการใช้shutdown: เพื่อส่งสัญญาณ EOF ไปยังเพียร์และยังสามารถรับข้อมูลที่ค้างอยู่ที่เพียร์ส่ง
Joey Adams

144

ไม่มีคำตอบใดที่บอกวิธีshutdownและcloseทำงานในระดับโปรโตคอล TCP ดังนั้นจึงควรเพิ่มสิ่งนี้

การเชื่อมต่อ TCP มาตรฐานสิ้นสุดลงด้วยการสรุป 4 ทาง:

  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) การหมดเวลาไม่ได้เป็นอันตรายต่อการเชื่อมต่อและไม่จำเป็นต้องระบุว่าคุณสามารถปิดได้
มาร์ควิสแห่ง Lorne

4
@EJP ไม่มันไม่ได้พูดซ้ำซาก คุณสามารถshutdown()เชื่อมต่อและจากนั้นจะไม่มีชีวิตอีกต่อไป คุณยังมีไฟล์ descriptor อยู่ คุณยังคงสามารถrecv()รับบัฟเฟอร์ได้ และคุณยังจำเป็นต้องโทรติดต่อclose()เพื่อทิ้งไฟล์ descriptor
Pavel Šimerda

นี่คือคำตอบที่ดีที่สุดเกี่ยวกับรูปแบบการวางสาย ฉันสนใจรายละเอียดเพิ่มเติมเกี่ยวกับการปิดระบบด้วย SHUT_RD ไม่มีการส่งสัญญาณ TCP เพราะไม่ต้องการข้อมูลเพิ่มเติมใช่ไหม มี FIN สำหรับส่งสัญญาณเท่านั้นที่ไม่ส่งข้อมูลเพิ่มเติมใช่หรือไม่
Pavel Šimerda

3
@ PavelŠimerdaใช่ TCP ไม่ส่งสัญญาณเพราะไม่ต้องการข้อมูลเพิ่มเติม สิ่งนี้ควรได้รับการพิจารณาในโปรโตคอลระดับสูง ในความคิดของฉันนี้โดยทั่วไปไม่จำเป็น คุณสามารถปิดประตูของคุณ แต่คุณไม่สามารถหยุดคนวางของขวัญไว้หน้าประตู นี่คือการตัดสินใจของพวกเขาไม่ใช่ของคุณ
Earth Engine

1
@EJP คุณมีข้อโต้แย้งที่เกิดขึ้นจริงหรือไม่? มิฉะนั้นฉันไม่สนใจ
Pavel Šimerda

35

มีข้อ จำกัด บางประการclose()ที่สามารถหลีกเลี่ยงได้หากใช้shutdown()แทน

close()จะยุติทั้งสองทิศทางในการเชื่อมต่อ TCP บางครั้งคุณต้องการบอกจุดสิ้นสุดอื่น ๆ ว่าคุณเสร็จสิ้นการส่งข้อมูลแล้ว แต่ยังต้องการรับข้อมูล

close()ลดจำนวนการอ้างอิง descriptors (เก็บรักษาไว้ในรายการตารางไฟล์และนับจำนวน descriptors ที่เปิดในขณะนี้ที่อ้างถึงไฟล์ / ซ็อกเก็ต) และไม่ปิดซ็อกเก็ต / ไฟล์หาก descriptor ไม่ใช่ 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
ฟังก์ชั่นทั้งสองนี้มีจุดประสงค์ที่แตกต่างกันโดยสิ้นเชิง ความจริงที่ว่าการปิดครั้งสุดท้ายของซ็อกเก็ตเริ่มต้นลำดับการปิดหากยังไม่ได้เริ่มต้นจะไม่เปลี่ยนความจริงที่ว่าการปิดมีไว้สำหรับการทำความสะอาดโครงสร้างข้อมูลซ็อกเก็ตและการปิดระบบมีไว้สำหรับเริ่มต้น
Len Holgate

25
คุณไม่สามารถ 'ใช้การปิดระบบแทน' คุณสามารถใช้มันได้เช่นกัน แต่คุณต้องปิดซ็อกเก็ตบางครั้ง
มาร์ควิสแห่ง Lorne

15

นี่อาจเป็นแพลตฟอร์มเฉพาะฉันสงสัยอย่างใด แต่ต่อไปคำอธิบายที่ดีที่สุดที่ฉันเคยเห็นอยู่ที่นี่ในหน้า msdn นี้ซึ่งพวกเขาอธิบายเกี่ยวกับการปิดตัวเลือกอิทธิพลการปิดซ็อกเก็ตและลำดับการยกเลิกการเชื่อมต่อทั่วไป

โดยสรุปให้ใช้การปิดระบบเพื่อส่งลำดับการปิดเครื่องที่ระดับ TCP และใช้การปิดเพื่อเพิ่มทรัพยากรที่ใช้โดยโครงสร้างข้อมูลซ็อกเก็ตในกระบวนการของคุณ หากคุณยังไม่ได้รับลำดับการปิดที่ชัดเจนตามเวลาที่คุณโทรเข้าจะมีการเริ่มต้นลำดับหนึ่งให้กับคุณ


9

ฉันยังประสบความสำเร็จภายใต้ linux ที่ใช้shutdown()จาก pthread หนึ่งเพื่อบังคับให้ pthread อื่นที่บล็อกในปัจจุบันconnect()ยกเลิกก่อนกำหนด

ภายใต้ระบบปฏิบัติการอื่น (อย่างน้อย OSX) ฉันพบว่าการโทรclose()นั้นเพียงพอที่จะทำให้ระบบconnect()ล้มเหลว


7

"shutdown () ไม่ได้ปิดตัวอธิบายไฟล์ - เพียงแค่เปลี่ยนการใช้งานของมันในการเพิ่มตัวแสดงรายละเอียดซ็อกเก็ตคุณต้องใช้ close ()" 1


3

ปิด

เมื่อคุณใช้ซ็อกเก็ตเสร็จแล้วคุณสามารถปิดไฟล์ descriptor พร้อมกับปิด หากยังมีข้อมูลรอการส่งผ่านการเชื่อมต่อโดยปกติแล้วจะพยายามปิดการส่งข้อมูลนี้ให้เสร็จ คุณสามารถควบคุมพฤติกรรมนี้ได้โดยใช้ตัวเลือกซ็อกเก็ต SO_LINGER เพื่อระบุระยะหมดเวลา ดูตัวเลือกซ็อกเก็ต

ปิดตัวลง

คุณยังสามารถปิดการรับหรือส่งสัญญาณผ่านการเชื่อมต่อโดยการปิดการโทร

ฟังก์ชั่นปิดเครื่องจะปิดการเชื่อมต่อของซ็อกเก็ต อาร์กิวเมนต์เป็นวิธีระบุการดำเนินการที่จะดำเนินการ: 0 หยุดรับข้อมูลสำหรับซ็อกเก็ตนี้ หากข้อมูลเพิ่มเติมมาถึงให้ปฏิเสธ 1 หยุดพยายามส่งข้อมูลจากซ็อกเก็ตนี้ ยกเลิกข้อมูลใด ๆ ที่รอส่ง หยุดค้นหาการตอบรับข้อมูลที่ส่งไปแล้ว อย่าส่งใหม่หากสูญหาย 2 หยุดการรับและส่งสัญญาณ

ค่าส่งคืนคือ 0 เมื่อสำเร็จและ -1 เมื่อล้มเหลว


2

ในการทดสอบของฉัน

close จะส่งแพ็คเก็ตครีบและทำลาย fd ทันทีเมื่อซ็อกเก็ตไม่ได้ใช้ร่วมกับกระบวนการอื่น ๆ

shutdown SHUT_RDกระบวนการยังคงสามารถรับข้อมูลจากซ็อกเก็ตได้ แต่recvจะส่งคืนค่า 0 หากบัฟเฟอร์ TCP ว่างเปล่าหลังจากเพียร์ส่งข้อมูลเพิ่มเติมrecvจะส่งคืนข้อมูลอีกครั้ง

shutdown SHUT_WRจะส่งแพ็คเก็ตครีบเพื่อระบุว่าการส่งเพิ่มเติมนั้นไม่ได้รับอนุญาต เพียร์สามารถ recv ข้อมูล แต่มันจะ recv 0 ถ้าบัฟเฟอร์ TCP ของมันว่างเปล่า

shutdown SHUT_RDWR (เท่ากับใช้ทั้งSHUT_RDและSHUT_WR ) จะส่งแพ็กเก็ต rst ถ้าเพื่อนส่งข้อมูลเพิ่มเติม


ฉันลองแล้วclose()ส่ง RST บน Linux แทน FIN
Pavel Šimerda

คุณต้องการระบุด้วยเช่นกันว่าหลังจากปิดด้านการอ่านแล้วโปรแกรมจะรับข้อมูลเพิ่มเติมหรือไม่
Pavel Šimerda

@ PavelŠimerdaฉันคิดว่ามันอาจขึ้นอยู่กับสถานะ tcp เพื่อส่ง RST หรือ FIN? ฉันไม่แน่ใจ.
simpx

1. 'หลังจากเพื่อนส่งข้อมูลเพิ่มเติมrecv()จะส่งคืนข้อมูลอีกครั้ง' ไม่ถูกต้อง 2. พฤติกรรมหากเพื่อนส่งข้อมูลมากขึ้นหลังจากSHUT_RDนั้นขึ้นอยู่กับแพลตฟอร์ม
มาร์ควิสแห่ง Lorne

@EJP ฉันลองด้วยตัวเองขอโทษฉันลืมแพลตฟอร์มที่ฉันทำแบบทดสอบนี้ centos หรือ Mac และนี่คือสิ่งที่ฉันได้รับ
simpx
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.