TCP: Hai ổ cắm khác nhau có thể chia sẻ một cổng không?


124

Đây có thể là một câu hỏi rất cơ bản nhưng nó làm tôi bối rối.

Hai ổ cắm kết nối khác nhau có thể chia sẻ một cổng không? Tôi đang viết một máy chủ ứng dụng có thể xử lý hơn 100 nghìn kết nối đồng thời và chúng tôi biết rằng số lượng cổng có sẵn trên một hệ thống là khoảng 60 nghìn (16 bit). Một ổ cắm đã kết nối được gán cho một cổng mới (chuyên dụng), do đó, điều đó có nghĩa là số lượng kết nối đồng thời bị giới hạn bởi số lượng cổng, trừ khi nhiều ổ cắm có thể chia sẻ cùng một cổng. Vì vậy, câu hỏi.

Cảm ơn đã giúp đỡ trước!

Câu trả lời:


175

Một máy chủ ổ cắm lắng nghe trên một cổng duy nhất. Tất cả các kết nối máy khách đã thiết lập trên máy chủ đó đều được liên kết với cùng một cổng lắng nghe ở phía máy chủ của kết nối. Một kết nối đã thiết lập được xác định duy nhất bằng sự kết hợp của các cặp IP / Cổng phía máy khách và phía máy chủ. Nhiều kết nối trên cùng một máy chủ có thể chia sẻ cùng một cặp IP / Cổng phía máy chủ miễn là chúng được liên kết với các cặp IP / Cổng phía máy khách khác nhau và máy chủ sẽ có thể xử lý nhiều máy khách nhất nếu tài nguyên hệ thống có sẵn cho phép. đến.

Về phía máy khách , thông lệ đối với các kết nối gửi đi mới là sử dụng cổng phía máy khách ngẫu nhiên , trong trường hợp này, có thể hết các cổng có sẵn nếu bạn thực hiện nhiều kết nối trong một khoảng thời gian ngắn.


2
Cảm ơn vì câu trả lời, Remy! Câu trả lời của bạn là tất cả những gì tôi tò mò. ;)
KJ

2
@Remy Connections bị phân biệt đối xử không chỉ bởi cổng / IP nguồn / đích mà còn bởi một giao thức (TCP, UDP, v.v.), nếu tôi không nhầm.
Ondrej Peterka

1
@OndraPeterka: có, nhưng không phải tất cả các nền tảng đều hạn chế điều đó. Ví dụ: Windows vui vẻ cho phép các ổ cắm máy chủ IPv4 và IPv6 riêng biệt nghe trên cùng một IP cục bộ: Cổng mà không cần nhảy qua vòng, nhưng hệ thống * Nix (bao gồm cả Linux và Android) thì không.
Remy Lebeau

6
@ user2268997: Bạn không thể sử dụng một ổ cắm duy nhất để kết nối với nhiều máy chủ. Bạn phải tạo một ổ cắm riêng cho mỗi kết nối.
Remy Lebeau

3
@FernandoGonzalezSanchez: Một ứng dụng khách có thể có nhiều ổ cắm TCP được liên kết với cùng một cặp IP / Cổng cục bộ miễn là chúng được kết nối với các cặp IP / Cổng từ xa khác nhau. Điều đó không dành riêng cho Windows, đó là một phần của cách TCP hoạt động nói chung.
Remy Lebeau

182

Nghe TCP / HTTP trên các cổng: Làm thế nào nhiều người dùng có thể chia sẻ cùng một cổng

Vì vậy, điều gì sẽ xảy ra khi máy chủ lắng nghe các kết nối đến trên cổng TCP? Ví dụ: giả sử bạn có máy chủ web trên cổng 80. Giả sử rằng máy tính của bạn có địa chỉ IP công cộng là 24.14.181.229 và người cố gắng kết nối với bạn có địa chỉ IP 10.1.2.3. Người này có thể kết nối với bạn bằng cách mở ổ cắm TCP tới 24.14.181.229:80. Đủ đơn giản.

Theo trực giác (và sai), hầu hết mọi người đều cho rằng nó trông giống như sau:

    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.

Điều này là trực quan, vì từ quan điểm của khách hàng, anh ta có địa chỉ IP và kết nối với máy chủ tại IP: PORT. Vì máy khách kết nối với cổng 80, thì cổng của anh ta cũng phải là 80? Đây là một điều hợp lý để nghĩ, nhưng thực tế không phải là những gì xảy ra. Nếu điều đó là chính xác, chúng tôi chỉ có thể phục vụ một người dùng cho mỗi địa chỉ IP nước ngoài. Khi một máy tính từ xa kết nối, anh ta sẽ chuyển kết nối cổng 80 sang cổng 80 và không ai khác có thể kết nối.

Ba điều phải được hiểu:

1.) Trên máy chủ, một tiến trình đang lắng nghe trên một cổng. Khi nó có kết nối, nó sẽ chuyển nó sang một chuỗi khác. Giao tiếp không bao giờ bị kẹt cổng nghe.

2.) Các kết nối được HĐH xác định duy nhất bởi 5 bộ sau: (local-IP, local-port, remote-IP, remote-port, protocol). Nếu bất kỳ phần tử nào trong tuple khác nhau, thì đây là một kết nối hoàn toàn độc lập.

3.) Khi một máy khách kết nối với một máy chủ, nó sẽ chọn một cổng nguồn bậc cao ngẫu nhiên, không được sử dụng . Bằng cách này, một máy khách có thể có tối đa ~ 64k kết nối đến máy chủ cho cùng một cổng đích.

Vì vậy, đây thực sự là những gì được tạo ra khi máy khách kết nối với máy chủ:

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

Nhìn vào những gì thực sự xảy ra

Đầu tiên, hãy sử dụng netstat để xem những gì đang xảy ra trên máy tính này. Chúng tôi sẽ sử dụng cổng 500 thay vì 80 (vì có rất nhiều thứ đang xảy ra trên cổng 80 vì nó là một cổng thông thường, nhưng về mặt chức năng thì nó không tạo ra sự khác biệt).

    netstat -atnp | grep -i ":500 "

Như mong đợi, đầu ra là trống. Bây giờ hãy bắt đầu một máy chủ web:

    sudo python3 -m http.server 500

Bây giờ, đây là kết quả của việc chạy lại netstat:

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

Vì vậy, bây giờ có một quá trình đang tích cực lắng nghe (Trạng thái: LISTEN) trên cổng 500. Địa chỉ cục bộ là 0.0.0.0, là mã để "lắng nghe tất cả các địa chỉ ip". Một sai lầm dễ mắc phải là chỉ nghe trên cổng 127.0.0.1, cổng này sẽ chỉ chấp nhận các kết nối từ máy tính hiện tại. Vì vậy, đây không phải là một kết nối, điều này chỉ có nghĩa là một quá trình được yêu cầu để ràng buộc () với IP của cổng và quá trình đó chịu trách nhiệm xử lý tất cả các kết nối đến cổng đó. Điều này cho thấy hạn chế rằng chỉ có thể có một quá trình cho mỗi máy tính lắng nghe trên một cổng (có nhiều cách để giải quyết vấn đề đó bằng cách sử dụng ghép kênh, nhưng đây là một chủ đề phức tạp hơn nhiều). Nếu một máy chủ web đang nghe trên cổng 80, nó không thể chia sẻ cổng đó với các máy chủ web khác.

Vì vậy, bây giờ, hãy kết nối người dùng với máy của chúng tôi:

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

Đây là một tập lệnh đơn giản ( https://github.com/grokit/quickweb ) mở một ổ cắm TCP, gửi tải trọng ("Kiểm tra tải trọng." Trong trường hợp này), đợi một vài giây và ngắt kết nối. Thực hiện lại netstat trong khi điều này đang xảy ra sẽ hiển thị như sau:

    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 -

Nếu bạn kết nối với một ứng dụng khách khác và thực hiện lại netstat, bạn sẽ thấy như sau:

    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 -

... tức là máy khách đã sử dụng một cổng ngẫu nhiên khác cho kết nối. Vì vậy, không bao giờ có sự nhầm lẫn giữa các địa chỉ IP.


11
Đây là câu trả lời hay nhất mà tôi từng thấy trên SO.
Việc làm

1
@ N0thing "Bằng cách này, một máy khách có thể có tối đa ~ 64k kết nối đến máy chủ cho cùng một cổng đích." Vì vậy, trong thực tế, nếu một máy khách không kết nối với cùng một máy chủ và cổng, hai lần hoặc nhiều lần đồng thời, thì một máy khách thậm chí có thể có hơn ~ 64K kết nối. Có đúng như vậy không. Nếu có thì điều đó ngụ ý rằng từ một cổng duy nhất ở phía máy khách, nó có thể có kết nối với nhiều quy trình máy chủ khác nhau (chẳng hạn như kết nối socket khác nhau). Vì vậy, trong tất cả, nhiều ổ cắm máy khách có thể nằm trên cùng một cổng trên máy khách? Hãy đọc bình luận của tôi để trả lời "Remey Lebeau". Cảm ơn: D
Prem KTiw

6
@premktiw: Có, nhiều ổ cắm máy khách có thể được liên kết với cùng một cặp IP / cổng cục bộ cùng một lúc, nếu chúng được kết nối với các cặp IP / Cổng máy chủ khác nhau, do đó, bộ giá trị của cặp cục bộ + từ xa là duy nhất. Và có, một máy khách có thể có tổng cộng hơn 64 nghìn kết nối đồng thời. Từ một cổng duy nhất, nó có thể được kết nối với vô số máy chủ (bị giới hạn bởi tài nguyên hệ điều hành có sẵn, cổng bộ định tuyến khả dụng, v.v.) miễn là cặp IP / cổng của máy chủ là duy nhất.
Remy Lebeau

1
@RemyLebeau Hài lòng. Cảm ơn bạn rất nhiều: D
Prem KTiw

1
@bibstha Tường lửa xử lý các cổng ngẫu nhiên như thế nào khi tất cả các kết nối đến đều từ chối?
PatrykG

35

Một ổ cắm đã kết nối được chỉ định cho một cổng mới (chuyên dụng)

Đó là một trực giác thông thường, nhưng nó không chính xác. Ổ cắm đã kết nối không được gán cho một cổng mới / chuyên dụng. Ràng buộc thực tế duy nhất mà ngăn xếp TCP phải thỏa mãn là bộ giá trị (local_address, local_port, remote_address, remote_port) phải là duy nhất cho mỗi kết nối socket. Do đó, máy chủ có thể có nhiều ổ cắm TCP sử dụng cùng một cổng cục bộ, miễn là mỗi ổ cắm trên cổng được kết nối với một vị trí từ xa khác nhau.

Xem đoạn "Socket Pair" tại: http://books.google.com.vn/books?id=ptSC4LpwGA0C&lpg=PA52&dq=socket%20pair%20tuple&pg=PA52#v=onepage&q=socket%20pair%20tuple&f=false


1
Cảm ơn bạn vì câu trả lời hoàn hảo, Jeremy!
KJ

6
Những gì bạn nói là hoàn toàn đúng với phía máy chủ. Tuy nhiên, cấu trúc của BSD Sockets API có nghĩa là các cổng gửi đi phía máy khách phải là duy nhất trong thực tế, bởi vì bind()hoạt động đi trước connect()hoạt động, thậm chí ngầm định.
Marquis of Lorne

1
@EJP Xin chào, tôi nghĩ trước bind()đây chỉ được sử dụng ở phía máy chủ accept()?Vì vậy phía máy khách cũng sẽ ràng buộc cổng cụ thể?
GMsoF,

5
@GMsoF: bind()có thể được sử dụng ở phía máy khách trước đây connect().
Remy Lebeau

10

Về mặt lý thuyết, có. Thực hành, không. Hầu hết các hạt nhân (bao gồm linux) không cho phép bạn một giây bind()vào một cổng đã được cấp phát. Nó không phải là một bản vá thực sự lớn để làm cho điều này được cho phép.

Đặc biệt, chúng ta nên phân biệt giữa ổ cắmcổng . Sockets là điểm cuối giao tiếp hai chiều, tức là "thứ" nơi chúng ta có thể gửi và nhận byte. Đó là một điều đặc biệt, không có trường nào như vậy trong tiêu đề gói có tên "socket".

Cổng là một định danh có khả năng xác định một ổ cắm. Trong trường hợp của TCP, một cổng là một số nguyên 16 bit, nhưng cũng có các giao thức khác (ví dụ, trên các ổ cắm unix, "cổng" về cơ bản là một chuỗi).

Vấn đề chính là như sau: nếu một gói tin đến, hạt nhân có thể xác định socket của nó bằng số cổng đích của nó. Đó là cách phổ biến nhất, nhưng nó không phải là khả năng duy nhất:

  • Các socket có thể được xác định bởi IP đích của các gói đến. Đây là trường hợp, ví dụ, nếu chúng ta có một máy chủ sử dụng đồng thời hai IP. Sau đó, chúng ta có thể chạy, ví dụ, các máy chủ web khác nhau trên cùng một cổng, nhưng trên các IP khác nhau.
  • Sockets có thể được xác định bằng cổng nguồn và ip của chúng. Đây là trường hợp trong nhiều cấu hình cân bằng tải.

Bởi vì bạn đang làm việc trên một máy chủ ứng dụng, nó sẽ có thể làm điều đó.


2
Anh ấy không hỏi về việc tạo ra một giây bind().
Marquis of Lorne

1
@ user207421 Bạn đã bao giờ thấy một hệ điều hành mà ổ cắm lắng nghe không được thiết lập bởi bind()? Tôi có thể tưởng tượng nó, vâng, nó là hoàn toàn có thể, nhưng thực tế là cả WinSock và Posix API đều sử dụng lệnh bind()gọi cho điều đó, ngay cả việc tham số của chúng thực tế cũng giống nhau. Ngay cả khi một API không có lệnh gọi này, bằng cách nào đó bạn cần phải nói nó, bạn muốn đọc các byte đến từ đâu .
peterh - Phục hồi Monica

1
@ user207421 Tất nhiên 100k kết nối TCP trở lên có thể được xử lý với các cổng giống nhau, listen()/ accept()các lệnh gọi API có thể tạo các socket theo cách mà hạt nhân sẽ phân biệt chúng theo các cổng đến của chúng. Câu hỏi của OP có thể được giải thích theo cách mà ông ấy yêu cầu về cơ bản. Tôi nghĩ, nó khá thực tế, nhưng đây không phải là câu hỏi của anh ấy theo nghĩa đen.
peterh - Phục hồi Monica

1

Không. Không thể chia sẻ cùng một cổng tại một thời điểm cụ thể. Nhưng bạn có thể làm cho ứng dụng của mình theo cách mà nó sẽ làm cho cổng truy cập ngay lập tức.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.