SO_REUSEADDR และ SO_REUSEPORT แตกต่างกันอย่างไร


663

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

แล้วSO_REUSEADDRแตกต่างกันSO_REUSEPORTอย่างไร

ระบบที่ไม่มีSO_REUSEPORTข้อ จำกัด มากขึ้น?

และสิ่งที่เป็นพฤติกรรมที่คาดหวังถ้าฉันใช้อย่างใดอย่างหนึ่งในระบบปฏิบัติการที่แตกต่างกัน?

คำตอบ:


1615

ยินดีต้อนรับสู่โลกอันแสนวิเศษของการพกพา ... หรือมากกว่านั้น ก่อนที่เราจะเริ่มวิเคราะห์รายละเอียดสองตัวเลือกเหล่านี้และมองลึกลงไปว่าระบบปฏิบัติการที่แตกต่างกันจัดการพวกเขาควรสังเกตว่าการใช้ซ็อกเก็ต BSD เป็นแม่ของการใช้ซ็อกเก็ตทั้งหมด โดยทั่วไประบบอื่น ๆ ทั้งหมดจะคัดลอกการใช้ซ็อกเก็ต BSD ในบางช่วงเวลา (หรืออย่างน้อยก็อินเตอร์เฟส) จากนั้นก็เริ่มพัฒนามันเอง แน่นอนว่าการติดตั้งซ็อกเก็ต BSD นั้นได้รับการพัฒนาเช่นกันในเวลาเดียวกันดังนั้นระบบที่คัดลอกมันในภายหลังนั้นมีคุณสมบัติที่ขาดในระบบที่คัดลอกมาก่อนหน้านี้ การทำความเข้าใจการใช้ซ็อกเก็ต BSD เป็นกุญแจสำคัญในการทำความเข้าใจการใช้ซ็อกเก็ตอื่น ๆ ทั้งหมดดังนั้นคุณควรอ่านเกี่ยวกับเรื่องนี้แม้ว่าคุณจะไม่สนใจเขียนโค้ดสำหรับระบบ BSD ก็ตาม

มีพื้นฐานสองสามข้อที่คุณควรรู้ก่อนที่เราจะดูตัวเลือกทั้งสองนี้ การเชื่อมต่อ TCP / UDP มีการระบุโดย tuple ของห้าค่า:

{<protocol>, <src addr>, <src port>, <dest addr>, <dest port>}

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

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

หากคุณผูกซ็อกเก็ตอย่างชัดเจนเป็นไปได้ที่จะผูกเข้ากับพอร์ต0ซึ่งหมายถึง "พอร์ตใด ๆ " เนื่องจากซ็อกเก็ตไม่สามารถผูกกับพอร์ตที่มีอยู่ทั้งหมดได้จริงระบบจะต้องเลือกพอร์ตเฉพาะในกรณีนั้น (โดยทั่วไปจะเป็นช่วงที่มาจากช่วงที่กำหนดไว้ล่วงหน้าของระบบปฏิบัติการของพอร์ตต้นทาง) มีไวด์การ์ดที่คล้ายกันสำหรับที่อยู่ต้นทางซึ่งสามารถเป็น "ที่อยู่ใดก็ได้" ( 0.0.0.0ในกรณีของ IPv4 และ::ในกรณีของ IPv6) ซึ่งแตกต่างจากในกรณีของพอร์ตซ็อกเก็ตสามารถถูกผูกไว้กับ "ที่อยู่ใด ๆ " ซึ่งหมายถึง "ที่อยู่ IP ของแหล่งที่มาทั้งหมดของการเชื่อมต่อท้องถิ่นทั้งหมด" หากเชื่อมต่อซ็อกเก็ตในภายหลังระบบจะต้องเลือกที่อยู่ IP ของแหล่งที่มาที่เฉพาะเจาะจงเนื่องจากซ็อกเก็ตไม่สามารถเชื่อมต่อและในเวลาเดียวกันถูกผูกไว้กับที่อยู่ IP ท้องถิ่นใด ๆ ขึ้นอยู่กับที่อยู่ปลายทางและเนื้อหาของตารางเส้นทางระบบจะเลือกที่อยู่ต้นทางที่เหมาะสมและแทนที่การผูก "ใด ๆ " ด้วยการผูกกับที่อยู่ IP ต้นทางที่เลือก

ตามค่าเริ่มต้นแล้วไม่มีซ็อกเก็ตสองตัวที่สามารถรวมกันเป็นที่อยู่เดียวกันและแหล่งที่มาของพอร์ตได้ ตราบใดที่พอร์ตต้นทางแตกต่างกันที่อยู่ของแหล่งข้อมูลนั้นไม่เกี่ยวข้องจริง ๆ มีผลผูกพันsocketAไปA:XและsocketBเพื่อB:Yที่AและBที่อยู่และXและYมีพอร์ตเป็นไปได้เสมอตราบใดที่X != Yถือเป็นจริง อย่างไรก็ตามแม้ว่าX == Yการเชื่อมโยงยังคงเป็นไปได้ตราบใดที่ยังคงเป็นA != Bจริง เช่นsocketAเป็นของโปรแกรมเซิร์ฟเวอร์ FTP และถูกผูกไว้192.168.0.1:21และsocketBเป็นของโปรแกรมเซิร์ฟเวอร์ FTP อื่นและถูกผูกไว้กับ10.0.0.1:21การผูกทั้งสองจะประสบความสำเร็จ โปรดจำไว้ว่าซ็อกเก็ตอาจถูกผูกไว้กับ "ที่อยู่ใดก็ได้" ในเครื่อง หากซ็อกเก็ตถูกผูกไว้กับ0.0.0.0:21มันถูกผูกไว้กับโลคัลแอดเดรสที่มีอยู่ทั้งหมดในเวลาเดียวกันและในกรณีนั้นจะไม่สามารถเชื่อมต่อซ็อกเก็ตอื่นกับพอร์ต21ไม่ว่าจะใช้ที่อยู่ IP ใดเฉพาะที่มันพยายามผูกไว้เนื่องจาก0.0.0.0ขัดแย้งกับที่อยู่ IP ท้องถิ่นที่มีอยู่ทั้งหมด

อะไรก็ตามที่บอกว่าเท่ากันสำหรับระบบปฏิบัติการหลักทั้งหมด สิ่งต่าง ๆ เริ่มที่จะได้รับเฉพาะระบบปฏิบัติการเมื่อนำมาใช้ซ้ำที่อยู่เข้ามาเล่น เราเริ่มต้นด้วย BSD เนื่องจากที่ฉันได้กล่าวไว้ข้างต้นเป็นแม่ของการใช้ซ็อกเก็ตทั้งหมด

BSD

SO_REUSEADDR

หากSO_REUSEADDRเปิดใช้งานบนซ็อกเก็ตก่อนที่จะมีผลผูกพันมันซ็อกเก็ตสามารถจะผูกพันประสบความสำเร็จจนกว่าจะมีความขัดแย้งกับซ็อกเก็ตอีกผูกไว้กับว่าการรวมกันเดียวกันของแหล่งที่อยู่และพอร์ต ตอนนี้คุณอาจสงสัยว่ามันแตกต่างจากครั้งก่อน ๆ อย่างไร? คำหลักคือ "ตรง" SO_REUSEADDRส่วนใหญ่จะเปลี่ยนวิธีการจัดการที่อยู่ของสัญลักษณ์แทน ("ที่อยู่ IP ใด ๆ ") เมื่อค้นหาข้อขัดแย้ง

โดยไม่ต้องSO_REUSEADDRมีผลผูกพันsocketAไป0.0.0.0:21แล้วมีผลผูกพันsocketBไป192.168.0.1:21จะล้มเหลว (ที่มีข้อผิดพลาดEADDRINUSE) ตั้งแต่ 0.0.0.0 หมายถึง "ใด ๆ ที่อยู่ในท้องถิ่น" จึงอยู่ IP ทั้งหมดในท้องถิ่นได้รับการพิจารณาในการใช้งานโดยซ็อกเก็ตนี้และรวมถึง192.168.0.1อีกด้วย ด้วยการSO_REUSEADDRจะประสบความสำเร็จตั้งแต่0.0.0.0และ192.168.0.1จะไม่ตรงกับที่อยู่เดียวกันหนึ่งคือสัญลักษณ์แทนสำหรับที่อยู่ภายในทั้งหมดและคนอื่น ๆ ที่เป็นอยู่ในท้องถิ่นที่เฉพาะเจาะจงมาก โปรดทราบว่าข้อความข้างต้นเป็นจริงโดยไม่คำนึงถึงลำดับsocketAและการsocketBเชื่อมโยง หากปราศจากSO_REUSEADDRมันจะล้มเหลวเสมอโดยSO_REUSEADDRจะประสบความสำเร็จเสมอ

หากต้องการให้ภาพรวมที่ดีขึ้นให้สร้างตารางที่นี่และแสดงรายการชุดค่าผสมที่เป็นไปได้ทั้งหมด:

ซ็อกเก็ต SO_REUSEADDR ซ็อกเก็ต A ผลลัพธ์ B
-------------------------------------------------- -------------------
  เปิด / ปิด 192.168.0.1:21 ข้อผิดพลาด 192.168.0.1:21 (EADDRINUSE)
  เปิด / ปิด 192.168.0.1:21 10.0.0.1:21 ตกลง
  เปิด / ปิด 10.0.0.1:21 192.168.0.1:21 ตกลง
   ปิด 0.0.0.0:21 ข้อผิดพลาด 192.168.1.0:21 (EADDRINUSE)
   ปิด 192.168.1.0:21 0.0.0.0:21 ข้อผิดพลาด (EADDRINUSE)
   ON 0.0.0.0:21 192.168.1.0:21 ตกลง
   ON 192.168.1.0:21 0.0.0.0:21 ตกลง
  เปิด / ปิด 0.0.0.0:21 0.0.0.0:21 ข้อผิดพลาด (EADDRINUSE)

ตารางด้านบนถือว่าสมมติว่าsocketAมีการเชื่อมโยงกับที่อยู่ที่กำหนดสำเร็จsocketAจากนั้นsocketBจะถูกสร้างขึ้นไม่ว่าจะได้รับการSO_REUSEADDRตั้งค่าหรือไม่และในที่สุดก็ถูกผูกไว้กับที่อยู่ที่ให้socketBไว้ เป็นผลมาจากการดำเนินการผูกสำหรับResult socketBหากคอลัมน์แรกบอกว่าON/OFFค่าของSO_REUSEADDRไม่เกี่ยวข้องกับผลลัพธ์

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

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

จำนวนเวลาเคอร์เนลจะรอก่อนที่จะปิดซ็อกเก็ตโดยไม่คำนึงถึงว่ามันยังคงมีข้อมูลในการบินหรือไม่เรียกว่าเวลา Linger Linger เวลาเป็นทั่วโลกที่กำหนดในระบบส่วนใหญ่และโดยค่าเริ่มต้นค่อนข้างยาว (สองนาทีเป็นค่าทั่วไปคุณจะพบในระบบจำนวนมาก) นอกจากนี้ยังสามารถกำหนดค่าต่อซ็อกเก็ตโดยใช้ตัวเลือกซ็อกเก็ตSO_LINGERซึ่งสามารถใช้ในการทำให้การหมดเวลาสั้นลงหรือนานขึ้นและแม้กระทั่งการปิดการใช้งานอย่างสมบูรณ์ การปิดใช้งานนั้นเป็นความคิดที่ไม่ดีนักเนื่องจากการปิดซ็อกเก็ต TCP เป็นกระบวนการที่ซับซ้อนเล็กน้อยและเกี่ยวข้องกับการส่งแพ็กเก็ตกลับมาและกลับมาสองครั้ง (เช่นเดียวกับการส่งแพ็กเก็ตซ้ำในกรณีที่สูญหาย) จะถูก จำกัด ด้วยเวลา Linger. หากคุณปิดการใช้งานซ็อกเก็ตของคุณอาจไม่เพียง แต่สูญเสียข้อมูลในการบินเท่านั้น แต่ยังปิดอย่างแน่นหนาแทนการใช้อย่างสง่างามซึ่งมักไม่แนะนำให้ใช้ รายละเอียดเกี่ยวกับวิธีปิดการเชื่อมต่อ TCP อย่างงดงามเกินขอบเขตของคำตอบนี้หากคุณต้องการเรียนรู้เพิ่มเติมฉันขอแนะนำให้คุณดูที่หน้านี้ และแม้ว่าคุณจะปิดการใช้งาน lingering ด้วยSO_LINGERหากกระบวนการของคุณตายโดยไม่ปิดซ็อกเก็ตอย่างชัดแจ้ง BSD (และระบบอื่น ๆ ที่เป็นไปได้) จะยังคงอยู่โดยไม่สนใจสิ่งที่คุณได้กำหนดค่าไว้ สิ่งนี้จะเกิดขึ้นเช่นถ้ารหัสของคุณเพิ่งโทรexit()(โดยทั่วไปมักใช้กับโปรแกรมเซิร์ฟเวอร์ที่เรียบง่ายเล็ก ๆ ) หรือกระบวนการถูกทำลายโดยสัญญาณ (ซึ่งรวมถึงความเป็นไปได้ที่มันจะล่มเนื่องจากการเข้าถึงหน่วยความจำที่ผิดกฎหมาย) ดังนั้นจึงไม่มีอะไรที่คุณสามารถทำได้เพื่อให้แน่ใจว่าซ็อกเก็ตจะไม่อวดอ้างในทุกสถานการณ์

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

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

SO_REUSEPORT

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

SO_REUSEPORTSO_REUSEADDRไม่ได้หมายความถึง ซึ่งหมายความว่าหากซ็อกเก็ตไม่ได้SO_REUSEPORTตั้งค่าเมื่อมันถูกผูกไว้และซ็อกเก็ตอื่นได้SO_REUSEPORTตั้งค่าเมื่อมันถูกผูกไว้กับที่อยู่เดียวกันและพอร์ตเดียวกันผูกล้มเหลวซึ่งคาดว่า แต่มันก็ล้มเหลวถ้าซ็อกเก็ตอื่นกำลังจะตาย อยู่ในTIME_WAITสถานะ เพื่อให้สามารถผูกซ็อกเก็ตไปยังที่อยู่และพอร์ตเดียวกันได้เนื่องจากซ็อกเก็ตอื่นที่อยู่ในTIME_WAITสถานะต้องSO_REUSEADDRตั้งค่าบนซ็อกเก็ตนั้นหรือSO_REUSEPORTต้องตั้งค่าบนซ็อกเก็ตทั้งสองก่อนที่จะผูกเข้า แน่นอนว่ามันได้รับอนุญาตให้ตั้งค่าทั้งสองSO_REUSEPORTและSO_REUSEADDRบนซ็อกเก็ต

ไม่มีอะไรจะพูดเกี่ยวกับสิ่งSO_REUSEPORTอื่นนอกเหนือจากที่เพิ่มเข้ามาในภายหลังSO_REUSEADDRนั่นเป็นเหตุผลที่คุณจะไม่พบมันในการใช้งานซ็อกเก็ตของระบบอื่น ๆ ซึ่ง "แยก" รหัส BSD ก่อนที่จะเพิ่มตัวเลือกนี้และไม่มี วิธีการผูกสองซ็อกเก็ตไปยังที่อยู่ซ็อกเก็ตเดียวกันใน BSD ก่อนตัวเลือกนี้

เชื่อมต่อ () EADDRINUSE ที่ส่งคืนหรือไม่

คนส่วนใหญ่รู้ว่าbind()อาจล้มเหลวพร้อมกับข้อผิดพลาดEADDRINUSEอย่างไรก็ตามเมื่อคุณเริ่มเล่นกับการใช้ที่อยู่ซ้ำคุณอาจพบสถานการณ์แปลก ๆ ที่connect()ล้มเหลวด้วยข้อผิดพลาดนั้นเช่นกัน สิ่งนี้จะเป็นอย่างไร ที่อยู่ระยะไกลจะทำอย่างไรหลังจากทั้งหมดนั่นคือสิ่งที่การเชื่อมต่อเพิ่มไปยังซ็อกเก็ตใช้งานอยู่แล้ว? การเชื่อมต่อซ็อกเก็ตหลายตัวเข้ากับที่อยู่ระยะไกลเดียวกันไม่เคยมีปัญหามาก่อนดังนั้นสิ่งที่ผิดพลาดที่นี่

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

ดังนั้นถ้าคุณผูกสองซ็อกเก็ตของโปรโตคอลเดียวกันกับที่อยู่แหล่งที่มาและพอร์ตเดียวกันและพยายามเชื่อมต่อพวกเขาทั้งสองไปยังที่อยู่ปลายทางและพอร์ตเดียวกันconnect()จริง ๆ แล้วจะล้มเหลวด้วยข้อผิดพลาดEADDRINUSEสำหรับซ็อกเก็ตที่สองที่คุณพยายามเชื่อมต่อ ซ็อกเก็ตที่มี tuple เหมือนกันจากห้าค่าเชื่อมต่อแล้ว

ที่อยู่แบบหลายผู้รับ

คนส่วนใหญ่ไม่สนใจความจริงที่ว่าที่อยู่แบบหลายผู้รับมีอยู่ แต่มีอยู่จริง แม้ว่าที่อยู่ unicast จะใช้สำหรับการสื่อสารแบบหนึ่งต่อหนึ่ง แต่ที่อยู่แบบหลายผู้รับใช้สำหรับการสื่อสารแบบหนึ่งต่อหลายคน คนส่วนใหญ่ทราบที่อยู่แบบหลายผู้รับเมื่อเรียนรู้เกี่ยวกับ IPv6 แต่ที่อยู่แบบหลายผู้รับมีอยู่ใน IPv4 แม้ว่าคุณลักษณะนี้จะไม่ได้ใช้กันอย่างแพร่หลายในอินเทอร์เน็ตสาธารณะ

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


FreeBSD / OpenBSD / NetBSD

ทั้งหมดนี้ค่อนข้างจะล่าช้าสำหรับรหัส BSD ดั้งเดิมนั่นคือเหตุผลที่พวกเขาทั้งสามเสนอตัวเลือกเดียวกับ BSD และพวกเขาก็ทำงานแบบเดียวกับใน BSD


macOS (MacOS X)

ที่แกนกลาง macOS เป็นเพียงรูปแบบ BSD UNIX ที่ชื่อ " ดาร์วิน " ซึ่งอิงกับทางแยกของรหัส BSD (BSD 4.3) ที่ค่อนข้างช้าซึ่งต่อมาจะมีการซิงโครไนซ์กับ FreeBSD (ในเวลานั้น) รหัส 5 ฐานสำหรับรุ่น Mac OS 10.3 เพื่อให้ Apple สามารถได้รับการปฏิบัติตาม POSIX เต็มรูปแบบ (macOS ได้รับการรับรอง POSIX) แม้จะมี microkernel ที่แกนกลาง (" Mach ") ส่วนที่เหลือของเคอร์เนล (" XNU ") นั้นเป็นเพียงเคอร์เนล BSD และนั่นเป็นเหตุผลที่ว่าทำไม macOS จึงเสนอตัวเลือกเดียวกับ BSD และพวกมันก็ทำงานแบบเดียวกับ BSD .

iOS / watchOS / tvOS

iOS เป็นเพียง MacOS fork ที่มีเคอร์เนลที่ถูกปรับแต่งเล็กน้อยและถูกตัดแต่งบางส่วนทำให้ชุดเครื่องมือของผู้ใช้ว่างเปล่าและชุดเฟรมเวิร์กเริ่มต้นแตกต่างกันเล็กน้อย watchOS และ tvOS เป็นอุปกรณ์ส้อมของ iOS ที่ถูกถอดออกมากยิ่งขึ้น (โดยเฉพาะ watchOS) เพื่อความรู้ที่ดีที่สุดของฉันพวกเขาทุกคนทำงานได้อย่างที่ macOS


ลินุกซ์

Linux <3.9

ก่อนหน้า Linux 3.9 มีเพียงตัวเลือกSO_REUSEADDRเท่านั้น ตัวเลือกนี้ทำงานโดยทั่วไปเหมือนกับใน BSD โดยมีข้อยกเว้นที่สำคัญสองข้อ:

  1. ตราบใดที่ซ็อกเก็ต TCP กำลังรอรับฟัง (เซิร์ฟเวอร์) ถูกผูกไว้กับพอร์ตเฉพาะตัวSO_REUSEADDRเลือกนั้นจะถูกละเว้นสำหรับซ็อกเก็ตทั้งหมดที่กำหนดเป้าหมายพอร์ตนั้น การเชื่อมต่อซ็อกเก็ตที่สองเข้ากับพอร์ตเดียวกันจะทำได้ก็ต่อเมื่อมันเป็นไปได้ใน BSD โดยไม่ต้องSO_REUSEADDRตั้งค่า เช่นคุณไม่สามารถเชื่อมโยงกับที่อยู่ตัวแทนแล้วหนึ่งที่เฉพาะเจาะจงมากขึ้นหรือรอบทางอื่น ๆ ทั้งที่เป็นไปได้ใน BSD SO_REUSEADDRถ้าคุณตั้งค่า สิ่งที่คุณสามารถทำได้คือคุณสามารถผูกเข้ากับพอร์ตเดียวกันและที่อยู่ที่ไม่ใช่ไวด์การ์ดสองที่แตกต่างกันตามที่ได้รับอนุญาตเสมอ ในแง่นี้ลินุกซ์มีข้อ จำกัด มากกว่า BSD

  2. ข้อยกเว้นที่สองคือสำหรับซ็อกเก็ตไคลเอ็นต์ตัวเลือกนี้ทำงานเหมือนกับSO_REUSEPORTใน BSD ตราบใดที่ทั้งคู่ตั้งค่าสถานะนี้ไว้ก่อนที่จะถูกผูกไว้ เหตุผลในการอนุญาตให้ใช้งานนั้นเป็นเรื่องสำคัญที่จะต้องผูกหลายซ็อกเก็ตให้ตรงกับที่อยู่ซ็อกเก็ต UDP เดียวกันสำหรับโปรโตคอลต่างๆและอย่างที่เคยเป็นมาSO_REUSEPORTก่อนหน้า 3.9 พฤติกรรมของSO_REUSEADDRการเปลี่ยนแปลงจึงถูกเติมลงในช่องว่างนั้น . ในแง่นั้นลินุกซ์มีข้อ จำกัด น้อยกว่า BSD

Linux> = 3.9

Linux 3.9 เพิ่มตัวเลือกSO_REUSEPORTใน Linux เช่นกัน ตัวเลือกนี้ทำงานเหมือนกับตัวเลือกใน BSD และอนุญาตให้รวมที่อยู่และหมายเลขพอร์ตเดียวกันได้ตราบใดที่ซ็อกเก็ตทั้งหมดได้ตั้งค่าตัวเลือกนี้ไว้ก่อนผูกพัน

ทว่าSO_REUSEPORTระบบอื่น ๆยังคงมีความแตกต่างกันสองประการ:

  1. เพื่อป้องกัน "การไฮแจ็กพอร์ต" มีข้อ จำกัด พิเศษหนึ่งข้อ: ซ็อกเก็ตทั้งหมดที่ต้องการใช้ที่อยู่เดียวกันและการรวมกันของพอร์ตจะต้องเป็นของกระบวนการที่ใช้ ID ผู้ใช้ที่มีประสิทธิภาพเดียวกัน! ดังนั้นผู้ใช้รายหนึ่งจึงไม่สามารถ "ขโมย" พอร์ตของผู้ใช้รายอื่นได้ นี่เป็นเวทย์มนตร์พิเศษที่ชดเชยความสูญเสียSO_EXCLBIND/ SO_EXCLUSIVEADDRUSEธง

  2. นอกจากเคอร์เนลดำเนินการบางอย่าง "วิเศษพิเศษ" สำหรับSO_REUSEPORTซ็อกเก็ตที่ไม่พบในระบบปฏิบัติการอื่น ๆ : สำหรับซ็อกเก็ต UDP ก็พยายามที่จะแจกจ่ายดาต้าแกรมอย่างสม่ำเสมอสำหรับ TCP ฟังซ็อกเก็ตก็พยายามที่จะกระจายการร้องขอการเชื่อมต่อเข้ามา (ผู้ที่ได้รับการยอมรับโดยการเรียกaccept()) ทั่วซ็อกเก็ตทั้งหมดที่ใช้ที่อยู่เดียวกันและพอร์ตร่วมกัน ดังนั้นแอปพลิเคชันสามารถเปิดพอร์ตเดียวกันในกระบวนการลูกหลายกระบวนการได้อย่างง่ายดายจากนั้นใช้SO_REUSEPORTเพื่อให้การทำโหลดบาลานซ์มีราคาไม่แพงมาก


Android

แม้ว่าระบบ Android ทั้งหมดจะค่อนข้างแตกต่างจากลีนุกซ์ส่วนใหญ่, แต่ที่แกนทำงานเป็นลีนุกซ์เคอร์เนลที่ถูกปรับเปลี่ยนเล็กน้อย, ดังนั้นทุกอย่างที่ใช้กับลีนุกซ์ควรใช้กับ Android เช่นกัน.


ของ windows

Windows เท่านั้นรู้ว่าตัวเลือกไม่มีSO_REUSEADDR SO_REUSEPORTการตั้งค่าSO_REUSEADDRบนซ็อกเก็ตในพฤติกรรมของ Windows เช่นการตั้งค่าSO_REUSEPORTและSO_REUSEADDRซ็อกเก็ตใน BSD ที่มีข้อยกเว้น: ซ็อกเก็ตที่มีSO_REUSEADDRสามารถเสมอผูกตรงที่อยู่เดียวกันแหล่งที่มาและพอร์ตเป็นซ็อกเก็ตที่ถูกผูกไว้แล้วแม้ว่าซ็อกเก็ตอื่น ๆ ที่ไม่ได้มีตัวเลือกนี้ ตั้งเมื่อมันถูกผูกไว้ ลักษณะการทำงานนี้ค่อนข้างอันตรายเนื่องจากอนุญาตให้แอปพลิเคชัน "ขโมย" พอร์ตที่เชื่อมต่อของแอปพลิเคชันอื่น จำเป็นต้องพูดสิ่งนี้อาจมีผลกระทบด้านความปลอดภัยที่สำคัญ SO_EXCLUSIVEADDRUSEไมโครซอฟท์ตระหนักว่านี่อาจจะมีปัญหาและทำให้เพิ่มตัวเลือกซ็อกเก็ตอีก การตั้งค่าSO_EXCLUSIVEADDRUSEบนซ็อกเก็ตทำให้แน่ใจว่าหากการเชื่อมสำเร็จการรวมกันของแหล่งที่อยู่และพอร์ตจะเป็นเจ้าของโดยซ็อกเก็ตนี้เท่านั้นและไม่มีซ็อกเก็ตอื่นที่สามารถเชื่อมโยงกับพวกเขาได้แม้ว่าจะได้SO_REUSEADDRตั้งค่าไว้ก็ตาม

สำหรับรายละเอียดเพิ่มเติมเกี่ยวกับวิธีการตั้งค่าสถานะSO_REUSEADDRและการSO_EXCLUSIVEADDRUSEทำงานบน Windows วิธีที่พวกเขามีผลต่อการเชื่อมโยง / ผูกมัด Microsoft โปรดระบุตารางที่คล้ายกับตารางของฉันใกล้กับส่วนบนของคำตอบนั้น เพียงไปที่หน้านี้และเลื่อนลงเล็กน้อย อันที่จริงมีสามตารางหนึ่งอันแรกแสดงพฤติกรรมเก่า (ก่อนหน้า Windows 2003), หนึ่งในสองพฤติกรรม (Windows 2003 และสูงกว่า) และหนึ่งในสามแสดงให้เห็นว่าพฤติกรรมการเปลี่ยนแปลงใน Windows 2003 และในภายหลังถ้ามีการbind()โทร ผู้ใช้ที่แตกต่างกัน


Solaris

Solaris เป็นผู้สืบทอดของ SunOS SunOS มีพื้นฐานมาจากทางแยกของ BSD, SunOS 5 และต่อมาขึ้นอยู่กับทางแยกของ SVR4 อย่างไรก็ตาม SVR4 นั้นเป็นการรวมกันของ BSD, System V, และ Xenix ดังนั้น Solaris ก็ยังเป็น BSD fork และ ค่อนข้างเร็ว เป็นผล Solaris รู้เพียงไม่มีSO_REUSEADDR พฤติกรรมสวยมากเหมือนกันเช่นเดียวกับใน BSD เท่าที่ฉันรู้ว่าไม่มีวิธีใดที่จะทำให้เกิดพฤติกรรมเช่นเดียวกับใน Solaris นั่นหมายความว่ามันเป็นไปไม่ได้ที่จะผูกสองซ็อกเก็ตเข้ากับที่อยู่และพอร์ตเดียวกันSO_REUSEPORTSO_REUSEADDRSO_REUSEPORT

คล้ายกับ Windows, Solaris มีตัวเลือกให้ซ็อกเก็ตมีผลผูกพันเฉพาะ SO_EXCLBINDตัวเลือกนี้จะตั้งชื่อ ถ้าตัวเลือกนี้ถูกตั้งค่าบนซ็อกเก็ตก่อนที่จะผูกมันการตั้งค่าSO_REUSEADDRในซ็อกเก็ตอื่นจะไม่มีผลกระทบหากทั้งสองซ็อกเก็ตได้รับการทดสอบสำหรับความขัดแย้งที่อยู่ เช่นถ้าsocketAถูกผูกไว้กับที่อยู่ตัวแทนและsocketBได้SO_REUSEADDRเปิดใช้งานและถูกผูกไว้กับที่อยู่ที่ไม่ใช่สัญลักษณ์แทนและพอร์ตเดียวกับsocketAผูกนี้โดยปกติจะประสบความสำเร็จเว้นแต่socketAได้SO_EXCLBINDเปิดใช้งานในกรณีที่มันจะล้มเหลวโดยไม่คำนึงถึงธงSO_REUSEADDRsocketB


ระบบอื่น ๆ

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

ทั้งหมดที่รหัสต้องการสร้างเป็นบิต POSIX API (สำหรับชิ้นส่วนเครือข่าย) และคอมไพเลอร์ C99 (อันที่จริงแล้วคอมไพเลอร์ที่ไม่ใช่ C99 ส่วนใหญ่จะทำงานได้ตราบใดที่มีให้inttypes.hและสนับสนุนstdbool.hเช่นgccยาวก่อนที่จะรองรับ C99 เต็ม) .

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

มันทดสอบชุดค่าผสมที่เป็นไปได้ทั้งหมดที่คุณสามารถคิดได้:

  • โปรโตคอล TCP และ UDP
  • ซ็อกเก็ตปกติฟัง (เซิร์ฟเวอร์) ซ็อกเก็ตมัลติคาสต์ซ็อกเก็ต
  • SO_REUSEADDR ตั้งค่าบน socket1, socket2 หรือทั้งสองซ็อกเก็ต
  • SO_REUSEPORT ตั้งค่าบน socket1, socket2 หรือทั้งสองซ็อกเก็ต
  • การรวมที่อยู่ทั้งหมดที่คุณสามารถทำได้0.0.0.0(wildcard), 127.0.0.1(ที่อยู่เฉพาะ) และที่อยู่เฉพาะที่สองที่พบในอินเทอร์เฟซหลักของคุณ (สำหรับมัลติคาสต์เป็นเพียง224.1.2.3การทดสอบทั้งหมด)

และพิมพ์ผลลัพธ์ในตารางที่ดี มันจะทำงานบนระบบที่ไม่ทราบSO_REUSEPORTด้วยซึ่งในกรณีนี้ตัวเลือกนี้จะไม่ถูกทดสอบ

สิ่งที่โปรแกรมไม่สามารถทดสอบได้อย่างง่ายดายคือการSO_REUSEADDRทำงานของซ็อกเก็ตในTIME_WAITสถานะที่เป็นเรื่องยากมากในการบังคับและเก็บซ็อกเก็ตในสถานะนั้น โชคดีที่ระบบปฏิบัติการส่วนใหญ่ดูเหมือนว่าจะทำตัวเหมือน BSD ที่นี่และโปรแกรมเมอร์ส่วนใหญ่สามารถเพิกเฉยต่อการดำรงอยู่ของสถานะนั้นได้

นี่คือรหัส (ฉันไม่สามารถรวมไว้ที่นี่คำตอบมีขนาด จำกัด และรหัสจะผลักดันคำตอบนี้เกินขีด จำกัด )


9
ตัวอย่างเช่น "ที่อยู่ต้นทาง" ควรเป็น "ที่อยู่ท้องถิ่น" จริง ๆ ฟิลด์สามฟิลด์ถัดไปเช่นเดียวกัน การINADDR_ANYเชื่อมโยงกับไม่ได้ผูกที่อยู่ท้องถิ่นที่มีอยู่ แต่ที่อยู่ในอนาคตทั้งหมดเช่นกัน listenสร้างซ็อกเก็ตอย่างแน่นอนด้วยโปรโตคอลที่แน่นอนที่อยู่ในพื้นที่และพอร์ตในพื้นที่เดียวกันแม้ว่าคุณจะบอกว่าไม่สามารถทำได้
Ben Voigt

9
@Ben Source and Destination เป็นคำศัพท์ทางการที่ใช้สำหรับการระบุที่อยู่ IP (ซึ่งฉันอ้างอิงหลัก) Local และ Remote จะไม่มีเหตุผลเนื่องจากที่อยู่ Remote อาจเป็นที่อยู่ "Local" และตรงกันข้ามกับ Destination คือ Source ไม่ใช่ Local ฉันไม่ทราบว่าปัญหาของคุณคือINADDR_ANYอะไรฉันไม่เคยบอกว่ามันจะไม่ผูกกับที่อยู่ในอนาคต และlistenไม่ได้สร้างซ็อกเก็ตใด ๆ เลยซึ่งทำให้ทั้งประโยคของคุณแปลกไปเล็กน้อย
Mecki

7
@Ben เมื่อมีการเพิ่มที่อยู่ใหม่ในระบบมันก็เป็น "ที่อยู่ในท้องถิ่นที่มีอยู่" มันเพิ่งจะเริ่มมีอยู่ ฉันไม่ได้พูด "กับที่อยู่ในท้องที่ปัจจุบันที่มีอยู่ทั้งหมด" ที่จริงแล้วฉันยังบอกว่าซ็อกเก็ตถูกผูกไว้กับสัญลักษณ์แทนซึ่งหมายความว่าซ็อกเก็ตถูกผูกไว้กับสิ่งที่ตรงกับสัญลักษณ์นี้ตอนนี้พรุ่งนี้และในร้อยปี เช่นเดียวกับแหล่งที่มาและปลายทางคุณแค่วางยาที่นี่ คุณมีส่วนร่วมทางเทคนิคที่แท้จริงที่จะทำให้?
Mecki

8
@Mecki: คุณคิดว่าคำที่มีอยู่รวมถึงสิ่งที่ไม่มีอยู่ในตอนนี้ แต่จะมีในอนาคตหรือไม่ แหล่งที่มาและปลายทางไม่ใช่ nitpick เมื่อแพ็คเก็ตที่เข้ามาถูกจับคู่กับซ็อกเก็ตคุณกำลังบอกว่าที่อยู่ปลายทางในแพ็คเก็ตจะถูกจับคู่กับที่อยู่ "แหล่งที่มา" ของซ็อกเก็ต? มันผิดและคุณก็รู้คุณพูดไปแล้วว่าแหล่งที่มาและปลายทางนั้นตรงกันข้าม ท้องถิ่นที่อยู่ในซ็อกเก็ตจะถูกจับคู่กับที่อยู่ปลายทางของแพ็กเก็ตที่เข้ามาและอยู่ในแหล่งที่อยู่ในแพ็คเก็ตที่ส่งออก
Ben Voigt

10
@Mecki: นั่นเหมาะสมกว่าถ้าคุณพูดว่า "Local address ของซ็อกเก็ตคือที่อยู่ต้นทางของแพ็กเก็ตขาออกและที่อยู่ปลายทางของแพ็กเก็ตขาเข้า" แพ็คเก็ตมีที่อยู่ต้นทางและปลายทาง โฮสต์และซ็อกเก็ตบนโฮสต์ไม่ สำหรับซ็อกเก็ตดาตาแกรมทั้งคู่มีค่าเท่ากัน สำหรับซ็อกเก็ต TCP เนื่องจากการจับมือสามทางมีผู้เริ่มต้น (ไคลเอนต์) และผู้ตอบกลับ (เซิร์ฟเวอร์) แต่นั่นก็ไม่ได้หมายความว่าการเชื่อมต่อหรือซ็อกเก็ตที่เชื่อมต่อมีแหล่งที่มาและปลายทางเช่นกัน
Ben Voigt
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.