web-dev-qa-db-ja.com

どのようにして複数のクライアントがサーバー上の1つのポート(たとえば80)に同時に接続するのですか?

私はportsの仕組みの基本を理解しています。しかし、私が得られないのは、どのようにして複数のクライアントが同時にポート80に接続することができるかです。サーバは利用可能なポートからクライアントに返答し、単に返答が80から来たと言っていますか?これはどのように作動しますか?

369
IamIC

まず、「ポート」は単なる数字です。すべての「ポートへの接続」は、実際には「宛先ポート」ヘッダーフィールドにその番号が指定されているパケットを表します。

さて、あなたの質問に対する答えは2つあります。1つはステートフルプロトコル用、もう1つはステートレスプロトコル用です。

ステートレスプロトコル(すなわちUDP)の場合、「接続」が存在しないので問題はありません。複数の人が同じポートにパケットを送信でき、それらのパケットはどのような順序で到着します。誰も「接続」状態になることはありません。

ステートフルプロトコル(TCPなど)の場合、接続は送信元ポートと宛先ポート、および送信元と宛先IPアドレスで構成される4タプルによって識別されます。したがって、2台の異なるマシンが3台目のマシンの同じポートに接続すると、送信元IPが異なるため、2つの異なる接続があります。同じマシン(またはNATの後ろに2台、または同じIPアドレスを共有している2台)が単一のリモートエンドに2回接続する場合、接続は送信元ポート(通常はランダムな大きな番号のポート)によって区別されます。

単純に、クライアントから同じWebサーバーに2回接続した場合、2つの接続には自分の視点から見た送信元ポートとWebサーバーからの送信先ポートが異なります。したがって、両方の接続に同じ送信元IPアドレスと宛先IPアドレスがある場合でも、あいまいさはありません。

ポートはIPアドレスを多重化するための手段であるため、異なるアプリケーションが同じIPアドレス/プロトコルの組み合わせを監視できます。アプリケーションが独自の上位プロトコルを定義しない限り、ポートを多重化する方法はありません。同じプロトコルを使用する2つの接続に、同じ送信元IPアドレスと宛先IPアドレス、および同じ送信元ポートと宛先ポートがある場合、それらは同じ接続である必要があります。

427
Borealid

重要:

"Borealid"からの応答が不正確で多少不正確だと言って申し訳ありません - この質問に答えるためのステートフルネスやステートレスネスとは無関係です。

まず、以下の2つの規則を覚えておいてください。

  1. ソケットの主キー:ソケットは{SRC-IP, SRC-PORT, DEST-IP, DEST-PORT, PROTOCOL}ではなく{SRC-IP, SRC-PORT, DEST-IP, DEST-PORT}によって識別されます - プロトコルはソケットの定義の重要な部分です。

  2. OSのプロセスとソケットのマッピング:プロセスは複数のソケットに関連付けることができます(開くことができます/聞くことができます)。これは多くの読者には明白かもしれません。

例1:同じサーバーポートに接続している2つのクライアントは、socket1 {SRC-A, 100, DEST-X,80, TCP}socket2{SRC-B, 100, DEST-X,80, TCP}を意味します。つまり、ホストAはサーバーXのポート80に接続し、別のホストBも同じサーバーXに同じポート80を接続します。サーバーがこれら2つのソケットを処理する方法は、サーバーがシングルスレッドかマルチスレッドかによって異なります。これについては後で説明します。重要なのは、1つのサーバーが同時に複数のソケットをlistenできるということです。

投稿の元の質問に答えるには

ステートフルまたはステートレスのプロトコルに関係なく、2つのクライアントが同じサーバーポートに接続できます(クライアントIPごとに異なるソケットを割り当てることができるため)。同じクライアントが同じサーバーポートに接続する2つのソケットを持つこともできます - そのようなソケットはSRC-PORTが異なるので。公平を期して、「Borealid」は本質的に同じ正解を述べましたが、state-less/fullへの言及は一種の不必要で混乱を招くものでした。

サーバーがどのソケットに答えるべきかをどのように知っているかについての質問の後半部分に答えること。最初に、同じポートをlistenしている単一サーバー・プロセスの場合、複数のソケットが存在する可能性があることを理解してください(同じクライアントからのものでも異なるクライアントからのものでもよい)。どのリクエストがどのソケットに関連付けられているかをサーバが知っている限り、常に同じソケットを使って適切なクライアントに応答することができます。したがって、サーバーは、クライアントが最初に試行した元のポートとは別のポートを自分のノードで開く必要がありません。 bind 接続してください。ソケットがバインドされた後にいずれかのサーバーが異なるサーバーポートを割り当てた場合、私の意見では、サーバーはそのリソースを無駄にしているため、クライアントは次のことを行う必要があります。 bind 割り当てられた新しいポートに再度接続します。

完全を期すためにもう少し:

例2:サーバーの2つの異なるプロセスが同じポートをリスンできることは非常に興味深い質問です。あなたがプロトコルをソケットを定義するパラメータの1つとして考えないならば、答えはノーです。そのような場合、サーバーポートに接続しようとしている単一のクライアントが、そのクライアントが2つのリスニングプロセスのうちどちらを意図しているかを言及するメカニズムを持たないと言えるのではないでしょうか。これは、規則(2)で主張されているものと同じテーマです。しかし、 'protocol'もソケット定義の一部であるため、これは間違った答えです。したがって、同じノード内の2つのプロセスは、それらが異なるプロトコルを使用している場合にのみ、同じポートをlistenできます。たとえば、2つの無関係なクライアント(1つはTCPを使用し、もう1つはUDPを使用しているとします) bind 同じサーバーノードと同じポートに接続して通信しますが、それらは2つの異なるサーバープロセスによって処理される必要があります。

サーバータイプ - 単一および複数:

サーバーのプロセスがポートをリッスンしているとき、つまり複数のソケットが同時に同じサーバープロセスに接続して通信できることを意味します。サーバーが単一の子プロセスのみを使用してすべてのソケットを処理する場合、そのサーバーはシングルプロセス/スレッドと呼ばれ、サーバーが各ソケットを1つのサブプロセスで処理するために多数のサブプロセスを使用する場合、サーバーはマルチプロセスと呼ばれます。プロセス/スレッドサーバーサーバーのタイプに関係なく、サーバーは常に同じ初期ソケットを使用して応答することができます(別のサーバーポートを割り当てる必要はありません)。

できれば と残りの2冊をお勧めします。

親子プロセスに関するメモ( 'Ioan Alexandru Cucu'のクエリ/コメントに対する回答として)

2つのプロセスに関連してAとBという概念を言及した場合は、それらが親子関係によって関連していないと考えてください。設計上、OS(特にUNIX)では、子プロセスは親からすべてのファイル記述子(FD)を継承できます。したがって、プロセスAがリスニングするすべてのソケット(UNIXのようにOSもFDの一部です)は、それらがAに対する親子関係によって関係している限り、もっと多くのプロセスA1、A2、...によってリスニングすることができます。独立したプロセスB(つまり、Aと親子関係がない)は、同じソケットをlistenできません。さらに、2つの独立したプロセスが同じソケットをリッスンすることを許可しないというこの規則は、OS(またはそのネットワークライブラリ)にあり、ほとんどのOSがこれを守っています。しかし、この制限に非常によく違反する可能性がある独自のOSを作成することができます。

335
KGhatak

ポートでのTCP/HTTPリスニング:多くのユーザーが同じポートを共有する方法

それで、サーバがTCPポートで入ってくる接続を待ち受けるとどうなりますか?たとえば、ポート80にWebサーバーがあるとします。コンピュータのパブリック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アドレスごとに1人のユーザーにしかサービスを提供できません。リモートコンピュータが接続すると、ポート80からポート80への接続を占有し、他の人が接続できなくなります。

3つのことを理解する必要があります。

1.)サーバーでは、プロセスはポートで待機しています。接続が確立されると、別のスレッドに渡します。通信がリスニングポートを占有することはありません。

2.)接続は、次の5タプルによってOSによって一意に識別されます。(ローカルIP、ローカルポート、リモートIP、リモートポート、プロトコル)。タプルの要素が異なる場合、これは完全に独立した接続です。

3.)クライアントがサーバーに接続すると、ランダムな未使用の上位送信元ポートが選択されます。このようにして、1つのクライアントが同じ宛先ポートに対して最大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を使ってこのコンピュータで何が起こっているのかを見てみましょう。私たちは80の代わりにポート500を使うつもりです(それが一般的なポートであるのでたくさんのものがポート80で起こっています、しかし機能的にそれは違いを生じません)。

    netstat -atnp | grep -i ":500 "

予想通り、出力は空白です。それでは、Webサーバーを起動しましょう。

    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      - 

そのため、ポート500上でアクティブにリスニングしているプロセス(State:LISTEN)が1つあります。ローカルアドレスは0.0.0.0です。これは「すべてのリスニング」のコードです。簡単な間違いは、アドレス127.0.0.1で待機することです。これは、現在のコンピュータからの接続のみを受け入れます。したがって、これは接続ではありません。これは、プロセスがポートIPへのbind()を要求したことを意味し、そのプロセスはそのポートへのすべての接続を処理する責任があります。これは、ポートで待機しているコンピュータごとに1つのプロセスしか存在できないという制限を示唆しています(多重化を使用してそれを回避する方法がありますが、これははるかに複雑なトピックです)。 Webサーバーがポート80をリッスンしている場合、そのポートを他のWebサーバーと共有することはできません。

それでは、ユーザーを私たちのマシンに接続しましょう。

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

これはTCPソケットを開く簡単なスクリプト( https://github.com/grokit/dcore/tree/master/apps/quicknet )で、送信します。ペイロード(この場合は「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アドレス間の混乱はありません。

163
N0thing

通常、接続しているクライアントごとに、サーバーはそのクライアントと通信する子プロセス(TCP)をフォークします。親サーバーは、クライアントと通信する確立されたソケットを子プロセスに渡します。

子サーバーからソケットにデータを送信すると、OSのTCPスタックはクライアントに戻るパケットを作成し、「fromポート」を80に設定します。

25
m1tk4

サーバー側でソケットバインディングを作成した後、複数のクライアントがサーバー上の同じポート(たとえば80)に接続できます。 )(ローカルIPとポートの設定)listenがソケットで呼び出され、OSに着信接続を受け入れるように指示します。

クライアントがポート80でサーバーに接続しようとすると、サーバーソケットでaccept呼び出しが呼び出されます。これにより、接続しようとしているクライアント用の新しいソケットが作成され、同じポート80を使用する後続のクライアント用にも同様に新しいソケットが作成されます。

イタリック体の単語はシステムコールです。

参考

http://www.scs.stanford.edu/07wi-cs244b/refs/net2.pdf

4
pumpkin_cat