ホームページ  >  記事  >  バックエンド開発  >  ソケットの so_reuseport と so_reuseaddr の違いについて詳しく説明します

ソケットの so_reuseport と so_reuseaddr の違いについて詳しく説明します

不言
不言オリジナル
2018-04-28 15:15:161739ブラウズ

次の記事では、ソケットの so_reuseport と so_reuseaddr の違いについて詳しく説明します。これは参考になるものであり、皆さんのお役に立てれば幸いです。一緒に見てみましょう

Socket の基本的な背景

これら 2 つのオプションの違いについて議論するとき、知っておく必要があるのは、BSD 実装がすべてのソケット実装の起源であるということです。基本的に他のすべてのシステムは、BSD ソケット実装 (または少なくともそのインターフェイス) をある程度参照し、その後独自の進化を開始しました。明らかに、BSD 自体は時間の経過とともに常に進化し、変化しています。したがって、後で BSD を参照するシステムには、以前に BSD を参照するシステムよりも多くの機能があります。したがって、BSD ソケット実装を理解することは、他のソケット実装を理解するための基礎となります。 BSD ソケットの実装を分析してみましょう。

その前に、まず TCP/UDP 接続を一意に識別する方法を理解する必要があります。 TCP/UDP は、次の 5 つのタプルによって一意に識別されます:


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


これらの値の一意の組み合わせにより、接続を一意に識別します。したがって、どのような接続においても、これら 5 つの値がまったく同じになることはありません。そうしないと、オペレーティング システムがこれらの接続を区別できなくなります。

ソケットのプロトコルは、socket()で初期化されるときに設定されます。送信元アドレスと送信元ポートは、bind() を呼び出すときに設定されます。宛先アドレスと宛先ポートは、connect() を呼び出すときに設定されます。 UDP はコネクションレス型であり、宛先ポートに接続しなくても UDP ソケットを使用できます。ただし、場合によっては、宛先アドレスとポートで接続を確立した後、UDP を使用することもできます。コネクションレス UDP を使用してデータを送信する場合、bind() が明示的に呼び出されない場合、初めてデータを送信するときに、システムは自動的に UDP ソケットをローカル アドレスと特定のポートにバインドします (そうしないと、プログラムは、によって応答されたデータを受け入れることができません)リモートホスト)。同様に、バインドされたアドレスのない TCP ソケットは、接続が確立されると自動的にローカル アドレスとポートにバインドされます。

ポートを手動でバインドする場合、ソケットをポート 0 にバインドできます。ポート 0 にバインドするということは、使用するポートをシステムに決定させることを意味します (通常は、事前に範囲が決定されたオペレーティング システム固有のポート番号のセットから)。任意のポートを意味します。同様に、ワイルドカードを使用して、どの送信元アドレスをバインドするかをシステムに決定させることもできます (ipv4 ワイルドカードは 0.0.0.0、ipv6 ワイルドカードは::)。ポートとは異なり、ソケットはホスト上のすべてのインターフェイスに対応する任意のアドレスにバインドできます。このソケットに接続されている宛先アドレスとルーティング テーブル内の対応する情報に基づいて、オペレーティング システムはこのソケットをバインドする適切なアドレスを選択し、このアドレスを使用して以前のワイルドカード IP アドレスを置き換えます。

デフォルトでは、2 つのソケットを同じ送信元アドレスと送信元ポートの組み合わせにバインドすることはできません。たとえば、socketA をアドレス A:X にバインドし、socketB をアドレス B:Y にバインドします。ここで、A と B は IP アドレス、X と Y はポートです。したがって、A==B の場合は X!=Y が満たされ、X==Y の場合は A!=B が満たされる必要があります。特定のソケットがワイルドカード IP アドレスにバインドされている場合、実際にはローカル マシンのすべての IP がシステムによってそのソケットにバインドされているとみなされることに注意してください。たとえば、ソケットは 0.0.0.0:21 にバインドされます。この場合、どの特定の IP アドレスが選択されても、他のソケットはポート 21 にバインドできなくなります。ワイルドカード IP0.0.0.0 はすべてのローカル IP と競合するためです。

上記はすべて、主要なオペレーティング システム間で基本的に同じです。各 SO_REUSEADDR は異なる意味を持ちます。まず、BSD 実装について説明します。 BSD は他のすべてのソケット実装メソッドのソースだからです。

BSD

SO_REUSEADDR

特定のアドレスとポートにバインドする前にソケットの SO_REUSEADDR 属性が設定されている場合、そのソケットがまったく同じソースアドレスに別のソケットにバインドしようとしない限り、それ以外の場合、ソケットはこのアドレス ポートのペアを正常にバインドできます。これは以前と同じように聞こえるかもしれません。しかし、そこにあるキーワードは完成しています。 SO_REUSEADDR は主に、システムがワイルドカード IP アドレスの競合を処理する方法を変更します。

SO_REUSEADDR が使用されていない場合、socketA を 0.0.0.0:21 にバインドすると、ローカル マシン上の他のソケットのポート 21 へのバインド (192.168.1.1:21 へのバインドなど) によって EADDRINUSE エラーが発生します。 0.0.0.0 はワイルドカード IP アドレス、つまり任意の IP アドレスを意味するため、このマシン上の他の IP アドレスはシステムによって占有されているとみなされます。 SO_REUSEADDR オプションが設定されている場合、0.0.0.0:21 と 192.168.1.1:21 はまったく同じアドレス ポート ペアではないため (そのうちの 1 つはワイルドカード IP アドレスで、もう 1 つはローカル マシンの特定の IP アドレスです)、このような結合は間違いなく成功することができます。ソケット A とソケット B が初期化される順序に関係なく、SO_REUSEADDR が設定されている限りバインディングは成功しますが、SO_REUSEADDR が設定されていない限りバインディングは成功しないことに注意してください。

以下の表は、考えられるいくつかのシナリオとその結果を示しています。 68 .1.1:21192.168.1.1:21

エラー (EADDRINUSE)

オン/オフ192.168.1.1:2110.0.1.1:21OKオン/オフ10.0.1.1:21192.16 8.1.1 :21OKオフ192.168.1.1:210.0.0.0:21エラー(EADDRINUSE)オフ0.0.0.0:21192.168.1.1: 2 1エラー(耳鳴り)ON192.168.1.1:210.0.0.0:21OKON0.0.0.0:21192.168.1.1:21 OK オン/オフ 0.0.0.0: 210.0.0.0: 21OK

この表は、socketA が表内の対応するアドレスに正常にバインドされ、次にソケット B が初期化され、その SO_REUSEADDR 設定が表の最初の列に示されているとおりであることを前提としています。その後、socketB が表内の対応するアドレスのバインドを試みます。 Result 列は、そのバインディングの結果です。最初の列の値が ON/OFF の場合、SO_REUSEADDR が設定されているかどうかは結果に関係ありません。

ワイルドカード IP アドレスに対する SO_REUSEADDR の役割については上で説明しましたが、この役割だけではありません。もう 1 つの役割は、サーバー側プログラミングを行うときに誰もが SO_REUSEADDR オプションを使用する理由です。その他の役割とその重要な用途を理解するには、まず TCP プロトコルがどのように機能するかについてより深く議論する必要があります。

各ソケットには対応する送信バッファがあります。 send() メソッドが正常に呼び出された場合、実際には、送信する必要があるデータは必ずしもすぐに送信されるわけではなく、送信バッファーに追加されます。 UDP ソケットの場合、データはすぐに送信されない場合でも、通常はすぐに送信されます。ただし、TCP ソケットの場合、送信バッファにデータを追加した後、データが実際に送信されるまでに比較的長い時間がかかることがあります。したがって、TCP ソケットを閉じると、実際には送信バッファ内に送信を待っているデータがまだ存在する可能性があります。ただし、この時点では、send() が成功を返すため、コードはデータが実際に正常に送信されたとみなします。 close() を呼び出した直後に TCP ソケットが閉じられた場合、このデータはすべて失われ、コードはそれを認識することはありません。ただし、TCP は信頼性の高いトランスポート層プロトコルであり、送信されるデータを直接破棄することは明らかに推奨されません。実際、送信バッファに送信するデータがまだあるときにソケットの close() メソッドが呼び出される場合、いわゆる TIME_WAIT 状態に入ります。この状態では、ソケットはすべてのデータが正常に送信されるまで、またはタイムアウトが発生するとソケットが強制的に閉じられるまで、バッファ内のデータの送信を試行し続けます。

ソケットを強制的に閉じるまでのオペレーティング システムのカーネルの最大待機時間は、リンガー タイムと呼ばれます。遅延時間はほとんどのシステムでグローバルに設定されており、比較的長くなります (ほとんどのシステムでは 2 分に設定されています)。ソケットを初期化するときに SO_LINGER オプションを使用して、各ソケットの遅延時間を具体的に設定することもできます。遅延待機を完全にオフにすることもできます。ただし、遅延時間を 0 に設定する (遅延待機を完全にオフにする) ことは、良いプログラミング方法ではないことに注意してください。 TCP ソケットを正常に閉じることは、リモート ホストとの複数のデータ パケットの交換 (パケット損失の場合の損失再送信を含む) を含む比較的複雑なプロセスであり、このデータ パケット交換プロセスに必要な時間も遅延時間に含まれるためです。 。遅延待機を無効にすると、ソケットは閉じられるときに送信されるすべてのデータを破棄するだけでなく、常に強制的に閉じられます (TCP は接続指向のプロトコルであるため、閉じているパケットをリモート ポートと交換しないと、リモート ポートは長い待機状態にあります)。したがって、通常、実際のプログラミングでこれを行うことはお勧めしません。 TCP 切断のプロセスはこの記事の範囲外です。興味がある場合は、このページを参照してください。実際、遅延待機を無効にし、明示的にソケットを閉じずにプログラムが終了した場合、BSD (および場合によっては他のシステム) は設定を無視して遅延待機を実行します。たとえば、プログラムが exit() メソッドを呼び出した場合、またはそのプロセスがシグナルを使用して終了された場合 (不正なメモリ アクセスなどによるプロセスのクラッシュを含む)。したがって、あらゆる状況において、遅延待ち時間に関係なくソケットが終了することを 100% 保証することはできません。

ここでの問題は、オペレーティング システムが TIME_WAIT ステージでソケットをどのように扱うかです。 SO_REUSEADDR オプションが設定されていない場合、TIME_WAIT フェーズのソケットは依然として元のアドレスとポートにバインドされているとみなされます。ソケットが完全に閉じられる (TIME_WAIT フェーズが終了する) まで、新しいソケットをこのアドレスとポートのペアにバインドしようとする他の試みは成功しません。この待機は、遅延された待機と同じ長さになる場合があります。したがって、閉じられたばかりのソケットに対応するアドレスとポートのペアに新しいソケットをすぐにバインドすることはできません。ほとんどの場合、この操作は失敗します。

ただし、新しいソケットに SO_REUSEADDR オプションを設定した場合、現在のアドレス ポート ペアにバインドされている別のソケットがあり、TIME_WAIT ステージにある場合、この既存のバインド関係は無視されます。実際、TIME_WAIT ステージのソケットはすでにセミクローズされており、新しいソケットをこのアドレスとポートのペアにバインドしても問題はありません。この場合、このポートにバインドされている元のソケットは通常、新しいソケットに影響を与えません。ただし、ある時点で、TIME_WAIT ステージにあるもののまだ動作しているソケットに対応するアドレス ポート ペアに新しいソケットをバインドすると、意図しない予測できない悪影響が生じる可能性があることに注意してください。しかし、この問題はこの記事の範囲を超えています。そして幸いなことに、これらの悪影響は実際にはほとんど見られません。

最後に、SO_REUSEADDR について注意する必要があるのは、新しいソケットに SO_REUSEADDR を設定している限り、上記のすべてが当てはまるということです。現在のアドレスとポートのペアにバインドされている元のソケットに関しては、TIME_WAIT ステージのソケットに SO_REUSEADDR が設定されているかどうかは影響しません。バインド操作が成功したかどうかを判断するコードは、bind() メソッドに渡された新しいソケットの SO_REUSEADDR オプションをチェックするだけです。関連する他のソケットの SO_REUSEADDR オプションはチェックされません。

SO_REUSEPORT

多くの人が SO_REUSEADDR を SO_REUSEPORT と間違えています。基本的に、SO_REUSEPORT を使用すると、以前にバインドされたすべてのソケットに SO_REUSEPORT オプションが設定されている限り、任意の数のソケットをまったく同じソース アドレスとポートのペアにバインドできます。アドレスとポートのペアにバインドされた最初のソケットに SO_REUSEPORT が設定されていない場合、後続のソケットが SO_REUSEPORT を設定しているかどうかに関係なく、このアドレス ポートとまったく同じアドレスにバインドすることはできません。このアドレスとポートのペアにバインドされた最初のソケットがバインド関係を解放しない限り。 SO_REUSEADDR とは異なり、SO_REUSEPORT を処理するコードは、現在バインドしようとしているソケットの SO_REUSEPORT をチェックするだけでなく、現在バインドしようとしているアドレス ポート ペアに以前にバインドされていたソケットの SO_REUSEPORT オプションもチェックします。

SO_REUSEPORT は SO_REUSEADDR と等しくありません。これが意味するのは、すでにバインドされたアドレスを持つソケットに SO_REUSEPORT が設定されておらず、別の新しいソケットに SO_REUSEPORT が設定されていて、現在のソケットとまったく同じポート アドレスのペアにバインドしようとした場合、バインドの試行は失敗するということです。同時に、現在のソケットがすでに TIME_WAIT ステージにあり、SO_REUSEPORT オプションが設定された新しいソケットが現在のアドレスにバインドしようとすると、バインド操作も失敗します。現在 TIME_WAIT フェーズにあるソケットに対応するアドレス ポート ペアに新しいソケットをバインドするには、バインドする前に新しいソケットの SO_REUSEADDR オプションを設定するか、バインドする前に両方のソケットに SO_REUSEADDR オプションを設定する必要があります。オプション。もちろん、ソケットの SO_REUSEADDR オプションと SO_REUSEPORT オプションを同時に設定することも可能です。

SO_REUSEPORT は、SO_REUSEADDR の後に BSD システムに追加されました。これが、現在一部のシステムのソケット実装に SO_REUSEPORT オプションがない理由です。このオプションが BSD システムに追加される前に、BSD ソケット実装を参照していたためです。このオプションが追加される前は、BSD システムでは 2 つのソケットをまったく同じアドレスとポートのペアにバインドする方法はありませんでした。

Connect() は EADDRINUSE を返しますか?

bind() 操作が EADDRINUSE エラーを返す場合があります。しかし、奇妙なことに、connect() オペレーションを呼び出すと、EADDRINUSE エラーも発生する可能性があります。どうしてこれなの?現在のポートで接続を確立しようとしているリモート アドレスも占有されているのはなぜですか?複数のソケットを同じリモート アドレスに接続する場合に問題は発生しますか?

以前この記事でも触れましたが、接続関係は5倍で決まります。あらゆる接続関係において、この 5 つ組は一意である必要があります。そうしないと、システムは 2 つの接続を区別できなくなります。アドレスの再利用を使用すると、同じプロトコルを使用して 2 つのソケットを同じアドレスとポートのペアにバインドできます。これは、これら 2 つのソケットについて、5 つのタプル {a88b79ba1ccee8890e978c768d80530d, 3037a7cee66f0ae45683db6cc2520e8a, 1b9006debff836a11384f3384ae84936} がすでに同じであることを意味します。この場合、両方を同じリモート アドレス ポートに接続しようとすると、2 つの接続関係の 5 タプルはまったく同じになります。つまり、2 つの同一の接続が生成されます。これは TCP プロトコルでは許可されません (UDP はコネクションレスです)。これら 2 つの同一の接続のいずれかがデータを受信した場合、システムはデータがどの接続に属しているかを判断できません。したがって、この場合、少なくとも 2 つのソケットが接続しようとしているリモート ホストのアドレスとポートは同じであってはなりません。この方法でのみ、システムは 2 つの接続関係を区別し続けることができます。

同じプロトコルを使用して 2 つのソケットを同じローカル アドレスとポートのペアにバインドし、それらを同じ宛先アドレスとポートのペアに接続しようとすると、2 番目のソケットは connect() メソッドを呼び出そうとします。 EADDRINUSE エラーが報告されます。これは、まったく同じ 5 タプルを持つソケットがすでに存在していることを意味します。

マルチキャストアドレス

1対1の通信に使用されるユニキャストアドレスに対して、マルチキャストアドレスは1対多の通信に使用されます。 IPv4 と IPv6 の両方にマルチキャスト アドレスがあります。ただし、IPv4 のマルチキャストがパブリック ネットワークで使用されることはほとんどありません。

マルチキャストアドレスの場合、SO_REUSEADDRの意味がこれまでとは異なります。この場合、SO_REUSEADDR を使用すると、複数のソケットをまったく同じソース ブロードキャスト アドレスとポートのペアにバインドできます。つまり、マルチキャスト アドレスの場合、SO_REUSEADDR はユニキャスト通信の SO_REUSEPORT に相当します。実際、マルチキャストの場合、SO_REUSEADDR と SO_REUSEPORT はまったく同じ効果があります。

FreeBSD/OpenBSD/NetBSD

これらのシステムはすべて、新しいネイティブ BSD システム コードを参照しています。したがって、これら 3 つのシステムは BSD とまったく同じソケット オプションを提供し、これらのオプションの意味はネイティブ BSD とまったく同じです。

MacOS X

MacOSのコアコードの実装は同じです。

iOS

iOS は実際には MacOS X にわずかに変更を加えたものなので、MacOS X で動作するものは iOS でも動作します。

Linux

Linux3.9 より前には、SO_REUSEADDR オプションのみが存在しました。このオプションの機能は基本的に BSD システムでの機能と同じです。しかし、まだ 2 つの重要な違いがあります。

最初の違いは、リスニング (サーバー) 状態の TCP ソケットがワイルドカード IP アドレスと特定のポートにバインドされている場合、これら 2 つのソケットに SO_REUSEADDR オプションが設定されているかどうかに関係なく、他の TCP ソケットをバインドできることです。同じポートにバインドされなくなります。これは、他のソケットが (BSD システムで許可されている) 特定の IP アドレスを使用している場合でも機能しません。非リッスン (クライアント) TCP ソケットにはこの制限はありません。

2 番目の違いは、UDP ソケットの場合、SO_REUSEADDR が BSD の SO_REUSEPORT と同じ機能を持つことです。したがって、2 つの UDP ソケットに SO_REUSEADDR が設定されている場合、それらはまったく同じアドレスとポートのペアのセットにバインドできます。

Linux3.9 では SO_REUSEPORT オプションが追加されています。ポートの下でアドレスをバインドする前に、すべてのソケット (最初のソケットを含む) にこのオプションが設定されている限り、2 つ以上の TCP または UDP、リスニング (サーバー) または非リスニング (クライアント) ソケットをまったく同じアドレスにバインドできます。組み合わせ。同時に、ポートのハイジャックを防ぐために、同じアドレスとポートの組み合わせにバインドしようとするすべてのソケットは、同じユーザー ID を持つプロセスに属している必要があるという特別な制限があります。したがって、あるユーザーが別のユーザーからポートを「盗む」ことはできません。

さらに、SO_REUSEPORT オプションが設定されたソケットの場合、Linux カーネルは、他のシステムにはないいくつかの特別な操作も実行します。同じアドレスとポートの組み合わせにバインドされている UDP ソケットの場合、カーネルはそれらの間で接続を試みます。受信したデータ パケット。同じアドレスとポートの組み合わせにバインドされている TCP リスニング ソケットの場合、カーネルは受信した接続要求 (accept() メソッドを呼び出して取得した要求) をそれらの間で均等に分散しようとします。これは、アドレスの再利用を許可しながら、受信したパケットまたは接続リクエストを同じアドレスとポートの組み合わせに接続されたソケットにランダムに割り当てる他のシステムと比較して、Linux はトラフィック分散の最適化を試みることを意味します。たとえば、単純なサーバー プロセスのいくつかの異なるインスタンスは、SO_REUSEPORT を簡単に使用して単純な負荷分散を実装できます。カーネルはこの負荷分散を担当し、プログラムにとっては完全に無料です。

Android

Android のコア部分は少し変更された Linux カーネルであるため、Linux で動作するものはすべて Android でも動作します。

Windows

Windows には SO_REUSEADDR オプションのみがあります。 Windows でソケットに SO_REUSEADDR を設定すると、BSD でソケットに SO_REUSEPORT と SO_REUSEADDR を同時に設定するのと同じ効果があります。ただし、違いは、バインドされたアドレスを持つ別のソケットに SO_REUSEADDR が設定されていない場合でも、SO_REUSEADDR が設定されているソケットは、別のバインドされたソケットとまったく同じアドレスとポートの組み合わせに常にバインドできることです。この行為はある意味危険であると言えます。それは、あるアプリケーションが、接続されたポートへの別の参照からデータを盗むことができるからです。 Microsoft はこの問題を認識しており、別のソケット オプション SO_EXCLUSIVEADDRUSE を追加しました。ソケットに SO_EXCLUSIVEADDRUSE を設定すると、ソケットがアドレスとポートの組み合わせにバインドされると、SO_REUSEADDR が設定されているかどうかに関係なく、他のソケットは現在のアドレスとポートの組み合わせにバインドできなくなります。

Solaris

Solaris は SunOS の後継です。 SunOS は、ある程度、BSD の以前のバージョンから派生したものでもあります。したがって、Solaris は SO_REUSEADDR のみを提供しており、そのパフォーマンスは基本的に BSD システムと同じです。私の知る限り、SO_REUSEPORT と同じ機能は Solaris システムでは実装できません。これは、Solaris では、2 つのソケットをまったく同じアドレスとポートの組み合わせにバインドできないことを意味します。

Windows と同様に、Solaris もソケットの排他的バインディング オプション SO_EXCLBIND を提供します。ソケットがアドレスをバインドする前にこのオプションを設定すると、他のソケットは、たとえ SO_REUSEADDR が設定されていたとしても、同じアドレスにバインドできなくなります。たとえば、socketA がワイルドカード IP アドレスにバインドされ、socketB が SO_REUSEADDR に設定され、特定の IP アドレスとソケット A と同じポートの組み合わせにバインドされている場合、socketA が SO_EXCLBIND に設定されていない場合、この操作は成功します。それ以外の場合は、この操作は成功します。失敗。

参照:

http://stackoverflow.com/a/14388707/6037083


以上がソケットの so_reuseport と so_reuseaddr の違いについて詳しく説明しますの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。