TCPソケットプログラミング

ソケットプログラミング

       TCP / IPプロトコルでは、「IPアドレス+ TCPまたはUDPポート番号」はネットワーク通信のプロセスを一意に識別し、「IPアドレス+ポート番号」はソケットと呼ばれます。

       TCPプロトコルでは、接続を確立する2つのプロセスがそれぞれ識別するソケットを持っており、2つのソケットによって形成されるソケットペアが接続を一意に識別します。「ソケット」自体は「ソケット」を意味し、ネットワーク接続における1対1の関係を説明するために使用されます。

       TCP / IPプロトコル用に設計されたアプリケーション層プログラミングインターフェイスは、ソケットAPIと呼ばれます。

       TCPプロトコルとUDPプロトコルの機能インターフェースを以下に紹介します。

 

TCPソケット

□ビッグエンドスモールエンド

       メモリ内のマルチバイトデータは、メモリアドレスに対してビッグエンディアンとリトルエンディアンに分割され、ディスクファイル内のファイルに対するマルチバイトデータのオフセットアドレスもビッグエンディアンとリトルエンディアンです。ネットワークデータストリームもビッグエンディアンとスモールエンディアンに分けられ、ネットワークデータストリームのアドレスが規定されています。最初に送信されるデータが下位アドレスで、後で送信されるデータが上位アドレスです。

       TCP / IPプロトコルでは、ネットワークデータストリームはビッグエンディアンバイトオーダー、つまり下位アドレス上位バイトである必要があると規定されています。たとえば、前のセクションのUDPセグメント形式では、アドレス0〜1は16桁の送信元ポート番号です。ポート番号が1000(0x3e8)の場合、アドレス0は0x03、アドレス1は0xe8です。最初のアドレスは0、2番目の送信アドレスは1です。ただし、送信ホストがリトルエンディアンのバイトオーダーの場合、これらの16ビットは1000ではなく0x3e8として解釈されます。したがって、途中でエンディアン変換が必要になります。

       ネットワークバイトオーダーとホストバイトオーダーの間で変換するには、次のライブラリ関数を呼び出すことができます。

 

       hはホストを意味します

       nはネットワークを意味します

       lは32ビット長整数を表します

       sは16ビットの短整数を表します。たとえば、htonlは、32ビット長整数をホストバイトオーダーからネットワークバイトオーダーに変換することを意味します。

 

     ソケットアドレスと関連関数のデータ型。ソケットAPIは、さまざまな基盤となるネットワークプロトコルに適した抽象的なネットワークプログラミングインターフェイスです。ただし、さまざまなネットワークプロトコルのアドレス形式は同じではありません。

sockaddrデータ構造:

        

 

       IPv4およびIPv6のアドレス形式はnetinet / in.hで定義され、IPv4アドレスはsockaddr_in構造体で表され、IPv6アドレスはsockaddr_in6構造体で表されます。UNIXドメインソケットのアドレス形式は、sockaddr_un構造体で表されるsys /un.hで定義されています。IPv4、IPv6、およびUNIXDomain Socketのアドレスタイプは、それぞれ定数AF_INET、AF_INET6、AF_UNIXとして定義されています。それらの構造は同じではありませんが、最初は同じです。このようにして、特定のsockaddr構造体の最初のアドレスが取得されている限り、sockaddr構造体の特定のタイプを知らなくても、アドレスタイプフィールドに従って構造体の内容を判別できます。したがって、ソケットAPIは、bind、accept、connect、その他の関数など、さまざまなタイプのsockaddr構造体ポインターをパラメーターとして受け入れることができます。これらの関数のパラメーターは、さまざまなタイプのポインターを受け入れるためにvoid *タイプとして設計する必要がありますが、 sockAPIの実装はANSICで初期に標準化されており、当時はvoid *型がなかったため、これらの関数のパラメーターはstruct sockaddr * typeで表され、パラメーターを渡す前に型変換を強制する必要があります。

         

 

 

       IPv4に基づくソケットネットワークプログラミングでは、sockaddr_inのメンバーstruct in_addrsin_addrは32ビットのIPアドレスを表します。ただし、通常はドット付き10進文字列を使用してIPアドレスを表します。次の関数は、文字列表現とin_addr表現の間で変換できます。

文字列をドット付き10進数に変換する関数:

         

 

TCPプロトコル通信プロセス:

 

       サーバー:socket()、bind()、listen()を呼び出して初期化を完了し、accept()を呼び出してブロックして待機すると、リスニングポートの状態になります。

       クライアント:socket()を呼び出して初期化した後、connect()を呼び出してSYNセグメントを送信し、サーバーが応答するのをブロックします。サーバーはSYN-ACKセグメントで応答し、クライアントはそれを受信した後にconnect()から戻り、同時にACKセグメントで応答します。

       サーバー:クライアントが受信したACKを受信した後、accept()から戻ります。

 

シンプルなTCPネットワークプログラム

tcp_server.c

         

        

        

 

 

 

       プログラムで使用されるソケットAPIは次のとおりです。

       <1>

        

 

       socket()は、ネットワーク通信ポートを開きます。成功すると、open()と同じようにファイル記述子を返します。アプリケーションは、読み取り/書き込みを使用して、ファイルの読み取りや書き込みなど、ネットワーク上でデータを送受信できます。socket()の場合呼び出しは失敗し、-1を返します。IPv4の場合、ファミリパラメータはAF_INETとして指定されます。TCPプロトコルの場合、typeパラメーターはSOCK_STREAMとして指定されます。これは、ストリーム指向の伝送プロトコルを表します。

 

        <2>

        

       bind()の機能は、パラメーターsockfdとmyaddrを一緒にバインドすることです。これにより、ネットワーク通信に使用されるファイル記述子sockfdは、myaddrによって記述されたアドレスとポート番号を監視します。前述のように、struct sockaddr *は一般的なポインタ型です。myaddrパラメータは実際には複数のプロトコルのsockaddr構造体を受け入れることができ、それらの長さは異なるため、構造体の長さを指定するには3番目のパラメータaddrlenが必要です。プログラムのmyaddrパラメータは次のように初期化されます。

local.sin_family = AF_INET;

local.sin_port = htons(_port);

local.sin_addr.s_addr = inet_addr(_ip);

 

       <3>

        

       クライアントが接続を開始すると、サーバーによって呼び出されたaccept()が戻って接続を受け入れます。多数のクライアントが接続を開始し、サーバーが処理するには遅すぎる場合、まだ受け入れていないクライアントは接続中です。待機状態であり、listen()は、sockfdが監視状態にあることを宣言し、最大でバックログクライアントが接続待機状態になることを許可し、さらに接続要求が受信された場合は無視します。listen()は、成功した場合は0を返し、失敗した場合は-1を返します。

 

        <4>

        

       サードパーティのハンドシェイクが完了した後、サーバーはaccept()を呼び出して接続を受け入れます。サーバーがaccept()を呼び出したときにクライアント接続要求がない場合、サーバーはブロックし、クライアント接続が確立されるまで待機します。addrは送信パラメーターであり、accept()が戻ると、クライアントのアドレスとポート番号が送信されます。addrlenパラメーターは着信および発信パラメーター(value-result引数)であり、呼び出し元によって提供されたバッファーaddrの長さは、バッファーオーバーフローの問題を回避するために渡され、クライアントアドレス構造の実際の長さ(存在しない場合があります)です。呼び出し元から提供されたバッファーをいっぱいにします)。addrパラメーターにNULLを渡すと、クライアントのアドレスを気にしないことを意味します。

 

tcp_client.c

        

        

 

 

       クライアントは固定のポート番号を必要としないため、bind()は使用されず、クライアントのポート番号はカーネルによって自動的に割り当てられます。サーバーはbind()を呼び出す必要はありませんが、サーバーがbind()を呼び出さない場合、カーネルは自動的にリスニングポートをサーバーに割り当てます。ポート番号はサーバーが起動するたびに異なり、クライアントはサーバーへの接続に問題があります。

 

        

 

       クライアントは、サーバーに接続するためにconnect()を呼び出す必要があります。connectとbindのパラメーターは同じ形式です。違いは、bindのパラメーターが自身のアドレスであり、connectのパラメーターが他のアドレスであるということです。パーティー。connect()は、成功した場合は0を返し、失敗した場合は-1を返します。

 

       最初にサーバーをコンパイルして実行し、次にクライアントを実行して、サーバーにメッセージを送信します。

        

 

       サーバー応答メッセージ:

        

 

       ここで、Ctrl Cを使用してサーバーを終了し、すぐにサーバーを実行すると、結果は次のようになります。

        バインドエラー:アドレスはすでに使用されています

        
       これは、サーバーアプリケーションプログラムが終了しても、TCPプロトコル層の接続が完全に切断されていないため、同じサーバーポートを再度監視できないためです。ここで、CtrlCを使用してクライアントを終了します。ソケット記述子は、クライアントが終了すると自動的に閉じられ、サーバーのTCP接続は、クライアントから送信されたFINセグメントを受信した後、TIME_WAIT状態になります。

       TCPプロトコルでは、接続をアクティブに閉じるパーティはTIME_WAIT状態であり、2つのMSLを待ってからCLOSED状態に戻る必要があると規定されています。最初にCtrl-Cでサーバーを終了するため、サーバーはアクティブに閉じるパーティです。接続。TIME_WAIT中はまだ使用できません。同じサーバーポートを再度リッスンします。

       netstat -nltpを使用してそのPIDを見つけ、killコマンドを使用してそれを強制終了できます。再バインドできます。

        

 

 

おすすめ

転載: blog.csdn.net/wxt_hillwill/article/details/73723750