TCP: สองซ็อกเก็ตที่แตกต่างกันสามารถแบ่งปันพอร์ตได้หรือไม่?


125

นี่อาจเป็นคำถามพื้นฐาน แต่มันทำให้ฉันสับสน

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

ขอบคุณสำหรับความช่วยเหลือล่วงหน้า!

คำตอบ:


176

เซิร์ฟเวอร์ซ็อกเก็ตฟังพอร์ตเดียว การเชื่อมต่อไคลเอ็นต์ที่สร้างขึ้นทั้งหมดบนเซิร์ฟเวอร์นั้นเชื่อมโยงกับพอร์ตการรับฟังเดียวกันที่ฝั่งเซิร์ฟเวอร์ของการเชื่อมต่อ การเชื่อมต่อที่สร้างขึ้นนั้นถูกระบุโดยไม่ซ้ำกันโดยการรวมกันของคู่ IP / พอร์ตฝั่งไคลเอ็นต์และฝั่งเซิร์ฟเวอร์ การเชื่อมต่อหลายรายการบนเซิร์ฟเวอร์เดียวกันสามารถแชร์คู่ IP / พอร์ตฝั่งเซิร์ฟเวอร์เดียวกันได้ตราบใดที่เชื่อมโยงกับคู่ IP / พอร์ตฝั่งไคลเอ็นต์ที่แตกต่างกันและเซิร์ฟเวอร์จะสามารถจัดการกับไคลเอนต์ได้มากเท่าที่ทรัพยากรระบบที่มีอยู่จะอนุญาต ถึง.

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


2
ขอบคุณสำหรับคำตอบ Remy! คำตอบของคุณคือทุกสิ่งที่ฉันอยากรู้ ;)
KJ

3
@Remy Connections ถูกเลือกปฏิบัติไม่เพียง แต่โดยพอร์ตต้นทาง / ปลายทาง / IP เท่านั้น แต่ยังรวมถึงโปรโตคอล (TCP, UDP เป็นต้น) ด้วยถ้าฉันไม่เข้าใจผิด
Ondrej Peterka

2
@OndraPeterka: ใช่ แต่ไม่ใช่ทุกแพลตฟอร์มที่ จำกัด แค่นั้น ตัวอย่างเช่น Windows มีความสุขที่อนุญาตให้ซ็อกเก็ตเซิร์ฟเวอร์ IPv4 และ IPv6 แยกกันเพื่อฟังบน IP ในเครื่องเดียวกัน: พอร์ตโดยไม่ต้องกระโดดผ่านห่วง แต่ระบบ * Nix (รวมถึง Linux และ Android) ไม่ทำเช่นนั้น
Remy Lebeau

7
@ user2268997: คุณไม่สามารถใช้ซ็อกเก็ตเดียวเพื่อเชื่อมต่อกับเซิร์ฟเวอร์หลายเครื่อง คุณต้องสร้างซ็อกเก็ตแยกต่างหากสำหรับการเชื่อมต่อแต่ละครั้ง
Remy Lebeau

3
@FernandoGonzalezSanchez: ไคลเอนต์เดียวสามารถมีซ็อกเก็ต TCP หลายตัวที่เชื่อมโยงกับคู่ IP / พอร์ตในเครื่องเดียวกันได้ตราบใดที่เชื่อมต่อกับคู่ IP / พอร์ตระยะไกลที่แตกต่างกัน ซึ่งไม่เฉพาะเจาะจงสำหรับ Windows ซึ่งเป็นส่วนหนึ่งของวิธีการทำงานของ TCP โดยทั่วไป
Remy Lebeau

183

TCP / HTTP Listening บนพอร์ต: ผู้ใช้หลายคนสามารถแชร์พอร์ตเดียวกันได้อย่างไร

จะเกิดอะไรขึ้นเมื่อเซิร์ฟเวอร์รับฟังการเชื่อมต่อขาเข้าบนพอร์ต TCP ตัวอย่างเช่นสมมติว่าคุณมีเว็บเซิร์ฟเวอร์บนพอร์ต 80 สมมติว่าคอมพิวเตอร์ของคุณมีที่อยู่ IP สาธารณะเป็น 24.14.181.229 และบุคคลที่พยายามเชื่อมต่อกับคุณมีที่อยู่ IP 10.1.2.3 บุคคลนี้สามารถเชื่อมต่อกับคุณได้โดยเปิดซ็อกเก็ต TCP ไปที่ 24.14.181.229:80 ง่ายพอ

โดยสัญชาตญาณ (และผิด) คนส่วนใหญ่คิดว่ามันมีลักษณะดังนี้:

    Local Computer    | Remote Computer
    --------------------------------
    <local_ip>:80     | <foreign_ip>:80

    ^^ not actually what happens, but this is the conceptual model a lot of people have in mind.

นี่เป็นเรื่องง่ายเพราะจากมุมมองของไคลเอนต์เขามีที่อยู่ IP และเชื่อมต่อกับเซิร์ฟเวอร์ที่ IP: PORT เนื่องจากไคลเอนต์เชื่อมต่อกับพอร์ต 80 แล้วพอร์ตของเขาก็ต้องเป็น 80 ด้วย? นี่เป็นสิ่งที่สมเหตุสมผลที่จะคิด แต่จริงๆแล้วไม่ใช่สิ่งที่เกิดขึ้น หากถูกต้องเราสามารถให้บริการผู้ใช้เพียงคนเดียวต่อที่อยู่ IP ต่างประเทศ เมื่อคอมพิวเตอร์ระยะไกลเชื่อมต่อเขาจะเชื่อมต่อพอร์ต 80 ถึงพอร์ต 80 และไม่มีใครสามารถเชื่อมต่อได้

ต้องเข้าใจสามสิ่ง:

1. ) บนเซิร์ฟเวอร์กระบวนการกำลังฟังบนพอร์ต เมื่อได้รับการเชื่อมต่อแล้วจะส่งต่อไปยังเธรดอื่น การสื่อสารไม่เคยทำให้พอร์ตการฟังติดขัด

2. ) การเชื่อมต่อถูกระบุโดยเฉพาะโดย OS โดย 5-tuple ต่อไปนี้: (local-IP, local-port, remote-IP, remote-port, protocol) หากองค์ประกอบใด ๆ ในทูเปิลแตกต่างกันแสดงว่าเป็นการเชื่อมต่อที่เป็นอิสระอย่างสมบูรณ์

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

ดังนั้นนี่คือสิ่งที่สร้างขึ้นเมื่อไคลเอนต์เชื่อมต่อกับเซิร์ฟเวอร์:

    Local Computer   | Remote Computer           | Role
    -----------------------------------------------------------
    0.0.0.0:80       | <none>                    | LISTENING
    127.0.0.1:80     | 10.1.2.3:<random_port>    | ESTABLISHED

มองไปที่สิ่งที่เกิดขึ้นจริง

ขั้นแรกให้ใช้ netstat เพื่อดูว่าเกิดอะไรขึ้นกับคอมพิวเตอร์เครื่องนี้ เราจะใช้พอร์ต 500 แทน 80 (เนื่องจากมีหลายสิ่งเกิดขึ้นบนพอร์ต 80 เนื่องจากเป็นพอร์ตทั่วไป แต่ใช้งานได้จริงไม่ได้สร้างความแตกต่าง)

    netstat -atnp | grep -i ":500 "

ตามที่คาดไว้เอาต์พุตจะว่างเปล่า ตอนนี้มาเริ่มเว็บเซิร์ฟเวอร์:

    sudo python3 -m http.server 500

ตอนนี้นี่คือผลลัพธ์ของการรัน netstat อีกครั้ง:

    Proto Recv-Q Send-Q Local Address           Foreign Address         State  
    tcp        0      0 0.0.0.0:500             0.0.0.0:*               LISTEN      - 

ตอนนี้มีกระบวนการหนึ่งที่กำลังฟังอยู่ (สถานะ: LISTEN) บนพอร์ต 500 ที่อยู่ในเครื่องคือ 0.0.0.0 ซึ่งเป็นรหัสสำหรับ "การรับฟังที่อยู่ IP ทั้งหมด" ข้อผิดพลาดง่ายๆในการทำคือฟังเฉพาะพอร์ต 127.0.0.1 ซึ่งจะยอมรับการเชื่อมต่อจากคอมพิวเตอร์ปัจจุบันเท่านั้น ดังนั้นนี่ไม่ใช่การเชื่อมต่อ แต่หมายความว่ากระบวนการที่ขอผูก () กับพอร์ต IP และกระบวนการนั้นมีหน้าที่จัดการการเชื่อมต่อทั้งหมดไปยังพอร์ตนั้น สิ่งนี้บ่งบอกถึงข้อ จำกัด ที่ว่าสามารถมีได้เพียงหนึ่งกระบวนการต่อคอมพิวเตอร์ที่ฟังบนพอร์ต (มีหลายวิธีในการหลีกเลี่ยงการใช้มัลติเพล็กซ์ แต่นี่เป็นหัวข้อที่ซับซ้อนกว่ามาก) หากเว็บเซิร์ฟเวอร์กำลังรับฟังบนพอร์ต 80 จะไม่สามารถแชร์พอร์ตนั้นกับเว็บเซิร์ฟเวอร์อื่นได้

ตอนนี้เรามาเชื่อมต่อผู้ใช้กับเครื่องของเรา:

    quicknet -m tcp -t localhost:500 -p Test payload.

นี่คือสคริปต์ง่ายๆ ( https://github.com/grokit/quickweb ) ที่เปิดซ็อกเก็ต TCP ส่ง payload ("Test payload." ในกรณีนี้) รอสักครู่แล้วตัดการเชื่อมต่อ การทำ netstat อีกครั้งในขณะที่เกิดขึ้นจะแสดงสิ่งต่อไปนี้:

    Proto Recv-Q Send-Q Local Address           Foreign Address         State  
    tcp        0      0 0.0.0.0:500             0.0.0.0:*               LISTEN      -
    tcp        0      0 192.168.1.10:500        192.168.1.13:54240      ESTABLISHED -

หากคุณเชื่อมต่อกับไคลเอนต์อื่นและทำ netstat อีกครั้งคุณจะเห็นสิ่งต่อไปนี้:

    Proto Recv-Q Send-Q Local Address           Foreign Address         State  
    tcp        0      0 0.0.0.0:500             0.0.0.0:*               LISTEN      -
    tcp        0      0 192.168.1.10:500        192.168.1.13:26813      ESTABLISHED -

... นั่นคือไคลเอนต์ใช้พอร์ตสุ่มอื่นสำหรับการเชื่อมต่อ ดังนั้นจึงไม่มีความสับสนระหว่างที่อยู่ IP


12
นี่คือคำตอบที่ดีที่สุดที่ฉันเคยเห็นใน SO
งาน

1
@ N0thing "ด้วยวิธีนี้ไคลเอนต์เดียวสามารถเชื่อมต่อกับเซิร์ฟเวอร์ได้ถึง 64k สำหรับพอร์ตปลายทางเดียวกัน" ดังนั้นในทางปฏิบัติหากไคลเอนต์ไม่เชื่อมต่อกับเซิร์ฟเวอร์และพอร์ตเดียวกันสองครั้งหรือหลายครั้งพร้อมกันไคลเอนต์สามารถมีการเชื่อมต่อได้มากกว่า ~ 64K ว่าจริงมั้ย. ถ้าใช่นั่นหมายความว่าจากพอร์ตเดียวบนฝั่งไคลเอ็นต์สามารถเชื่อมต่อกับกระบวนการเซิร์ฟเวอร์ต่างๆได้มากมาย (เช่นการเชื่อมต่อซ็อกเก็ตแตกต่างกัน) ดังนั้นซ็อกเก็ตไคลเอนต์หลายตัวสามารถอยู่บนพอร์ตเดียวกันบนเครื่องไคลเอนต์ได้หรือไม่? โปรดอ่านความคิดเห็นของฉันเพื่อตอบ "Remey Lebeau" Thanks: D
Prem KTiw

6
@premktiw: ใช่ซ็อกเก็ตไคลเอนต์หลายตัวสามารถเชื่อมโยงกับคู่ IP / พอร์ตในเครื่องเดียวกันได้ในเวลาเดียวกันหากเชื่อมต่อกับคู่ IP / พอร์ตของเซิร์ฟเวอร์ที่แตกต่างกันดังนั้นคู่ของคู่รีโมตในเครื่อง + จึงไม่ซ้ำกัน และใช่เป็นไปได้ที่ไคลเอ็นต์จะมีการเชื่อมต่อพร้อมกันมากกว่า 64K จากพอร์ตเดียวสามารถเชื่อมต่อกับเซิร์ฟเวอร์จำนวนไม่ จำกัด ที่อาจเกิดขึ้นได้ (ถูก จำกัด โดยทรัพยากรระบบปฏิบัติการที่มีพอร์ตเราเตอร์ที่มีอยู่ ฯลฯ ) ตราบใดที่คู่ IP / พอร์ตของเซิร์ฟเวอร์ไม่ซ้ำกัน
Remy Lebeau

1
@RemyLebeau พอใจ. ขอบคุณมาก: D
Prem KTiw

1
@bibstha ไฟร์วอลล์จัดการกับพอร์ตแบบสุ่มอย่างไรเมื่อการเชื่อมต่อขาเข้าทั้งหมดปฏิเสธ
PatrykG

35

ซ็อกเก็ตที่เชื่อมต่อถูกกำหนดให้กับพอร์ตใหม่ (เฉพาะ)

นั่นเป็นสัญชาตญาณทั่วไป แต่ไม่ถูกต้อง ซ็อกเก็ตที่เชื่อมต่อไม่ได้กำหนดให้กับพอร์ตใหม่ / เฉพาะ ข้อ จำกัด ที่แท้จริงเพียงประการเดียวที่สแต็ก TCP ต้องเป็นไปตามคือทูเปิลของ (local_address, local_port, remote_address, remote_port) ต้องไม่ซ้ำกันสำหรับการเชื่อมต่อซ็อกเก็ตแต่ละรายการ ดังนั้นเซิร์ฟเวอร์สามารถมีซ็อกเก็ต TCP จำนวนมากโดยใช้พอร์ตโลคัลเดียวกันตราบเท่าที่แต่ละซ็อกเก็ตบนพอร์ตเชื่อมต่อกับตำแหน่งระยะไกลที่แตกต่างกัน

ดูย่อหน้า "Socket Pair" ได้ที่: http://books.google.com/books?id=ptSC4LpwGA0C&lpg=PA52&dq=socket%20pair%20tuple&pg=PA52#v=onepage&q=socket%20pair%20tuple&f=false


1
ขอบคุณสำหรับคำตอบที่สมบูรณ์แบบ Jeremy!
KJ

6
สิ่งที่คุณพูดเป็นความจริงทั้งหมดจากฝั่งเซิร์ฟเวอร์ อย่างไรก็ตามโครงสร้างของ BSD Sockets API หมายความว่าพอร์ตฝั่งไคลเอ็นต์ขาออกจะต้องไม่ซ้ำกันในทางปฏิบัติเนื่องจากการbind()ดำเนินการมาก่อนการconnect()ดำเนินการแม้โดยปริยาย
Marquis of Lorne

1
@EJP สวัสดีฉันคิดว่าbind()ใช้เฉพาะที่ฝั่งเซิร์ฟเวอร์ก่อนaccept()?ดังนั้นฝั่งไคลเอ็นต์จะผูกพอร์ตเฉพาะด้วย?
GMsoF

5
@GMsoF: สามารถนำมาใช้ในฝั่งไคลเอ็นต์ก่อนbind() connect()
Remy Lebeau

10

ในทางทฤษฎีใช่ ปฏิบัติไม่ใช่ เมล็ดส่วนใหญ่ (รวม linux) ไม่อนุญาตให้คุณใช้bind()พอร์ตที่จัดสรรไว้แล้วแม้แต่วินาทีเดียว มันไม่ใช่แพทช์ใหญ่จริงๆที่อนุญาตให้ทำเช่นนี้

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

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

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

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

เนื่องจากคุณกำลังทำงานบนแอปพลิเคชันเซิร์ฟเวอร์จึงสามารถทำเช่นนั้นได้


2
bind()เขาไม่ได้ถามเกี่ยวกับการทำครั้งที่สอง
Marquis of Lorne

1
@ user207421 คุณเคยเห็นระบบปฏิบัติการที่ซ็อกเก็ตการฟังไม่ได้ตั้งค่าโดยbind()? ฉันนึกภาพออกใช่มันค่อนข้างเป็นไปได้ แต่ความจริงก็คือทั้ง WinSock และ Posix API ใช้การbind()เรียกแบบนั้นแม้กระทั่งพาราเมตริเซชั่นของพวกเขาก็เหมือนกัน แม้ว่า API จะไม่มีการเรียกนี้ แต่คุณต้องบอกว่าคุณต้องการอ่านไบต์ขาเข้าจากที่ใด
peterh - คืนสถานะ Monica

1
@ user207421 แน่นอนว่าการเชื่อมต่อ TCP 100k ขึ้นไปสามารถจัดการได้ด้วยพอร์ตเดียวกันlisten()/ การaccept()เรียก API สามารถสร้างซ็อกเก็ตด้วยวิธีที่เคอร์เนลจะแยกความแตกต่างจากพอร์ตขาเข้า คำถามของ OP สามารถตีความได้ในแบบที่เขาถามเป็นหลัก ฉันคิดว่ามันค่อนข้างเป็นจริง แต่ไม่ใช่สิ่งที่คำถามของเขาหมายถึงอย่างแท้จริง
peterh - คืนสถานะ Monica

1

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

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