Linux ネットワークプログラミングにおけるソケットオプションの実装

Linux ネットワークプログラミングにおけるソケットオプションの実装

ソケットオプション機能

機能: ソケットファイル記述子の属性の読み取りと設定に使用されるメソッド

#include <sys/scoket.h>
int getsockopt ( int sockfd、 int level、 int option_name、 void* option_value、 socklen_t* restrict option_len );
int setsockopt ( int sockfd、 int レベル、 int option_name、 const void* option_value、 socklen_t option_len);

ソケットオプションテーブルは次のとおりです。

getsockopt および setsockopt 関数は、成功した場合は 0 を返し、失敗した場合は -1 を返して errno を設定します。

サーバーの場合、一部のソケット オプションは、listen システム コールを呼び出す前にリスニング ソケットに設定されている場合にのみ有効です。これは、接続ソケットは accept 呼び出しによってのみ返され、listen キューから accept によって受け入れられた接続は、少なくとも TCP 3 ウェイ ハンドシェイクの最初の 2 つの手順を完了しているためです (listen キュー内の接続は少なくとも SYN_RCVD 状態になっているため)。つまり、サーバーは受信した接続に TCP 同期セグメントを送信したことになります。ただし、TCP 最大セグメント オプションなど、一部のソケット オプションは TCP 同期セグメントで設定する必要があります。このような状況に対して、Linux が開発者に提供する解決策は、リスニング ソケットにこれらのソケット オプションを設定することです。そうすると、accept によって返される接続ソケットはこれらのオプションを自動的に継承します。これらのオプションは、SO_DEBUG、SO_DONTROUTE、SO_KEEPALIVE、SO_LINGER、SO_OOBINLINE、SO_RCVBUF、SO_RCVLOWAT、SO_SNDBUF、SO_SNDLOWAT、TCP_MAXSEG、および TCP_NODELAY です。

クライアントの場合、接続呼び出しが正常に返された後に TCP 3 ウェイ ハンドシェイクが完了するため、接続関数を呼び出す前にこれらのソケット オプションを設定する必要があります。

SO_REUSEADDR オプション

以前、TCP 接続の TIME_WAIT 状態について説明し、サーバー プログラムはソケット オプション SO_REUSEADDR を設定することで、TIME_WAIT 状態の接続で占有されているソケット アドレスの使用を強制できることを説明しました。

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <文字列.h>
 
int main( int argc, char* argv[] )
{
  if(argc <= 2)
  {
    printf( "使用法: %s ip_address port_number\n", basename( argv[0] ) );
    1 を返します。
  }
  定数 char* ip = argv[1];
  int ポート = atoi( argv[2] );
 
  int sock = socket( PF_INET, SOCK_STREAM, 0 );
  アサート( sock >= 0 );
  再利用 = 1;
  setsockopt( sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof( 再利用 ) );
 
  構造体 sockaddr_in アドレス;
  bzero( &address, sizeof( address ) );
  アドレス.sin_family = AF_INET;
  inet_pton( AF_INET, ip, &address.sin_addr );
  アドレス.sin_port = htons( ポート );
  int ret = bind( sock, ( struct sockaddr* )&address, sizeof( address ) );
  アサート( ret != -1 );
 
  ret = listen( ソック、 5 );
  アサート( ret != -1 );
 
  構造体 sockaddr_in クライアント;
  socklen_t client_addrlength = sizeof( クライアント );
  int connfd = accept( sock, ( struct sockaddr* )&client, &client_addrlength );
  (接続数 < 0 ) の場合
  {
    printf( "errnoは: %d\n", errno );
  }
  それ以外
  {
    char リモート[INET_ADDRSTRLEN];
    printf( "IP: %s、ポート: %d で接続しました\n", 
      inet_ntop( AF_INET, &client.sin_addr, リモート, INET_ADDRSTRLEN ), ntohs( client.sin_port ) );
    閉じる( connfd );
  }
 
  close( 靴下 );
  0を返します。
}

setsocketopt が設定されると、たとえ sock が TIME_WAIT 状態であっても、それにバインドされたソケット アドレスはすぐに再利用できるようになります。さらに、カーネル パラメータ /proc/sys/net/ipv4/tcp_tw_recycle を変更して TCP 接続が TIME_WAIT 状態にまったく入らず、アプリケーションがローカル ソケット アドレスをすぐに再利用できるようにすることで、閉じたソケットをすばやくリサイクルすることもできます。

SO_RCVBUF および SO_SNDBUF オプション

SO_RCVBUF および SO_SNDBUF オプションは、それぞれ TCP 受信バッファと送信バッファのサイズを表します。ただし、setsockopt を使用して TCP の受信バッファと送信バッファのサイズを設定すると、システムはその値を 2 倍にし、最小値よりも小さくすることはできません。 TCP 受信バッファの最小値は 256 バイト、送信バッファの最小値は 2048 バイトです (ただし、システムによってデフォルトの最小値が異なる場合があります)。さらに、カーネル パラメータ /proc/sys/net/ipv4/tcp_rmem と /proc/sys/net/ipv4/tcp_wmem を直接変更して、TCP 受信バッファと送信バッファに最小サイズ制限がないように強制することもできます。

TCP 送信バッファのクライアント プログラムを変更します。

#include <sys/socket.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <文字列.h>
#include <stdlib.h>
 
#BUFFER_SIZE 512 を定義します
 
int main( int argc, char* argv[] )
{
  if(argc <= 3)
  {
    printf( "使用法: %s ip_address port_number send_bufer_size\n", basename( argv[0] ) );
    1 を返します。
  }
  定数 char* ip = argv[1];
  int ポート = atoi( argv[2] );
 
  構造体 sockaddr_in サーバーアドレス;
  bzero( &server_address, sizeof( server_address ) );
  server_address.sin_family = AF_INET;
  inet_pton( AF_INET, ip, &server_address.sin_addr );
  server_address.sin_port = htons( ポート );
 
  int sock = socket( PF_INET, SOCK_STREAM, 0 );
  アサート( sock >= 0 );
 
  int sendbuf = atoi( argv[3] );
  int len ​​= sizeof( sendbuf );
  setsockopt( sock, SOL_SOCKET, SO_SNDBUF, &sendbuf, sizeof( sendbuf ) );
  getsockopt( sock, SOL_SOCKET, SO_SNDBUF, &sendbuf, ( socklen_t* )&len );
  printf( "設定後の TCP 送信バッファ サイズは %d\n です", sendbuf );
 
  if ( connect( sock, ( struct sockaddr* )&server_address, sizeof( server_address ) ) != -1 )
  {
    char バッファ[ BUFFER_SIZE ];
    memset(バッファ, 'a', BUFFER_SIZE);
    送信( ソック, バッファ, BUFFER_SIZE, 0 );
  }
 
  close( 靴下 );
  0を返します。
}

TCP 受信バッファのサーバー プログラムを変更します。

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <文字列.h>
 
#BUFFER_SIZE 1024 を定義します
 
int main( int argc, char* argv[] )
{
  if(argc <= 3)
  {
    printf( "使用法: %s ip_address port_number receive_buffer_size\n", basename( argv[0] ) );
    1 を返します。
  }
  定数 char* ip = argv[1];
  int ポート = atoi( argv[2] );
 
  構造体 sockaddr_in アドレス;
  bzero( &address, sizeof( address ) );
  アドレス.sin_family = AF_INET;
  inet_pton( AF_INET, ip, &address.sin_addr );
  アドレス.sin_port = htons( ポート );
 
  int sock = socket( PF_INET, SOCK_STREAM, 0 );
  アサート( sock >= 0 );
  int recvbuf = atoi( argv[3] );
  int len ​​= sizeof( recvbuf );
  setsockopt( sock, SOL_SOCKET, SO_RCVBUF, &recvbuf, sizeof( recvbuf ) );
  getsockopt( sock, SOL_SOCKET, SO_RCVBUF, &recvbuf, ( socklen_t* )&len );
  printf( "設定後の受信バッファサイズは%d\n", recvbuf );
 
  int ret = bind( sock, ( struct sockaddr* )&address, sizeof( address ) );
  アサート( ret != -1 );
 
  ret = listen( ソック、 5 );
  アサート( ret != -1 );
 
  構造体 sockaddr_in クライアント;
  socklen_t client_addrlength = sizeof( クライアント );
  int connfd = accept( sock, ( struct sockaddr* )&client, &client_addrlength );
  (接続数 < 0 ) の場合
  {
    printf( "errnoは: %d\n", errno );
  }
  それ以外
  {
    char バッファ[ BUFFER_SIZE ];
    memset(バッファ, '\0', BUFFER_SIZE);
    while( recv( connfd, バッファ, BUFFER_SIZE-1, 0 ) > 0 ){}
    閉じる( connfd );
  }
 
  close( 靴下 );
  0を返します。
}

実行結果:

root@iZbp1anc6yju2dks3nw5j0Z:~/test/socket# ./client 127.0.0.1 12345 2000
設定後のTCP送信バッファサイズは4608です

root@iZbp1anc6yju2dks3nw5j0Z:~/test/socket# ./server 127.0.0.1 12345 50
設定後の受信バッファサイズは2304です

上記で説明したように、setsockopt を使用して TCP の受信バッファと送信バッファのサイズを設定すると、システムはその値を 2 倍にし、その値は最小値よりも小さくなってはなりません。

SO_RCVLOWAT および SO_SNDLOWAT オプション

  • SO_RCVLOWAT オプションと SO_SNDLOWAT オプションは、それぞれ TCP 受信バッファと送信バッファの最低水準点を表します。これらは通常、ソケットが読み取り可能か書き込み可能かを判断するために I/O 多重化システムによって呼び出されます。 TCP 受信バッファ内の読み取り可能なデータの合計量が最低水準点より大きい場合、I/O 多重化システム コールは、対応するソケットからデータを読み取ることができることをアプリケーションに通知します。また、TCP 送信バッファ内の空き領域 (データを書き込むことができる領域) が最低水準点より大きい場合、I/O 多重化システム コールは、対応するソケットにデータを書き込むことができることをアプリケーションに通知します。
  • デフォルトでは、TCP 受信バッファの最低水準点と TCP 送信バッファの最低水準点はどちらも 1 バイトです。

SO_LINGER オプション

SO_LINGER オプションは、TCP 接続を閉じるときに close システム コールの動作を制御するために使用されます。デフォルトでは、close システム コールを使用してソケットを閉じると、close はすぐに戻り、TCP モジュールはソケットに対応する TCP 送信バッファ内の残りのデータを相手に送信する役割を担います。

SO_LINGER オプションの値を設定するときは、次のように定義される linger 型構造体を setsockopt (getsockopt) システム コールに渡す必要があります。

#include <sys/socket.h>
構造体リンガー
{
  int l_onoff; //このオプションをオン(0以外)またはオフ(0)にします int l_linger; //リンガー時間};
  • linger 構造体の 2 つのメンバー変数の値に応じて、close システム コールは次の 3 つの動作のいずれかを生成します。
  • l_onoff は 0 に等しい。この時点では、SO_LINGER オプションは効果がなく、close はデフォルトの動作でソケットを閉じます。
  • l_onoff が 0 でない場合、l_linger は 0 に等しくなります。このとき、close システム コールはすぐに戻り、TCP モジュールは閉じられたソケットに対応する TCP 送信バッファ内の残りのデータを破棄し、リセット セグメントを相手に送信します。したがって、この状況では、サーバーが接続を異常終了する可能性があります。 l_onoff は 0 ではなく、l_linger は 0 より大きいです。このときのクローズの動作は、(1) 閉じられたソケットに対応する TCP 送信バッファに残留データがあるかどうか、(2) ソケットがブロッキングか非ブロッキングか、という 2 つの条件によって決まります。 ブロックされたソケットの場合、TCP モジュールが残りのデータをすべて送信し、相手側から確認を受信するまで、close は l_linger の期間待機します。この期間中に TCP モジュールが残りのデータをすべて送信せず、相手側から確認を取得しない場合、close システム コールは -1 を返し、errno を EWOULDBLOCK に設定します。 ソケットが非ブロッキングの場合、close はすぐに戻ります。このとき、戻り値と errno に基づいて残りのデータが送信されたかどうかを判断する必要があります。

以上がこの記事の全内容です。皆様の勉強のお役に立てれば幸いです。また、123WORDPRESS.COM を応援していただければ幸いです。

以下もご興味があるかもしれません:
  • Linuxネットワークプログラミングで使用されるネットワーク関数の詳細な説明と使用例
  • Linux ネットワーク プログラミング UDP ソケット プログラム例
  • Linux ネットワーク プログラミング ソケット ファイル転送の例
  • Linux ネットワークプログラミング機能の簡単な分析

<<:  Vueは動的コンポーネントを使用してTAB切り替え効果を実現します

>>:  Centos7 のインストールと Mysql5.7 の設定

推薦する

Windows 10 で MySql の解凍バージョンをインストールして構成する方法のチュートリアル

Windows 10 で MySql データベースの解凍バージョンをインストールするステップ 1: ...

Linux プログラムの実行中に動的ライブラリをロードできない場合の解決策

Linux でダイナミック ライブラリをロードできません次のような異常事態が発生した場合./test...

JS 正規マッチングの落とし穴の記録

最近、JS の正規表現マッチングの落とし穴を発見したのですが、その時はあまりにも奇妙だったので、何か...

TypeScript マッピング型の詳細

目次1. マップされた型2. マッピング修飾子3. キーの再マッピング4. さらなる探究序文: Ty...

CentOS に Docker をインストールし、Springboot で Docker をリモート公開する方法

目次1. CentOS7.0へのJDK1.8のインストール2. Dockerのインストール3.Doc...

MySQL 変数の原理と応用例

MySQL ドキュメントでは、MySQL 変数はシステム変数とユーザー変数の 2 つのカテゴリに分類...

Reactフックとzarmコンポーネントライブラリ構成に基づいてh5フォームページを開発するためのサンプルコード

最近、React Hooks を zarm コンポーネント ライブラリと組み合わせて使用​​し、js...

WeChatアプレットが9マスグリッド効果を実現

この記事では、WeChatアプレットの9マスグリッド効果を実現するための具体的なコードを参考までに紹...

CSS の高度な使い方(実戦で活用)

1. ul タグには、Mozilla ではデフォルトでパディング値がありますが、IE ではマージン値...

CentOS7.4 で JDK1.8 をインストールするためのグラフィカル チュートリアル

Linux インストール JDK1.8 手順1. CentOS に独自の openJDK があるかど...

ウェブフロントエンド開発の細部

1 選択タグは閉じられている必要があります <select></select>...

背景のグラデーションと自動フルスクリーンを実現するCSSコード

背景グラデーションと自動フルスクリーンに関する CSS の問題編集長は CSS の開発中に致命的な問...

CentOS 7 で yum を使用して MySQL 5.7.20 をインストールする最も簡単な方法

CentOS7 のデフォルトのデータベースは mariadb ですが、mysql を使っている人も多...

魔法のMySQLデッドロックトラブルシューティング記録

背景MySQL のデッドロックについて言えば、私は以前 MySQL のロックに関する基本的な紹介記事...

HTML チュートリアル: 定義リスト

<br />原文: http://andymao.com/andy/post/104.h...