回答:
サーバソケットは、単一のポートでリッスンします。そのサーバー上で確立されたすべてのクライアント接続は、接続のサーバー側の同じリスニングポートに関連付けられています。確立された接続は、クライアント側とサーバー側のIP /ポートのペアの組み合わせによって一意に識別されます。同じサーバー上の複数の接続は、異なるクライアント側の IP /ポートペアに関連付けられている限り、同じサーバー側の IP /ポートペアを共有でき、サーバーは、利用可能なシステムリソースが許可する限り多くのクライアントを処理できます。に。
上のクライアント側、それは新しいアウトバウンド接続がランダムに使用するための一般的な方法でクライアント側のあなたは短時間での接続をたくさん作れば、可能なポートが不足することも可能であり、その場合には、ポートを。
それでは、サーバーがTCPポートで着信接続をリッスンするとどうなりますか?たとえば、ポート80にWebサーバーがあるとします。コンピューターのパブリックIPアドレスが24.14.181.229であり、接続しようとするユーザーのIPアドレスが10.1.2.3であるとします。この人は、24.14.181.229:80へのTCPソケットを開くことによってあなたに接続できます。十分に単純です。
直感的に(そして間違って)、ほとんどの人はそれが次のように見えると思います:
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.)接続は、OSによって次の5タプルによって一意に識別されます:(ローカルIP、ローカルポート、リモートIP、リモートポート、プロトコル)。タプルのいずれかの要素が異なる場合、これは完全に独立した接続です。
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を使用して、このコンピューターで何が起こっているかを確認します。ポート80ではなくポート500を使用します(ポート80は一般的なポートであるため、大量のものがポート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でアクティブにリッスンしているプロセス(状態:LISTEN)が1つあります。ローカルアドレスは0.0.0.0で、これは「すべてのIPアドレスをリッスンする」ためのコードです。簡単な間違いは、現在のコンピューターからの接続のみを受け入れるポート127.0.0.1のみをリッスンすることです。つまり、これは接続ではありません。これは、プロセスがポートIPへのbind()を要求したことを意味し、そのプロセスはそのポートへのすべての接続の処理を担当します。これは、ポートでリッスンするコンピュータごとに1つのプロセスしか存在できないという制限を示唆しています(多重化を使用してこれを回避する方法はありますが、これははるかに複雑なトピックです)。Webサーバーがポート80でリッスンしている場合、そのポートを他のWebサーバーと共有することはできません。
それでは、ユーザーをマシンに接続しましょう:
quicknet -m tcp -t localhost:500 -p Test payload.
これは、TCPソケットを開き、ペイロード(この場合は「テストペイロード」)を送信し、数秒待って切断する単純なスクリプト(https://github.com/grokit/quickweb)です。これが起こっている間に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アドレスが混同されることはありません。
接続されたソケットは新しい(専用)ポートに割り当てられます
それは一般的な直観ですが、それは正しくありません。接続されたソケットは、新しい/専用ポートに割り当てられていません。TCPスタックが満たす必要がある唯一の実際の制約は、(local_address、local_port、remote_address、remote_port)のタプルが、ソケット接続ごとに一意でなければならないことです。したがって、ポート上の各ソケットが異なるリモートロケーションに接続されている限り、サーバーは同じローカルポートを使用する多くのTCPソケットを持つことができます。
http://books.google.com/books?id=ptSC4LpwGA0C&lpg=PA52&dq=socket%20pair%20tuple&pg=PA52#v=onepage&q=socket%20pair%20tuple&f=falseにある「ソケットペア」の段落を参照してください。
bind()
操作の前にconnect()
操作が行われるため、クライアント側の発信ポートは実際には一意でなければならないことを意味します。
bind()
は以前にサーバー側でのみ使用されると思いました。accept()?
クライアント側も特定のポートをバインドしますか?
bind()
前にクライアント側で使用できますconnect()
。
理論的にはそうです。練習ではなく。ほとんどのカーネル(Linuxを含む)ではbind()
、すでに割り当てられているポートに1秒アクセスできません。これを許可するための大きなパッチではありませんでした。
概念的には、ソケットとポートを区別する必要があります。ソケットは双方向通信のエンドポイント、つまりバイトを送受信できる「もの」です。これは概念的なものであり、「socket」という名前のパケットヘッダーにはそのようなフィールドはありません。
ポートは、ソケットを識別することができる識別子です。TCPの場合、ポートは16ビット整数ですが、他のプロトコルもあります(たとえば、UNIXソケットでは、「ポート」は基本的に文字列です)。
主な問題は次のとおりです。着信パケットが到着した場合、カーネルは宛先ポート番号によってソケットを識別できます。これは最も一般的な方法ですが、唯一の可能性ではありません。
アプリケーションサーバーで作業しているので、それを行うことができます。
bind()
。
bind()
ますか?私はそれを想像することができます、はい、それはかなり可能ですが、実際には、WinSockとPosix APIの両方がそのbind()
呼び出しを使用しているため、それらのパラメーター化は実質的に同じです。APIにこの呼び出しがない場合でも、どういうわけか、着信バイトをどこから読み取るかを指定する必要があります。
listen()
/ accept()
APIコールはカーネルが自分の着信ポートによってそれらを区別することを途中でソケットを作成することができます。OPの質問は、彼が本質的にそれを求めている方法で解釈できます。私はそれはかなり現実的だと思いますが、これは彼の質問が文字通り意味するものではありません。