0. 序文
プロセス通信の概念はもともとスタンドアロン システムから生まれました。各プロセスは独自のアドレス範囲内で実行されるため、2 つのプロセスが相互に通信できるようにするために
プロセスは互いに干渉せず、調和して動作します。オペレーティング システムは、プロセス通信に対応する次のような機能を提供します。
UNIX BSD:パイプ、名前付きパイプ (fifo)、信号 (sinal)
UNIX系:メッセージキュー(message)、共有メモリ(shm)、セマフォ(semaphore)
どちらもローカルのプロセス間通信に限定されます。ネットワーク間通信で解決する必要があるのは、異なるホスト プロセス間の通信問題です (同じマシン上のプロセス間の通信は特殊なケースとみなすことができます)。同じホスト上では、異なるプロセスはプロセス ID によって一意に識別できます。しかし、ネットワーク環境では、各ホストが独自に割り当てたプロセス番号ではプロセスを一意に識別できません。たとえば、ホスト A はあるプロセスにプロセス番号 5 を割り当てますが、プロセス番号 5 はホスト B にも存在する可能性があります。したがって、「プロセス番号 5」という文は意味がありません。次に、オペレーティング システムは多くのネットワーク プロトコルをサポートしており、プロトコルが異なれば動作方法も異なり、アドレス形式も異なります。したがって、ネットワーク間のプロセス通信では、複数のプロトコルを識別するという問題も解決する必要があります。
TCP/IP プロトコル スイートは、この問題の解決に役立ちました。ネットワーク層の「IP アドレス」はネットワーク内のホストを一意に識別でき、トランスポート層の「プロトコル + ポート」はアプリケーション (プロセス) を一意に識別できます。ホストで。このように、トリプレット ( ip アドレス、プロトコル、ポート) を使用してネットワークのプロセスを識別でき、ネットワーク内のプロセス通信ではこのマークを使用して他のプロセスと対話できます。
TCP/IP プロトコルを使用するアプリケーションは通常、アプリケーション プログラミング インターフェイス、つまり UNIX BSD のソケットと UNIX System V (すでに廃止されました) の TLI を使用して、ネットワーク プロセス間の通信を実現します。今のところ、ほとんどすべてのアプリケーションがソケットを使用しており、今はインターネットの時代です。ネットワーク上のプロセス通信は遍在しています。これが、私が「すべてがソケットである」と言う理由です。
1.ソケットソケット
ソケット ソケットは通信ブレークポイントの抽象化です。アプリケーションがファイル記述子を使用してファイルにアクセスするのと同じように、ソケットにアクセスするにはソケット記述子が必要です。ソケット記述子は UNIX システムではファイル記述子として実装されるため、ファイル記述子を処理する多くの関数 (読み取りや書き込みなど) もソケット記述子を呼び出すことができます。
2.ソケット()
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
この関数は、ソケットを一意に識別するソケット記述子を作成するために使用されます。
戻り値:
- 成功した場合は、ソケット記述子を返します。
- エラーが発生した場合は -1 が返されます。
パラメータ:
- ドメイン: ドメイン、通信の特性を決定します。詳細については、セクション2.1を参照してください。
- type: ソケットのタイプを決定し、さらに通信特性を決定します。詳細については、セクション2.2を参照してください。
- プロトコル: 通常は 0 で、指定されたドメインとソケット タイプに従ってデフォルトのプロトコルが選択されることを示します。もちろん、同じドメインおよびソケット タイプに対して複数のプロトコルをサポートする場合は、protocol パラメーターを使用して特定のプロトコルを選択できます。
- AF_INET 通信ドメインでは、ソケット タイプは SOCK_STREAM で、デフォルト プロトコルは TCP です。
- AF_INET 通信ドメインでは、ソケット タイプは SOCK_DGRAM で、デフォルト プロトコルは UDP です。
2.1 パラメータドメイン
ドメインの種類は大きく分けて以下のようになります。
- AF_INET: IPv4 インターネット ドメイン。
- AF_INET6: IPv6 インターネット ドメイン。
- AF_UNIX: UNIX ドメイン。ほとんどのシステムは、AF_UNIX の別名である AF_LOCAL も定義します。
- AF_UNSPEC:指定されていません。任意のドメインを表すことができます。
UNIX ドメイン ソケットは、同じマシン上で実行されているプロセス間の通信に使用されます。インターネット ドメイン ソケットも同じ目的に使用できますが、UNIX ドメイン ソケットの方が効率的です。UNIX ドメインはデータをコピーするだけであり、プロトコル処理の実行、ネットワーク ヘッダーの追加または削除、チェックサムの計算、シーケンス番号の生成、または確認メッセージの送信は行いません。
UNIX ドメイン ソケットは、ストリーム インターフェイスとデータグラム インターフェイスの両方を提供します。UNIX ドメイン データグラム サービスは信頼性が高く、メッセージ損失も配信エラーもありません。UNIX ドメイン ソケットは、ソケットとパイプのハイブリッドです。
相互接続された名前のない UNIX ドメイン ソケットのペアを作成するには、ユーザーはネットワークに面したドメイン ソケット インターフェイスを使用するか、socketpair ()関数を使用できます。
#include <sys/socket.h>
int socketpair(int domain, int type, int protocol, int sockfd[2]);
返回值:若成功返回0,若出错返回-1
2.2 パラメータの種類
type はソケットのタイプを決定するために使用されます。
- SOCK_STREAM:バイト ストリームとも呼ばれ、順序付けされた信頼性の高い双方向接続指向のバイト ストリーム。
- SOCK_DGRAM:データグラムとも呼ばれ、固定長で接続がない信頼性の低いメッセージ配信です。
- SOCK_RAW: IP プロトコルのデータグラム インターフェイス。
- SOCK_SEQPACKET:固定長で順序付けされた信頼性の高い接続指向のメッセージ配信。
データグラム (SOCK_DGRAM) は自己完結型のメッセージです。データグラムの送信は、誰かに手紙を郵送するのと似ています。多くの手紙を郵送することができますが、配達順序は保証されず、一部の手紙は途中で紛失する可能性があります。各手紙には受信者の住所が含まれており、この手紙は他のすべての手紙から独立しています。各手紙は異なる受信者に届く可能性があります。
バイト ストリーム (SOCK_STREAM) の場合、ソケットはバイト ストリーム サービスを提供するため、アプリケーションはメッセージ境界を認識しません。これは、ソケットからデータを読み取るときに、送信プロセスによって書き込まれたすべてのバイトが返されない可能性があることを意味します。最終的には送信されたすべてのデータを取得できますが、それを取得するまでに複数の関数呼び出しが必要になる場合があります。
SOCK_SEQPACKET は SOCK_STREAM ソケットに似ていますが、ソケットはバイト ストリーム サービスではなくパケット サービスに基づいています。これは、SOCK_SEQPACKET ソケットから受信したデータの量が、相手が送信したデータの量と一致していることを意味します。ストリーム制御伝送プロトコル (SCTP) は、インターネット ドメイン上でシーケンシャル パケット サービスを提供します。
SOCK_RAW ソケットは、基礎となるネットワーク層 (インターネット ドメインの IP) に直接アクセスするためのデータグラム インターフェイスを提供します。アプリケーションは、このソケットを使用して独自のプロトコル ヘッダーを構築し、悪意のあるプログラムが組み込みのセキュリティ メカニズムをバイパスしてメッセージを作成するのを防ぎます。
3.バインド()
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t len);
返回值:若成功返回0,若出错返回-1
サーバーの場合、クライアント要求を受信するソケットに既知のアドレスをバインドする必要があります。bind() 関数を使用して、アドレスをソケットにバインドします。
クライアントが指定する必要はありません。システムはポート番号と独自の IP アドレスの組み合わせを自動的に割り当てます。
パラメータ:
- sockfd: socket() を通じて作成されたソケット記述子には、ソケットにバインドされた addr が含まれます。
- addr: sockfd にバインドされるプロトコル アドレスを指します。このアドレス構造は、ソケット作成時のアドレス プロトコル ファミリによって異なります。
- len:対応するアドレスの長さ。
パラメータ addr に注目してください。このアドレス構造は、ソケット作成時のアドレス プロトコル ファミリによって異なります。
アドレスの形式は特定の通信ドメインに関連付けられています。異なる形式のアドレスをソケット関数に渡すことができるようにするために、アドレスは強制的にユニバーサル sockaddr 表現に変換されます。
struct sockaddr {
sa_family_t sa_family; /* address family, AF_xxx */
char sa_data[];
.
.
.
};
たとえば、Linux では、構造は次のように定義されます。
struct sockaddr {
sa_family_t sa_family; /* address family, AF_xxx */
char sa_data[14]; /* 14 bytes of protocol address */
};
freeBSD では、この構造は次のように定義されます。
struct sockaddr {
unsigned char sa_len; /*total length*/
sa_family_t sa_family; /*address family*/
char sa_data[14]; /*variable-length address*/
};
IPv4 インターネット ドメインでは、構造は次のように定義されます。
struct sockaddr_in {
sa_family_t sin_family; /* Address family */
in_port_t sin_port; /* Port number */
struct in_addr sin_addr; /* Internet address */
}
struct in_addr {
uint32_t s_addr; /* address in network byte order */
};
使用できるアドレスにはいくつかの制限があります。
- 指定されたアドレスは、プロセスが実行されているマシン上で有効である必要があり、別のマシンのアドレスを指定することはできません。
- アドレスは、ソケットの作成時にアドレス ファミリでサポートされている形式と一致する必要があります。
- プロセスが対応する権限を持っている (つまり、スーパー ユーザーである) 場合を除き、ポート番号は 1024 以上でなければなりません。
- 通常、アドレスにバインドできるのはソケット ブレークポイントのみですが、一部のプロトコルでは複数のバインドが許可されています。
3.1 ネットワークのバイトオーダーとホストのバイトオーダー
ホスト バイト オーダーは、通常ビッグ エンディアン モードとリトル エンディアン モードと呼ばれるものです。CPU ごとにバイト オーダー タイプが異なります。これらのバイト オーダーは、整数がメモリに格納される順序を指します。これはホスト オーダーと呼ばれます。ビッグエンディアンとリトルエンディアンの標準定義を引用すると、次のようになります。
- リトルエンディアンとは、下位バイトがメモリの下位アドレス端に配置され、上位バイトがメモリの上位アドレス端に配置されることを意味します。
- ビッグエンディアンとは、 上位バイトがメモリの下位アドレス端に配置され、下位バイトがメモリの上位アドレス端に配置されることを意味します。
ネットワーク バイト オーダーでは、4 バイトの 32 ビット値が次の順序で送信されます: 最初に 0 ~ 7 ビット、次に 8 ~ 15 ビット、次に 16 ~ 23 ビット、最後に 24 ~ 31 ビット。この転送順序はビッグエンディアンと呼ばれます。TCP/IP ヘッダー内のすべての 2 進整数は、ネットワーク上で送信されるときにこの順序である必要があるため、ネットワーク バイト オーダーとも呼ばれます。バイトオーダーとは、名前のとおり、1 バイトを超えるデータがメモリに格納される順序であり、1 バイトのデータには順序の問題はありません。
したがって、アドレスをソケットにバインドするときは、まずホストのバイトオーダーをネットワークのバイトオーダーに変換し、ホストのバイトオーダーがネットワークのバイトオーダーと同様にビッグエンディアンを使用すると想定しないでください。この問題が原因で殺人事件も起きています!この問題は、同社のプロジェクト コードに多くの不可解な問題を引き起こしているため、ホストのバイト オーダーについては何も仮定せず、ソケットに割り当てる前に必ずネットワーク バイト オーダーに変換してください。
3.2 getsockname()
#include <sys/socket.h>
int getsockname(int sockfd, struct sockaddr *restrict addr, socklen_t *restrict alenp);
返回值:若成功返回0,若出错返回-1
ソケットにバインドされているアドレスは、関数 getsockname() を呼び出すことで検出できます。
getsockname() を呼び出す前に、alenp をバッファ sockaddr のサイズを指定する整数へのポインタに設定します。戻り時に、この整数は戻りアドレスのサイズに設定されます。アドレスが指定されたバッファ長と一致しない場合、エラーなしで切り捨てられます。現在ソケットにバインドされているアドレスがない場合、結果は未定義です。
4.connect()
接続指向のネットワーク サービス (SOCK_STREAM または SOCK_SEQPACKET) を扱っている場合は、データ交換を開始する前に、サービスを要求するプロセス ソケット (クライアント) とサービスを提供するプロセス ソケット (サーバー) の間の接続を確立する必要があります。接続する。
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t len);
返回值:若成功返回0,若出错返回-1
connect() で指定するアドレスは、通信するサーバーのアドレスです。sockfd がアドレスにバインドされていない場合、connect() はデフォルトのアドレスを呼び出し元にバインドします。
サーバーに接続するとき、さまざまな理由で接続が失敗することがあります。接続先のマシンが稼働していて、サーバーが接続先のアドレスにバインドされており、サーバーのキューに接続を待機する十分なスペースがある必要があります。したがって、アプリケーションは、一時的に変化する条件によって引き起こされる可能性のある connect() によって返されるエラーを処理できなければなりません。
5.リッスン()
#include <sys/socket.h>
int listen(int sockfd, int backlog);
返回值:若成功返回0,若出错返回-1
パラメータ:
- sockfd: 監視されるソケット記述子。
- バックログ: 接続リクエストの数を制限するために使用され、キューがいっぱいになると、システムは過剰な接続リクエストを拒否します。
デフォルトでは、socket() 関数によって作成されたソケットはアクティブ型ですが、listen() によってソケットがパッシブ型に変わり、クライアントの接続要求を待ちます。
6. accept()
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *restrict addr, socklen_t *restrict len);
返回值:若成功返回socket描述符,若出错返回-1
サーバーが listen() リスニング関数を呼び出した後、ソケットは接続要求を受け入れ、accept() を使用して接続要求を取得し、接続を確立できます。
accept() が正常に戻ると、戻り値はconnect() を呼び出したクライアントに接続するために使用されるソケット記述子になります。この新しいソケット記述子(接続ソケットとも呼ばれる) は、元の sockfd (リスニング ソケットとも呼ばれる) と同じソケット タイプとアドレス ファミリを持ちます。
クライアント ID を気にしない場合は、パラメータ addr と len を NULL に設定できます。それ以外の場合は、accept() を呼び出す前に、アドレスを格納するのに十分な大きさのバッファにパラメータ addr を設定し、len を設定する必要があります。バッファ サイズを指す整数へのポインタ。戻るとき、accept() はバッファにクライアントのアドレスを埋め、ポインタ len が指す整数をアドレスのサイズに更新します。
接続が来ない場合、accept() は接続要求が来るまでブロックします。
7. データ送信
- 読み書き()
- sned() /recv()
- sendto() /recvfrom()
- sendmsg() /recvmsg()
ソケットのデータ送信は大きく上記の 4 つに分類され、read() および write() システムコールに加えて、socket は 3 つのグループのデータ送信インターフェイスを提供します。
7.1 send()
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t nbytes, int flags);
返回值:若成功返回发送字节数,若出错返回-1
write() と同様に、send() を使用する場合はソケットを接続する必要があります。
send() が正常に返ったとしても、接続の相手側のプロセスがデータを受信したことを必ずしも意味するわけではありません。保証されているのは、send() が正常に戻ったときに、データがエラーなくネットワークに送信されたことだけです。
send() の戻り値は 3 つあります。
- 0 より大きい: 送信されるデータの長さを示します。
- 0 に等しい: タイムアウトまたはピア ホストがシャットダウンされていることを示します。
- 0未満:異常。
パケット制限の設定をサポートするプロトコルの場合、単一のパケットがそのプロトコルでサポートされる最大サイズを超えると、send() は失敗し、errno を EMSGSIZE に設定します。バイト ストリーム プロトコルの場合、sned() はデータ全体が送信されるまでブロックします。
sned() の 4 番目のパラメーター flags (通常は 0) は、送信されたデータの処理方法を変更するフラグを指定します。
- MSG_DONTROUTE: ローカル データからデータをルーティングしません。
- MSG_DONTWAI: O_NONBLOCK を使用するのと同等の非ブロッキング操作を許可します。
- MSG_EOR: プロトコルがサポートしている場合、これでレコードは終わりです。
- MSG_OOB: プロトコルがサポートしている場合は、帯域外データを送信します。
7.1.1 帯域外データ
帯域外データは、一部の通信プロトコルでサポートされるオプション機能であり、通常のデータよりも優先度の高いデータを送信できるようになります。送信キューにデータが存在する場合でも、コードデータが先に送信されます。
TCP は帯域外データをサポートしますが、UDP はサポートしません。ソケット インターフェイスの帯域外データのサポートは、TCP 帯域外データの特定の実装に大きく影響されます。
7.2 受信()
#include <sys/socket.h>
ssize_t recv(int sockfd, const void *buf, size_t nbytes, int flags);
返回值:若成功返回接收消息字节数,若出错返回-1
recv() は read() に似ていますが、データの受信方法を制御するオプションを指定できます。
- MSG_OOB: プロトコルがサポートしている場合は、帯域外データを受信します。
- MSG_PEEK: 実際にメッセージを削除せずにメッセージの内容を返します。
- MSG_TRUNC: メッセージが切り詰められている場合でも、メッセージの実際の長さを返す必要があります。
- MSG_WAITALL: すべてのデータが利用可能になるまで待機します (SOCK_STREAM タイプの場合のみ)。
SOCK_STREAM タイプのソケットの場合、要求されたデータよりも少ないデータを受信できます。ただし、フラグ MSG_WAITALL はこの動作を防止し、必要なデータがすべて受信されるまで、recv() 関数は戻ることができません。
SOCK_DGRAM および SOCK_SEQPACKET タイプのソケットの場合、これらのメッセージベースのソケット タイプは 1 回の読み取りでメッセージ全体を返すため、MSG_WAITALL フラグによって動作は変わりません。
送信者が shutdown() を呼び出して送信を終了した場合、またはネットワーク プロトコルがデフォルトの順次シャットダウンをサポートしていて送信者がシャットダウンされた場合、すべてのデータが受信されると、recv() は 0 を返します。
7.3 sendto()
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t nbytes, int flags,
const struct sockaddr *destaddr, socklen_t destlen);
返回值:若成功返回发送字节数,若出错返回-1
sendto() は send() に似ていますが、sendto() ではコネクションレス型ソケットでターゲット アドレスを指定できる点が異なります。
接続指向のソケット通信の場合、宛先アドレスは接続内で暗黙的に指定されるため、宛先アドレスは無視されます。コネクションレス型ソケットの場合、connect() を呼び出すときにターゲット アドレスが事前に設定されているか、sendto() を使用してメッセージを送信する別の方法が提供されていない限り、send() は使用できません。
7.4 recvfrom()
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *restrict buf, size_t len, int flags,
struct sockaddr *restrict addr,
socklen_t *restrict addrlen);
返回值:若成功返回接收消息字节数,若出错返回-1
addr が空でない場合、データ送信者のソケット エンドポイント アドレスが含まれます。recvfrom() を呼び出すときは、addr が指すソケット バッファのサイズを含む整数を指すように addrlen パラメータを設定する必要があります。戻ると、この整数はアドレスの実際のバイト サイズに設定されます。
送信者のアドレスを取得できるため、通常、recvfrom() はコネクションレス型ソケットに使用されます。それ以外の場合、recvfrom() は recv() と同等です。
戻り値はrecv()と同じです。
7.5 sendmsg()
#include <sys/socket.h>
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
返回值:若成功返回发送字节数,若出错返回-1
パラメータ:
sockfd: データを送信する sockt fd を指定します。
msg: msghdr 構造体へのポインタ。送信されるメッセージと関連するメタデータが含まれます。
フラグ: フラグ;
msghdr 構造を見てみましょう。
/* Structure describing messages sent by
`sendmsg' and received by `recvmsg'. */
struct msghdr
{
void *msg_name; /* Address to send to/receive from. */
socklen_t msg_namelen; /* Length of address data. */
struct iovec *msg_iov; /* Vector of data to send/receive into. */
size_t msg_iovlen; /* Number of elements in the vector. */
void *msg_control; /* Ancillary data (eg BSD filedesc passing). */
size_t msg_controllen; /* Ancillary data buffer length.
!! The type should be socklen_t but the
definition of the kernel is incompatible
with this. */
int msg_flags; /* Flags on received message. */
};
- msg_name:情報を送受信するソケットアドレスを指す構造体ポインタ。
- msg_namelen:ソケットアドレス構造の長さ。
- msg_iov: iov 配列ポインター。各要素にはバッファーと送信されるデータの長さが含まれます。
- msg_iovlen: iov 配列内の要素の数。
- msg_control:補助データのバッファを指します。
- msg_controllen:補助データ バッファーの長さ。
- msg_flags:フラグビット;
sendmsg()関数は、これらのメンバーを使用して送信するメッセージを記述し、それをターゲットのソケットに送信します。具体的な実装としては、sendmsg 関数は送信するメッセージを複数の部分 (iov 配列の要素) に分割し、1 つずつ送信します。送信プロセス中にさまざまな状況 (ネットワーク遅延、パケット損失など) が発生する可能性がありますが、この時点で、それに応じて flags パラメーターを設定して、目的の効果を達成できます。
7.6 recvmsg()
#include <sys/socket.h>
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
返回值:若成功返回接收消息字节数,若出错返回-1
sendmsg() と同様に、受信したデータを複数のバッファに送信する場合、または補助データを受信する場合は、recvmsg() 関数を使用できます。
構造体 msghdr は、データを受信する入力バッファを指定するために、recvmsg() によって使用されます。パラメータ フラグを設定して、recvmsg のデフォルトの動作を変更できます。戻り時に、msghdr 構造体の msg_flags フィールドは、受信データのさまざまな特性に設定されます。
msg_flags recvmsg() から返されるフラグ:
- MSG_CTRUNC: 制御データは切り捨てられます。
- MSG_DONTWAIT:recvmsg() はノンブロッキング モードです。
- MSG_EOR: レコード終了文字を受信しました。
- MSG_OOB: 帯域外データを受信しました。
- MSG_TRUNC: 一般データは切り捨てられます。
8.close()
サーバーがクライアントとの接続を確立した後、いくつかの読み取りおよび書き込み操作が実行されます。読み取りおよび書き込み操作が完了した後、対応するソケット記述子を閉じる必要があります。たとえば、開いているファイルを操作した後、閉じるには fclose を呼び出す必要があります。開いているファイル。
#include <unistd.h>
int close(int fd);
close TCP ソケットのデフォルトの動作では、ソケットにクローズのマークを付け、すぐに呼び出しプロセスに戻ります。この記述子は呼び出しプロセスでは使用できなくなります。つまり、読み取りまたは書き込みの最初のパラメーターとして使用できなくなります。
注: クローズ操作では、対応するソケット記述子の参照カウントが -1 だけ減少します。参照カウントが 0 の場合にのみ、TCP クライアントはサーバーに終了要求を送信するようにトリガーされます。
9.ソケットでのTCP通信処理
上の図はソケットにおける TCP の通信プロセスを示しており、大きく 4 つの部分に分かれています。
- 初期化フェーズでは、サーバーは、socket() インターフェースを通じてソケット fd を作成し、bind() インターフェースを通じてサーバーのアドレスとポートをバインドし、最後に listen() を通じてソケット fd を監視します。サーバーは正式にリスニング・フェーズに入ります。クライアントはsocket()を使用します)インターフェースはソケットfdを作成します。
- 接続作成フェーズでは、サーバーは listen() の後に accept() を呼び出してクライアント接続をブロックして待機し、クライアントは connect() を介して接続を試みます。
- データ送信段階では、サーバーとクライアントが相互に通信します。
- 通信終了フェーズでは、クライアントとサーバーはウェイビングプロセスに入ります。
9.1 接続を確立するための 3 ウェイ ハンドシェイク
ハンドシェイクの前、サーバーには 2 つの状態があります。
- 初期デフォルトの CLOSED 状態では、connect() の前にクライアントも CLOSED 状態でした。
- listen() が呼び出された後の listen 状態。
クライアントが connect() を呼び出すと、正式に接続作成フェーズに入ります。このプロセスでは 3 つのハンドシェイクが行われます。
- 最初のハンドシェイクでは、クライアントは connect() を呼び出し、syn パケット (シーケンス番号の同期)をサーバー ( syn = j) に送信します。クライアントは SYN_SEND 状態に入り、サーバーからの確認を待ちます。
- 2 回目のハンドシェイクでは、サーバーはクライアントの syn パケットを受信して確認し (ack=j+1)、同時にクライアントに syn パケット (syn=k)、つまり SYN + ACK を送信します。サーバーは SYN_RECV 状態に入ります。
- 3 回目のハンドシェイクでは、クライアントはサーバーから SYN + ACK パケットを受信し、確認パケット (ack=k+1) をサーバーに送信します。パケットの送信後、クライアントとサーバーは ESTABLISHED 状態になり、ハンドシェイクが完了します。 ;
3 ウェイ ハンドシェイクが完了すると、クライアントとサーバーは ESTABLISHED 状態になり、両端間でデータを送信できるようになります。
TCP ハンドシェイク プロセスは、実際には相手に初期シーケンス番号 (ISN) を通知します。このシーケンス番号は、後続のデータ送信の基礎として使用され、送信中に TCP メッセージが混乱しないようにします。
TCP ヘッダーの構造に戻りますが、シーケンス番号と確認応答番号は両方とも 32 ビットを占有するため、seq と ack の値の範囲は 0 ~ 2^32 -1 になります。
seq と ack が 2^32-1 に増加するたびに、再び 0 から始まります。seq の初期値 (ISN) は毎回 0 から始まるわけではないことに注意してください。0 から始まる場合、接続を確立するための TCP スリーウェイ ハンドシェイクの後、クライアントが 30 個のメッセージを送信し、その後クライアントが切断されたと想像してみましょう。したがって、クライアントは再接続し、再び初期シーケンスとして 0 を使用します。このようにすると、2 つのパケットが同じシーケンスを持つことになり、混乱が発生します。
実際、TCP のアプローチは、4 マイクロ秒ごとに ISN に 1 を追加することです。ISN が 2^32-1 に達し、再び 0 から始まると、数時間が経過し、以前の seq=0 レポートのテキストはこのレポートには存在しません。接続することで上記の問題を回避できます。
9.2 4 回手を振ると切断されます
- 初めて手を振るとき、クライアントはサーバーに切断要求 (seq=m) を送信し、これによりクライアントからサーバーへのデータ転送が終了し、クライアントは FIN_WAIT-1 状態になります。m は、クライアントからサーバーに送信された最後のメッセージ セグメントの最後のバイト シーケンス番号 + 1 です。
- 2 回目に手を振った後、サーバーはクライアントの要求を受信し、確認メッセージ (seq=n、ack=m+1) をクライアントに送信し、サーバーは CLOSE_WAIT 状態に入ります。このとき、TCP コネクションは半オープン、半クローズの状態になっており、サーバーがデータを送信すると、クライアントはそれを受信することができます。n は、サーバーからクライアントに送信された最後のメッセージ セグメントの最後のバイト シーケンス番号 + 1 です。
- 3 回目に手を振った後、サーバーは切断確認メッセージ (seq=u、ack=m+1) をクライアントに送信し、サーバーは LAST_ACK 状態に入ります。u は、ハーフオープンおよびハーフクローズ状態でサーバーからクライアントに送信された最後のメッセージセグメントの最後のバイトシーケンス番号 + 1 です。
- 4 回目に手を振った後、クライアントはサーバー切断確認メッセージを受信した後、確認メッセージ (seq=m+1、ack=u+1) をサーバーに送信します。クライアントは TIME_WAIT 状態に入ります。
サーバーがクライアントの ACK を受信すると、CLOSED 状態に入り、TCP 接続を切断します。クライアントは、クライアントとサーバー間の最後の切断確認が到着したことを確認するために、一定期間 TIME_WAIT 状態で待機します (到着しない場合、サーバーはステップ (3) の切断確認メッセージ セグメントをサーバーに再送信します)。クライアントに最後の確認を伝えます。切断は受信されませんでした)。クライアントは、TIME-WAIT プロセス中にサーバーのメッセージ セグメントを再度受信しない場合、CLOSES 状態に入ります。TCP 接続が切断されました。
関連するブログ投稿: