เมื่อใดจึงจำเป็นต้องใช้อ็อพชัน TCP SO_LINGER (0)


96

ฉันคิดว่าฉันเข้าใจความหมายที่เป็นทางการของตัวเลือก ในรหัสเดิมที่ฉันจัดการอยู่ตอนนี้ตัวเลือกนี้ถูกใช้ ลูกค้าบ่นเกี่ยวกับ RST เป็นการตอบสนองต่อ FIN จากด้านข้างเมื่อปิดการเชื่อมต่อจากด้านข้าง

ฉันไม่แน่ใจว่าจะสามารถถอดออกได้อย่างปลอดภัยเนื่องจากฉันไม่เข้าใจว่าควรใช้เมื่อใด

คุณช่วยยกตัวอย่างได้ไหมว่าจะต้องมีตัวเลือกเมื่อใด


1
คุณควรลบออก ไม่ควรใช้ในรหัสการผลิต ครั้งเดียวที่ฉันเคยเห็นมันใช้เป็นผลมาจากเกณฑ์มาตรฐานที่ไม่ถูกต้อง
Marquis of Lorne

คำตอบ:


83

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

เมื่อการเชื่อมต่อ TCP ถูกปิดอย่างสมบูรณ์จุดสิ้นสุดที่เริ่มต้นการปิด ("active close") จะจบลงด้วยการเชื่อมต่ออยู่ในนั้นTIME_WAITเป็นเวลาหลายนาที ดังนั้นหากโปรโตคอลของคุณเป็นส่วนที่เซิร์ฟเวอร์เริ่มต้นการปิดการเชื่อมต่อและเกี่ยวข้องกับการเชื่อมต่อที่มีอายุสั้นเป็นจำนวนมากก็อาจทำให้เกิดปัญหา

นี่ไม่ใช่ความคิดที่ดี - TIME_WAITมีอยู่ด้วยเหตุผล (เพื่อให้แน่ใจว่าแพ็กเก็ตที่หลงทางจากการเชื่อมต่อเก่าจะไม่รบกวนการเชื่อมต่อใหม่) เป็นความคิดที่ดีกว่าในการออกแบบโปรโตคอลของคุณใหม่ให้เป็นโปรโตคอลที่ไคลเอ็นต์เริ่มต้นการปิดการเชื่อมต่อหากเป็นไปได้


4
ฉันเห็นด้วยอย่างยิ่ง ฉันได้เห็นแอปพลิเคชันการตรวจสอบที่เริ่มต้นจำนวนมาก (การเชื่อมต่อสั้น ๆ ไม่กี่พันครั้งทุกๆ X วินาที) และมีแนวโน้มที่จะปรับขนาดให้ใหญ่ขึ้น (การเชื่อมต่อมากกว่าพันครั้ง) ฉันไม่รู้ว่าทำไม แต่แอปพลิเคชันไม่ตอบสนอง มีคนแนะนำ SO_LINGER = true, TIME_WAIT = 0 เพื่อเพิ่มทรัพยากรระบบปฏิบัติการอย่างรวดเร็วและหลังจากการตรวจสอบระยะสั้นเราได้ลองใช้โซลูชันนี้ซึ่งได้ผลลัพธ์ที่ดีมาก TIME_WAIT ไม่ใช่ปัญหาสำหรับแอปนี้อีกต่อไป
bartosz.r

24
ฉันไม่เห็นด้วย. โปรโตคอลระดับแอปพลิเคชันที่อยู่ด้านบนของ TCP ควรได้รับการออกแบบในลักษณะที่ไคลเอ็นต์เริ่มต้นการปิดการเชื่อมต่อเสมอ ด้วยวิธีนี้TIME_WAITจะนั่งอยู่ที่ลูกค้าโดยไม่ทำอันตรายใด ๆ โปรดจำไว้ว่าตามที่ระบุไว้ใน "UNIX Network Programming" รุ่นที่สาม (Stevens et al) หน้า 203: "สถานะ TIME_WAIT เป็นเพื่อนของคุณและพร้อมที่จะช่วยเหลือเราแทนที่จะพยายามหลีกเลี่ยงสถานะเราควรทำความเข้าใจ (ข้อ 2.7) .”
mgd

8
จะเกิดอะไรขึ้นหากลูกค้าต้องการเปิดการเชื่อมต่อ 4000 ครั้งทุก ๆ 30 วินาที (แอปพลิเคชันการตรวจสอบนี้เป็นไคลเอนต์! เพราะเริ่มการเชื่อมต่อ) ใช่เราสามารถปรับปรุงแอปพลิเคชันใหม่เพิ่มตัวแทนท้องถิ่นบางส่วนในโครงสร้างพื้นฐานเปลี่ยนรูปแบบเพื่อผลักดัน แต่ถ้าเรามีแอปพลิเคชันดังกล่าวอยู่แล้วและมันเติบโตขึ้นเราก็สามารถทำให้มันทำงานได้โดยการปรับแต่ง คุณเปลี่ยนพารามิเตอร์หนึ่งตัวและคุณก็มีแอปพลิเคชันที่ใช้งานได้โดยไม่ต้องลงทุนงบประมาณเพื่อใช้สถาปัตยกรรมใหม่
bartosz.r

4
@ bartosz.r: ฉันแค่บอกว่าการใช้ SO_LINGER กับการหมดเวลา 0 ควรเป็นทางเลือกสุดท้ายจริงๆ อีกครั้งใน "UNIX Network Programming" รุ่นที่สาม (Stevens et al) หน้า 203 ยังบอกด้วยว่าคุณเสี่ยงต่อการเสียหายของข้อมูล ลองอ่าน RFC 1337 เพื่อดูว่าทำไม TIME_WAIT จึงเป็นเพื่อนของคุณ
mgd

7
@caf ไม่โซลูชันแบบคลาสสิกจะเป็นพูลการเชื่อมต่อดังที่เห็นใน TCP API สำหรับงานหนักทุกตัวเช่น HTTP 1.1
Marquis of Lorne

191

สำหรับข้อเสนอแนะของฉันโปรดอ่านส่วนล่าสุด: “เมื่อใช้ SO_LINGER กับหมดเวลา 0”

ก่อนที่เราจะมาบรรยายเล็กน้อยเกี่ยวกับ:

  • การสิ้นสุด TCP ปกติ
  • TIME_WAIT
  • FIN, ACKและRST

การสิ้นสุด TCP ปกติ

ลำดับการสิ้นสุด TCP ปกติมีลักษณะดังนี้ (แบบง่าย):

เรามีเพื่อนสองคน: A และ B

  1. โทร close()
    • A ส่งFINไปยัง B
    • A เข้าสู่FIN_WAIT_1สถานะ
  2. B ได้รับ FIN
    • B ส่งACKให้ A
    • B เข้าสู่CLOSE_WAITสถานะ
  3. ได้รับ ACK
    • A เข้าสู่FIN_WAIT_2สถานะ
  4. B โทร close()
    • B ส่งFINให้ A
    • B เข้าสู่LAST_ACKสถานะ
  5. ได้รับ FIN
    • A ส่งACKไปยัง B
    • A เข้าสู่TIME_WAITสถานะ
  6. B ได้รับ ACK
    • B เข้าสู่CLOSEDสถานะ - คือถูกลบออกจากตารางซ็อกเก็ต

TIME_WAIT

ดังนั้นเพียร์ที่เริ่มต้นการยุติ - เช่นการโทรclose()ก่อน - จะจบลงในTIME_WAITสถานะ

เพื่อทำความเข้าใจว่าเหตุใดTIME_WAITรัฐจึงเป็นเพื่อนของเราโปรดอ่านหัวข้อ 2.7 ใน "UNIX Network Programming" ฉบับที่สามโดย Stevens et al (หน้า 43)

อย่างไรก็ตามอาจมีปัญหากับซ็อกเก็ตจำนวนมากในTIME_WAITสถานะบนเซิร์ฟเวอร์เนื่องจากอาจทำให้การเชื่อมต่อใหม่ไม่ได้รับการยอมรับ

เพื่อหลีกเลี่ยงปัญหานี้ผมได้เห็นหลายแนะนำการตั้งค่าตัวเลือกซ็อกเก็ต SO_LINGER กับหมดเวลา 0 close()ก่อนที่จะเรียก อย่างไรก็ตามนี่เป็นวิธีแก้ปัญหาที่ไม่ดีเนื่องจากทำให้การเชื่อมต่อ TCP ถูกยกเลิกด้วยข้อผิดพลาด

ให้ออกแบบโปรโตคอลของแอปพลิเคชันแทนเพื่อให้การยุติการเชื่อมต่อเริ่มต้นจากฝั่งไคลเอ็นต์ หากไคลเอนต์รู้เสมอว่าเมื่อใดที่อ่านข้อมูลที่เหลือทั้งหมดก็สามารถเริ่มต้นลำดับการสิ้นสุดได้ ตัวอย่างเช่นเบราว์เซอร์รู้จากContent-Lengthส่วนหัว HTTP เมื่ออ่านข้อมูลทั้งหมดแล้วและสามารถเริ่มการปิดได้ (ฉันรู้ว่าใน HTTP 1.1 จะเปิดไว้สักพักเพื่อนำกลับมาใช้ใหม่ได้แล้วจึงปิด)

close()ถ้าเซิร์ฟเวอร์จำเป็นต้องปิดการเชื่อมต่อการออกแบบโปรโตคอลประยุกต์ใช้เพื่อให้เซิร์ฟเวอร์ถามลูกค้าที่จะเรียกร้อง

เมื่อใดควรใช้ SO_LINGER กับการหมดเวลา 0

อีกครั้งตาม "UNIX Network Programming" รุ่นที่สามหน้า 202-203 การตั้งค่าSO_LINGERด้วยการหมดเวลา 0 ก่อนการโทรclose()จะทำให้ลำดับการสิ้นสุดปกติไม่สามารถเริ่มต้นได้

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

ดังนั้นในสถานการณ์ปกติจึงเป็นความคิดที่แย่มากที่จะตั้งค่าการSO_LINGERหมดเวลาเป็น 0 ก่อนที่จะโทรclose()- จากนี้ไปเรียกว่ายกเลิกการปิด - ในแอปพลิเคชันเซิร์ฟเวอร์

อย่างไรก็ตามสถานการณ์บางอย่างรับประกันว่าจะทำเช่นนั้น:

  • หากไคลเอนต์ของแอปพลิเคชันเซิร์ฟเวอร์ของคุณทำงานผิดพลาด (หมดเวลาส่งคืนข้อมูลที่ไม่ถูกต้อง ฯลฯ ) การปิดที่ไม่ถูกต้องเหมาะสมที่จะหลีกเลี่ยงการติดขัดCLOSE_WAITหรือสิ้นสุดในTIME_WAITสถานะ
  • หากคุณต้องรีสตาร์ทแอ็พพลิเคชันเซิร์ฟเวอร์ของคุณซึ่งปัจจุบันมีการเชื่อมต่อไคลเอ็นต์หลายพันรายการคุณอาจพิจารณาตั้งค่าอ็อพชันซ็อกเก็ตนี้เพื่อหลีกเลี่ยงซ็อกเก็ตเซิร์ฟเวอร์นับพันในTIME_WAIT(เมื่อเรียกclose()จากเซิร์ฟเวอร์ปลายทาง) เนื่องจากอาจทำให้เซิร์ฟเวอร์ไม่สามารถรับพอร์ตที่พร้อมใช้งานสำหรับการเชื่อมต่อไคลเอ็นต์ใหม่ หลังจากเริ่มต้นใหม่
  • ในหน้า 202 ในหนังสือดังกล่าวระบุไว้เป็นพิเศษว่า: "มีสถานการณ์บางอย่างที่รับประกันว่าจะใช้คุณลักษณะนี้เพื่อส่งการปิดที่ยกเลิกตัวอย่างหนึ่งคือเซิร์ฟเวอร์เทอร์มินัล RS-232 ซึ่งอาจหยุดทำงานตลอดไปในการCLOSE_WAITพยายามส่งข้อมูลไปยังเทอร์มินัลที่ติดขัด พอร์ต แต่จะรีเซ็ตพอร์ตที่ติดอย่างถูกต้องหากมีการRSTทิ้งข้อมูลที่รอดำเนินการ "

ฉันจะแนะนำนี้บทความยาวซึ่งผมเชื่อว่าจะช่วยให้คำตอบที่ดีมากที่จะตอบคำถามของคุณ


6
TIME_WAITเป็นเพื่อนก็ต่อเมื่อไม่ได้เริ่มก่อปัญหา: stackoverflow.com/questions/1803566/…
Pacerier

2
แล้วถ้าคุณเขียนเว็บเซิร์ฟเวอร์ล่ะ? คุณจะ "บอกให้ลูกค้าเริ่มปิด" ได้อย่างไร?
Shaun Neal

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

นี่เป็นคำตอบที่ยอดเยี่ยม ขอบคุณ!
Juraj Martinka

17

เมื่อเปิดอยู่ แต่หมดเวลาเป็นศูนย์สแต็ก TCP จะไม่รอให้ส่งข้อมูลที่รอดำเนินการก่อนที่จะปิดการเชื่อมต่อ ข้อมูลอาจสูญหายเนื่องจากสิ่งนี้ แต่การตั้งค่าด้วยวิธีนี้คุณจะยอมรับสิ่งนี้และขอให้รีเซ็ตการเชื่อมต่อทันทีแทนที่จะปิดอย่างสง่างาม สิ่งนี้ทำให้ RST ถูกส่งแทนที่จะเป็น FIN ปกติ

ขอบคุณ EJP สำหรับความคิดเห็นของเขาโปรดดูรายละเอียดที่นี่


1
ฉันเข้าใจเรื่องนี้ สิ่งที่ฉันขอคือตัวอย่าง "จริง" เมื่อเราต้องการใช้ฮาร์ดรีเซ็ต
dimba

5
เมื่อใดก็ตามที่คุณต้องการยกเลิกการเชื่อมต่อ ดังนั้นหากโปรโตคอลของคุณไม่ผ่านการตรวจสอบความถูกต้องและคุณมีลูกค้าพูดเรื่องขยะกับคุณในทันใดคุณจะยกเลิกการเชื่อมต่อกับ RST เป็นต้น
Len Holgate

5
คุณกำลังสับสนกับการหมดเวลาเป็นศูนย์โดยไม่ต้องรอ Linger off หมายความว่า close () ไม่ปิดกั้น การคงอยู่กับการหมดเวลาที่เป็นบวกหมายความว่าการปิด () จะปิดกั้นจนถึงระยะหมดเวลา การคงอยู่กับการหมดเวลาเป็นศูนย์ทำให้เกิด RST และนี่คือสิ่งที่คำถามเกี่ยวกับ
มาร์ควิสแห่งลอร์น

2
ใช่คุณถูกต้อง ฉันจะปรับคำตอบเพื่อแก้ไขคำศัพท์ของฉัน
เลนโฮลเกต

6

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

หากแอปพลิเคชันของคุณมีรสชาติของ“ ไคลเอนต์” (ปิดก่อน) และคุณเริ่มต้นและปิดการเชื่อมต่อจำนวนมากไปยังเซิร์ฟเวอร์ต่างๆ (เช่นเมื่อแอปของคุณเป็นแอปตรวจสอบที่ดูแลการเข้าถึงเซิร์ฟเวอร์จำนวนมาก) แอปของคุณ มีปัญหาว่าการเชื่อมต่อไคลเอนต์ทั้งหมดของคุณติดขัดในสถานะ TIME_WAIT จากนั้นฉันขอแนะนำให้ลดระยะหมดเวลาให้เป็นค่าที่น้อยกว่าค่าเริ่มต้นเพื่อให้ยังคงปิดระบบได้อย่างสง่างาม แต่ทำให้ทรัพยากรการเชื่อมต่อไคลเอ็นต์ว่างก่อนหน้านี้ ฉันจะไม่ตั้งค่าการหมดเวลาเป็น 0 เนื่องจาก 0 ไม่ได้ปิดอย่างสง่างามด้วย FIN แต่ยกเลิกด้วย RST

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

หากแอปพลิเคชันของคุณเป็น "เซิร์ฟเวอร์" (วินาทีที่ใกล้เคียงกับการตอบสนองต่อการปิดของเพียร์) เมื่อปิด () การเชื่อมต่อของคุณจะปิดลงอย่างสง่างามและทรัพยากรจะถูกปลดปล่อยเมื่อคุณไม่ได้เข้าสู่สถานะ TIME_WAIT ไม่ควรใช้ Linger แต่หากแอปเซิร์ฟเวอร์ของคุณมีกระบวนการกำกับดูแลที่ตรวจพบการเชื่อมต่อแบบเปิดที่ไม่ได้ใช้งานเป็นเวลานาน (ต้องกำหนด "ยาว") คุณสามารถปิดการเชื่อมต่อที่ไม่ได้ใช้งานนี้ได้จากด้านข้างของคุณซึ่งจะเห็นว่าเป็นการจัดการข้อผิดพลาดด้วยการปิดที่ไม่ได้ใช้งาน ทำได้โดยตั้งค่าการหมดเวลาเป็น 0 แล้วปิด () จะส่ง RST ไปยังลูกค้าบอกเขาว่าคุณกำลังโกรธ :-)


1

ในเซิร์ฟเวอร์คุณอาจต้องการส่งRSTแทนFINเมื่อตัดการเชื่อมต่อไคลเอ็นต์ที่ทำงานไม่ดี ซึ่งจะข้ามFIN-WAITตามด้วยTIME-WAITสถานะซ็อกเก็ตในเซิร์ฟเวอร์ซึ่งป้องกันไม่ให้ทรัพยากรเซิร์ฟเวอร์หมดลงและด้วยเหตุนี้จึงป้องกันจากการโจมตีแบบปฏิเสธการให้บริการประเภทนี้


1

ฉันชอบการสังเกตของ Maxim ที่ว่าการโจมตี DOS อาจทำให้ทรัพยากรเซิร์ฟเวอร์หมดไป นอกจากนี้ยังเกิดขึ้นโดยไม่มีฝ่ายตรงข้ามที่เป็นอันตรายจริงๆ

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

อีกสถานการณ์หนึ่งคือเมื่อ 'ไคลเอ็นต์ทั้งหมดมีที่อยู่ TCP เดียวกัน' จากนั้นการเชื่อมต่อไคลเอ็นต์จะสามารถแยกแยะได้ด้วยหมายเลขพอร์ตเท่านั้น (หากเชื่อมต่อกับเซิร์ฟเวอร์เดียว) และหากไคลเอ็นต์เริ่มการเปิด / ปิดการเชื่อมต่ออย่างรวดเร็วไม่ว่าด้วยเหตุผลใดก็ตามพวกเขาสามารถใช้งาน tuple-space (ไคลเอนต์ addr + พอร์ต, พอร์ต IP + ของเซิร์ฟเวอร์) ได้

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


0

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

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