TCP/IPプロトコルスタックのハートビート、パケットロス再送、接続タイムアウトメカニズム例の詳細説明

皆さんこんにちは。この記事では、TCP/IP プロトコル スタックのハートビートの仕組みやパケットロスの再送の仕組みなどについて、具体的な問題例を組み合わせて詳しく説明し、参考にしていただけます。

1. 問題の概要

ソフトウェアの基礎となるモジュールはネットワークの復旧後に自動的にサーバーに再接続できますが、ネットワークの問題により会議が終了したため、再参加する必要があります。

お客様の特殊なネットワーク動作環境により、ネットワークのジッターや不安定性が頻繁に発生するため、お客様は、会議プロセスが中断されないように、ネットワークが回復した後 60 秒以内に会議を維持する必要があると要求しています。

顧客は、この特別な機能ポイントの実現を強く求めています。プロジェクトは完成に近づき、現在顧客によるトライアル段階にあります。この機能が実装されない場合、プロジェクトは承認されず、顧客は料金を支払いません。

フロントの同僚が現状の問題点やプロジェクトの進捗状況を研究開発部門のリーダーに報告し、研究開発部門は緊急の意見交換会を開催し、60秒間会議を欠席しないように計画を立てました。

これには、2 つの主要なタイプのネットワーク接続が関係します。1 つは制御信号を送信する TCP 接続、もう 1 つはオーディオおよびビデオ ストリームを送信する UDP 接続です。UDP 接続の問題はそれほど大きくなく、主な問題は TCP 接続の切断と再接続であり、以下では主に TCP 接続に関連する問題について説明します。

ネットワークが不安定でセッションが失われた場合、システムの TCPIP プロトコル スタックがネットワークの異常を検出し、システム プロトコル層がネットワークの切断を行った可能性があります。また、ソフトウェア アプリケーション層のハートビート メカニズムがネットワークの異常を検出した可能性もあります。ネットワーク障害が発生し、サーバーとの接続が切断されました。リンク。

システムの TCPIP プロトコル スタック自体によって検出されるネットワーク異常には、TCPIP プロトコル スタック自体のハートビート メカニズムによって検出される場合と、TCP 接続のパケット損失および再送信メカニズムによって検出される場合の 2 つの状況が考えられます。

アプリケーション層のハートビート検出機構については、タイムアウト検出時間を延長することができます。この記事では、TCPIP プロトコル スタックの TCP 接続のハートビート、パケット損失再送信、接続タイムアウト、その他のメカニズムについて主に説明します。

ネットワークの異常を検出した後、最下層は自動的に再接続を開始するか、シグナリングを送信することで自動再接続をトリガーできます。ビジネス モジュールは会議関連のリソースを保存し、解放しません。ネットワークが復元された後も、会議に参加し続けることができます。音声とビデオのコード ストリームを受信し続けることで、会議中の一部の操作を引き続き実行できます。

2. TCPIPプロトコルスタックのハートビート機構

2.1. TCP の ACK メカニズム

TCP リンク確立時の 3 ウェイ ハンドシェイク プロセスは次のとおりです。

写真

 

TCP 接続が信頼できる理由は、データを送信する前に接続を確立する必要があり、データを受信した後、データ パケットを受信したことを示す ACK パケットが相手に復元されるためです。データ送信側では、データ送信後に ACK パケットが受信されない場合、パケット損失再送メカニズムがトリガーされます。

リンク確立時やリンク確立後のデータ送受信にはACKパケットが存在し、TCP/IPプロトコルスタックのハートビートパケットも例外ではありません。

2.2. TCPIPプロトコルスタックのハートビート機構の説明

TCP/IP プロトコル スタックにはデフォルトの TCP ハートビート メカニズムがあり、このハートビート メカニズムはソケット (TCP ソケット) にバインドされており、指定したソケットに対してプロトコル スタックのハートビート検出メカニズムをオンにすることができます。デフォルトでは、プロトコル スタックのハートビート メカニズムはソケットに対して閉じられているため、使用する場合は手動でオンにする必要があります。

Windows では、デフォルトでは 2 時間ごとにハートビート パケットが送信されます。クライアント プログラムがハートビート パケットをサーバーに送信した後、次の 2 つの状況が発生します。

1) ネットワークが正常な場合: サーバーはハートビートパケットを受信するとすぐに ACK パケットを返信し、クライアントは ACK パケットを受信して​​からさらに 2 時間待って次のハートビートパケットを送信します。このうち、ハートビート パケットの送信間隔 keepalivetime は、Windows システムではデフォルトで 2 時間であり、設定可能です。2 時間間隔内にクライアントとサーバーの間でデータのやり取りがあった場合、クライアントはサーバーから ACK パケットを受信します。これはハートビート メカニズムのハートビート パケットとしてもカウントされ、2 時間の間隔は次のようになります。時間をやり直した。

2) ネットワーク異常時:クライアントが送信したハートビートパケットをサーバーが受信できず、ACK を返信できない Windows システムのデフォルトタイムアウトは 1 秒であり、ハートビートパケットは 1 秒後に再送信されます。ハートビートパケットのACKが受信されない場合は、1秒後にハートビートパケットを再送信しますが、それでもハートビートパケットを受信できない場合は、ハートビートパケットを10回送信した時点でシステムの上限に達し、ネットワークが考慮されます。プロトコルスタックに問題があるため、接続は直接切断されます。このうち、ハートビートパケットを送信してACKの受信に失敗した場合のタイムアウト時間はキープアライブインターバルと呼ばれ、Windowsシステムのデフォルトは1秒であり、ハートビートパケットのプローブに応じて受信できなかったACKパケットの再送回数を設定可能です。 Windows システムでは固定されており、10 回、構成できません。

そのため、TCP/IPプロトコルスタックのハートビート機構でもネットワークの異常を検出できますが、ハートビート送信後、相手からの応答を待っている間にネットワーク異常が発生しない限り、デフォルトの構成では検出に時間がかかる場合があります。この場合、ハートビート パケットの再送信後に ACK 応答が受信されない場合、プロトコル スタックはネットワークに障害があると判断し、接続をアクティブに閉じます。

2.3. TCP/IP プロトコルスタックのデフォルトのハートビートパラメータを変更する

TCP/IP プロトコル スタックのデフォルトのハートビート メカニズムでは、システムのプロトコル スタック全体のハートビート監視は有効になりませんが、特定のソケットに対して有効になります。

ハートビート メカニズムをオンにした後、ハートビートの時間パラメータを変更することもできます。コードの観点から見ると、まず setsockopt を呼び出してターゲット ソケットのハートビート監視メカニズムを有効にし、次に WSAIoctl を呼び出してハートビート検出のデフォルトの時間パラメーターを変更します。関連するコードは次のとおりです。

SOCKET ソケット;
// ......(中间代省略)

int optval = 1;
int nRet = setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (const char *)&optval,
sizeof(optval));
if (nRet != 0)
が戻る;

tcp_キープアライブアライブ;
生きている.onoff = TRUE;
生きている.キープアライブ時間 = 10*1000;
live.keepaliveinterval = 2*1000;

DWORD dwBytesRet = 0;
nRet = WSAIoctl(ソケット、SIO_KEEPALIVE_VALS、&alive、sizeof(alive)、NULL、0、
&dwBytesRet、NULL、NULL);
if (nRet != 0)
が戻る;

上記のコードからわかるように、setsockopt 関数が最初に呼び出され、SO_KEEPALIVE パラメータが渡され、TCP 接続のハートビート スイッチがオンになります。このとき、ハートビート パラメータにはシステムのデフォルトのハートビート パラメータ値が使用されます。

次に、WSAIoCtrl 関数を呼び出し、SIO_KEEPALIVE_VALS パラメータを渡し、設定された時間値を含むハートビート パラメータ構造体を渡します。

以下は、ハートビート パラメータ構造体 tcp_keepalive の詳細な説明です: (Windows システムを例に挙げます)

1) keepalivetime: デフォルトでは、ハートビート キープアライブ パケットは 2 時間ごとに送信されます。たとえば、最初のキープアライブ パケットを送信した後、次のキープアライブ パケットは 2 時間の間隔後に送信されます。この期間中にデータのやり取りがある場合、それは有効なキープアライブ パケットとみなされます。この期間中はキープアライブ パケットは送信されません。次のキープアライブ パケットの送信間隔は、その時刻から再び 0 から始まります。送受信された最後のデータ部分。

2) keepaliveinterval: keep-alive パケットを送信した後、ピアから ack を受信しない場合のタイムアウトはデフォルトで 1 秒です。ピアとのネットワークに問題があるとします。最初のキープアライブ パケットをピアに送信します。1 秒以内にピアから ACK が受信されない場合は、2 番目のキープアライブ パケットを送信します。キープアライブ パケットは存在しません。 1 秒以内にピアから ACK 応答を受信し、次のキープアライブ パケットを送信します...10 番目のキープアライブ パケットを送信した後、1 秒以内に ACK 応答が受信されない場合、検出回数の上限は10 個のキープアライブ パケットの送信に達すると、ネットワークに問題があると見なされます。

3) プローブ検出数: Windows システムのプローブ数は 10 回に固定されており、変更できません。

ハートビート メカニズムによって検出されたネットワーク異常についての MSDN の説明は次のとおりです。

キープアライブの結果として接続が切断された場合、ソケット上で進行中の呼び出しに対してエラー コード WSAENETRESET が返され、後続の呼び出しは WSAENOTCONN で失敗します。

キープアライブ数が上限に達し、接続が破棄されるため、呼び出されるすべてのソケット インターフェイスは WSAENETRESET エラー コードを返し、その後のソケット API 関数の呼び出しは WSAENOTCONN を返します。

3. libwebsockets オープン ソース ライブラリのハートビート メカニズムは、TCPIP プロトコル スタックのハートビート メカニズムを使用します。

以前、当社の製品が WebSocket を使用していたとき、ハートビート メカニズムが設定されておらず、ネットワーク デバイスによって長時間の TCP 接続が理由もなく解放されるという問題が発生しました。

クライアント プログラムがログインすると、特定の企業の登録サーバーに接続し、長い WebSocket 接続を確立します。この長い接続は常に維持されており、この接続はこの業務モジュールの業務を利用する場合にのみ使用され、この接続上でデータのやり取りが行われます。ソフトウェアにログインした後、ビジネス モジュールが操作されていない場合、この長い接続はアイドル状態のままになります。つまり、この接続ではデータのやり取りはありません。

その結果、あるテスト中に問題が発生し、調査の結果、長時間データのやりとりがなかったために、この長い接続が中間ネットワークデバイスによって切断されたことが判明しました。その後、この問題を解決するために、WebSocket ライブラリの初期化時にハートビート パラメータを設定しました。これにより、上記の長い WebSocket 接続がアイドル状態のときにハートビート パケットを実行できるようになり、長い接続が閉じられないことが保証されます。長期間データが実行されていないため、問題はありません。

lws_create_context インターフェースを呼び出して WebSocket セッション コンテキストを作成する場合、インターフェースの構造パラメーター lws_context_creation_info には、ハートビート パラメーターを設定するためのフィールドがあります。

/** 
* struct lws_context_creation_info - コンテキストを作成するパラメータ
* 
* これは仮想ホストの作成にも使用されます.... LWS_SERVER_OPTION_EXPLICIT_VHOSTS * が指定されていない場合、下位互換性のために、 
* からの情報を使用してコンテキスト作成時
に 1 つの仮想ホストが作成されますこの構造体。
* * LWS_SERVER_OPTION_EXPLICIT_VHOSTS が指定されている場合、仮想ホスト
は * コンテキストと同時に
作成されず、後で作成されることが期待されます。
* 
* @port: VHOST: リッスンするポート... CONTEXT_PORT_NO_LISTEN を使用すると、 * 任意の
ポートでのリッスンを抑制できます。これは、
* WebSocket サーバーをまったく実行しておらず、
* 単にクライアントとして使用している場合に必要なことです
* @iface: VHOST: NULL (待機ソケットをすべてのインターフェイスにバインドする場合)、または
* インターフェイス名 (例: "eth2") 
* オプションで LWS_SERVER_OPTION_UNIX_SOCK を指定する場合、このメンバーは
* UNIX ドメイン ソケットのパス名です。
* ソケット名の前に @ シンボルを追加することで、抽象名前空間でUNIX ドメイン * ソケットを使用できます
* @protocols: VHOST: サポートされているプロトコルと、
* それぞれのプロトコル固有のコールバックをリストする構造体の配列。リストは、
NULL コールバック ポインタを持つ * エントリで終了します。
* owning_server メンバーを記述するため、これは const ではありません。
* @extensions: VHOST: NULL または * --without-extensions をリストする lws_extension 構造体の配列。 * @extensions: VHOST: NULL または
このコンテキストがサポートする拡張機能で設定した場合
。ここでは NULL を指定する必要があります。
* @token_limits: CONTEXT: NULL または struct lws_token_limits ポインター。
* 可能な各 WSI_TOKEN_*** のトークン長制限を使用して初期化されます。 * @ssl_cert_filepath: VHOST: libwebsockets が ssl を使用するようにコンパイルされており、 
* SSL を使用してリッスンし
たい場合、 * サーバー証明書を取得するファイルパスに設定します
。それ以外の場合は暗号化されていない場合は NULL 
* @ssl_private_key_filepath: VHOST: SSL モードが必要な場合は秘密キーへのファイルパス。
* これが NULL に設定されているが、sll_cert_filepath が設定されている場合、
* OPENSSL_CONTEXT_REQUIRES_PRIVATE_KEY コールバックが呼び出され、 * openSSL
ライブラリ呼び出し
を介して秘密キーを直接設定できるようになります *
* @ssl_ca_filepath: VHOST: CA 証明書ファイルパスまたは NULL  
* @ssl_cipher_list: VHOST: 有効な暗号のリスト使用する(例:
* "RC4-MD5:RC4-SHA:AES128-SHA:AES256-SHA:HIGH:!DSS:!aNULL" * または、
NULL のままにして「DEFAULT」を取得することもできます
* @http_proxy_address: VHOST: NULL 以外の場合、指定されたアドレス経由でプロキシを試みます。
* プロキシ認証が必要な場合は、形式を使用します。
* "username:password@server:port" 
* @http_proxy_port: VHOST: http_proxy_address が NULL 以外の場合、このポートを
* アドレスで使用します
。 * @gid: CONTEXT: 変更するグループ ID待機ソケットを設定した後、または -1。
* @uid: CONTEXT: 待機ソケットの設定後に変更するユーザー ID、または -1。
* @options: VHOST + CONTEXT: 0、または LWS_SERVER_OPTION_... ビットフィールド
* @user: CONTEXT: コンテキスト経由で回復できるオプションのユーザー ポインター
* lws_context_user を使用するポインター
* @ka_time: CONTEXT: キープアライブなしの場合は 0、
* すべての libwebsocket ソケット、クライアントまたはサーバー
* @ka_probes: CONTEXT: ka_time がゼロ以外の場合、タイムアウトが経過した後、
* 応答の取得を試行する回数
* あきらめて接続を強制終了する前のピア* @ka_interval: コンテキスト: ka_time がゼロ以外の場合、各 ka_probes 
* 試行
前に待機する時間
* @provided_client_ssl_ctx: コンテキスト: null 以外の場合、libwebsockets ssl 
* 実装をスワップアウトするprovided_ssl_ctx によって提供されます。
*このオプションが選択されている場合、
Libwebsockets は * コンテキストを解放する責任を負いません。* @max_http_header_data: コンテキスト: 
http リクエストで *
処理できるヘッダー ペイロードの最大量(認識されないヘッダー ペイロードはドロップされます) 
* @max_http_header_pool: CONTEXT: * 同時に処理できる http ヘッダーを持つ接続の最大数
(対応するメモリは
* コンテキストの存続期間中に割り当てられます)。プールが
* ビジーな場合、新しい着信接続は * 1 つが空くまで受け入れを待つ必要があります
* @count_threads: CONTEXT: 配列内に作成するコンテキストの数、0 = 1 
* @fd_limit_per_thread: CONTEXT: 非ゼロは、各サービス スレッドをこれに制限することを意味します
* fd の数、0 は、プロセス fd を分割するデフォルトを意味します
* それ以外の場合は、デフォルトのタイムアウトが使用されます。
* @ecdh_curve: VHOST: NULL の場合、デフォルトでサーバーを「prime256v1」で初期化します 
* 制限を で割るデフォルトを意味します。スレッドの数。
* @timeout_secs: VHOST: 
* ライブラリ内のネットワーク ラウンドトリップを伴うさまざまなプロセスは、タイムアウトによって永久にハングアップすることから保護されています。* ゼロ以外の場合
、このメンバーにより、使用されるタイムアウトを秒単位で設定できます。
* @vhost_name: VHOST: 仮想ホストの名前。
* サイトにアクセスするために使用される外部 DNS 名と一致する必要があります
。* ホスト: ヘッダーおよび/または SSL の SNI 名と一致するために使用される「warmcat.com」など。
* @plugin_dirs: CONTEXT: NULL、または NULL で終了するディレクトリの配列
* コンテキスト作成時に lws プロトコル プラグインをスキャン
* @pvo: VHOST: vhost ごとのオプションのリンク リストへのポインター
* プロトコルにアクセスできるオプション
* @keepalive_timeout : VHOST: (デフォルト = 0 = 60 秒) リモートを許可する秒数
* クライアントからアイドル状態の HTTP/1.1 接続を保持します
* @log_filepath: VHOST: ログを追加するファイルパス...これは
* 初期権限が削除される前に開かれます
* @mounts: VHOST: この仮想ホストのマウントのオプションのリンクされたリスト
* @server_string: CONTEXT: サーバーを識別するために HTTP ヘッダーで使用される文字列
* ソフトウェア、NULL の場合は「libwebsockets」。
*/ 
struct lws_context_creation_info { 
int ポート; /* VH */ 
const char *iface; /* VH */ 
const struct lws_protocols *プロトコル; /* VH */ 
const struct lws_extension *extensions; /* VH */ 
const struct lws_token_limits *token_limits; /* コンテキスト */ 
const char *ssl_private_key_password; /* VH */ 
const char *ssl_cert_filepath; /* VH */ 
const char *ssl_private_key_filepath; /* VH */  
const char *ssl_ca_filepath; /* VH */
const char *ssl_cipher_list; /* VH */ 
const char *http_proxy_address; /* VH */ 
unsigned int http_proxy_port; /* VH */ 
int gid; /* コンテクスト */ 
int uid; /* コンテキスト */ 
unsigned int オプション; /* VH + コンテキスト */ 
void *user; /* コンテキスト */ 
int ka_time; /* コンテキスト */ 
int ka_probes; /* コンテキスト */ 
int ka_interval; /* コンテキスト */ 
#ifdef LWS_OPENSSL_SUPPORT 
SSL_CTX *provided_client_ssl_ctx; /* context */ 
#else /* どちらの方法でも構造レイアウトを維持します */ 
void *provided_client_ssl_ctx; 
#endif 
short max_http_header_data; /* コンテキスト */ 
short max_http_header_pool; /* context */ 
unsigned int count_threads; /* コンテキスト */ 
unsigned int fd_limit_per_thread; /* コンテクスト */
unsigned int timeout_secs; /* VH */ 
const char *ecdh_curve; /* VH */  
const char *vhost_name; /* VH */ 
const char * const *plugin_dirs; /* コンテクスト */
const struct lws_protocol_vhost_options *pvo; /* VH */ 
int keepalive_timeout; /* VH */ 
const char *log_filepath; /* VH */ 
const struct lws_http_mount *マウント; /* VH */ 
const char *server_string; /* context */ 
/* ここのすぐ上に新しいものを追加します ---^ 
* これは ABI の一部です。不必要に互換性を壊さないでください
* 
* 以下は、
上に追加された新しいメンバーを含む以降のライブラリ バージョンで 0 が表示されるようにするためのものです(デフォルト) アプリが
* 新しいヘッダーに対してビルドされていない場合でも。
*/ 
void *_unused[8]; 
};

3 つのフィールド ka_time、ka_probes、ka_interval はハートビート関連の設定パラメーターです。WebSocket コンテキストを初期化するコードは次のとおりです。

static lws_context* CreateContext() 
{ 
lws_set_log_level( 0xFF, NULL ); 
lws_context* plcContext = NULL; 

lws_context_creation_info tCreateinfo; 
memset(&tCreateinfo, 0, sizeof tCreateinfo); 

tCreateinfo.port = CONTEXT_PORT_NO_LISTEN; 
tCreateinfo.protocols = プロトコル; 
tCreateinfo.ka_time = LWS_TCP_KEEPALIVE_TIME; 
tCreateinfo.ka_interval = LWS_TCP_KEEPALIVE_INTERVAL; 
tCreateinfo.ka_probes = LWS_TCP_KEEPALIVE_PROBES; 
tCreateinfo.options = LWS_SERVER_OPTION_DISABLE_IPV6; 

plcContext = lws_create_context(&tCreateinfo); 

plcContext を返します。
}

libwebsockets オープン ソース ライブラリ コードを参照すると、ここで設定されたハートビートは、以下に示すように、TCPIP プロトコル スタックのハートビート メカニズムを使用していることがわかります。

LWS_VISIBLE int 
lws_plat_set_socket_options(struct lws_vhost *vhost, lws_sockfd_type fd) 
{ 
int optval = 1; 
int optlen = sizeof(optval); 
u_long optl = 1; 
DWORD dwBytesRet; 
構造体 tcp_keepalive アライブ; 
intプロトンbr; 
#ifndef _WIN32_WCE 
struct protoent *tcp_proto; 
#endif 

if (vhost->ka_time) { 
/* このソケットでキープアライブを有効にする */ 
// 先调用setsockopt打开発信心跳包(設置)选项
optval = 1; 
if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, 
(const char *)&optval, optlen) < 0) 
1 を返します。

生きている.onoff = TRUE; 
live.keepalivetime = vhost->ka_time*1000; 
live.keepaliveinterval = vhost->ka_interval*1000;

if (WSAIoctl(fd, SIO_KEEPALIVE_VALS, &alive, sizeof(alive), 
NULL, 0, &dwBytesRet, NULL, NULL)) は
1 を返します。
} 

/* Nagle を無効にする */ 
optval = 1; 
#ifndef _WIN32_WCE 
tcp_proto = getprotobyname("TCP"); 
if (!tcp_proto) { 
lwsl_err("getprotobyname() がエラー %d\n" で失敗しました。LWS_ERRNO); 
1 を返します。
protonbr 
= tcp_proto->p_proto; 
#else
プロトンbr = 6; 
#endif 

setsockopt(fd, protonbr, TCP_NODELAY, (const char *)&optval, optlen); 

/* ノンブロッキングです... */ 
ioctlsocket(fd, FIONBIO, &optl); 

0を返します。
}

4. TCPIP パケット損失再送メカニズム

ネットワーク障害が発生し、クライアントとサーバー間で TCP データ対話が進行中で、クライアントがデータ パケットをサーバーに送信した後、ネットワーク障害によりサーバーから ACK パケットを受信できない場合、クライアントの TCP パケット損失再送がトリガーされ、パケットロスが発生しますが、再送メカニズムにより、ネットワークに異常があるかどうかを判断することもできます。

TCP 接続の場合、クライアントがサーバーにデータを送信した後、サーバーから ACK パケットを受信しない場合、パケット損失と再送信がトリガーされます。各再送の時間間隔は 2 倍になり、再送回数がシステムの上限 (Windows のデフォルトの上限は 5 回、Linux のデフォルトの上限は 15 回) に達すると、プロトコル スタックは再送回数がシステムの上限に達したと判断します。ネットワークに障害が発生したため、対応するデータを直接転送します。接続は閉じられています。         

そのため、ネットワーク障害時にデータのやりとりがあった場合、プロトコルスタックが数十秒以内にネットワークの異常を検知し、直接接続を切断します。パケット損失再送メカニズムの詳細な説明は次のとおりです。

写真

 

写真

 

パケット損失の再送信メカニズムについては、PC にネットワーク ケーブルを抜き差しすることで確認することも、Wireshark を使用してパケットをキャプチャすることもできます。ネットワーク ケーブルを素早く抜き差しすると (最初にネットワーク ケーブルを抜き、数秒待ってから差し込む)、サーバーに送信された操作指示はパケットの損失と再送信によりデータを受信します。

5. ノンブロッキングソケットと選択インターフェースを使用して、接続接続のタイムアウト制御を実装します。

5.1. MSDN の接続および選択インターフェイスの説明

TCP ソケットの場合は、ソケット関数 connect を呼び出して TCP 接続を確立する必要があります。まず、Microsoft MSDN でソケット インターフェイス接続の説明を見てみましょう。

写真

写真

写真

写真

 

ブロッキングソケットでは、戻り値は接続試行の成功または失敗を示します。

ソケットをブロックする場合、connect の戻り値を使用して、接続が成功したかどうかを判断できます。0 が返された場合は、接続が成功したことを示します。

ノンブロッキングソケットでは、接続試行をすぐに完了できません。この場合、connect は SOCKET_ERROR を返し、WSAGetLastError は WSAEWOULDBLOCK を返します。この場合、考えられるシナリオは 3 つあります。

select 関数を使用して、ソケットが書き込み可能かどうかを確認して接続要求の完了を判断します。

非グループソケットの場合、接続呼び出しはすぐに戻りますが、接続操作はまだ完了していません。接続は SOCKET_ERROR を返します。非ブロッキング ソケットの場合、SOCKET_ERROR が返されても失敗を意味しません。接続関数の実行後に WSAGetLastError を呼び出して LastError 値を取得する必要があります。通常、WSAGetLastError はこの時点で WSAEWOULDBLOCK を返します。

写真

 

接続が進行中であることを示します。select インターフェイスを使用して、ソケットが書き込み可能かどうか (ソケットが writefds コレクションにあるかどうか) を確認できます。書き込み可能であれば、接続は成功です。ソケットが例外fdsコレクションにある場合は、以下に示すように、接続で例外が発生したことを意味します。

写真

 

5.2. ノンブロッキングソケットを使用し、接続タイムアウトの制御を選択する

Windows でソケットをブロックする場合、リモート IP とポートに到達できない場合は、75 秒間のブロック後に SOCKET_ERROR が返され、接続が失敗したことを示します。したがって、リモート IP とポートが接続できるかどうかをテストするときは、ブロッキング ソケットではなく非ブロッキング ソケットを使用し、select を呼び出して接続タイムアウトを制御する select を介して接続タイムアウトを追加します。

select 関数はタイムアウトのため 0 を返しますが、エラーが発生した場合は SOCKET_ERROR を返すため、判定の際には select の戻り値を判断する必要があります。0 以下の場合は接続に失敗し、ソケットが切断されます。すぐに閉じられます。

select 戻り値が 0 より大きい場合、戻り値は、接続が成功したソケットなど、準備ができているソケットの数です。ソケットが書き込み可能なコレクション writefds 内にあるかどうかを確認し、このコレクション内にある場合、接続は成功です。

MSDN の関連記述によると、connect のタイムアウト制御の実装方法はおおよそわかります。関連するコードは次のとおりです。

bool ConnectDevice( char* pszIP, int nPort ) 
{ 
// TCP ソケット
  SOCKET を作成 connSock =ソケット(AF_INET, SOCK_STREAM, 0); 
if (connSock == INVALID_SOCKET) 
{ 
return false; 
} // IP とポート
SOCKADDR_IN devAddr

を入力します。 
memset(&devAddr, 0, sizeof(SOCKADDR_IN)); 
devAddr.sin_family = AF_INET; 
devAddr.sin_port = htons(nPort); 
devAddr.sin_addr.s_addr = inet_addr(pszIP); 
// ソケットを非ブロッキングに設定し、準備します以下の選択
unsigned long ulnoblock = 1; 
ioctlsocket(connSock, FIONBIO, &ulnoblock); 
// 接続を開始し、インターフェースは直ちに
connect(connSock, (sockaddr*)&devAddr, sizeof(devAddr)); 
FD_SET writefds ; 
FD_ZERO(&writefds) ;



FD_SET(connSock, &writefds);

//接続タイムアウトを 1 秒に設定します timeval 
tv; 
tv.tv_sec = 1; //タイムアウト 1 秒
tv.tv_usec = 0; 
// select 関数は、準備ができていて
fd_set に含まれている //ソケット ハンドルの合計数を返します構造体、制限時間が経過した場合は 0、エラーが発生した場合は SOCKET_ERROR(-1) if 
(select(0, NULL, &writefds, NULL, &tv) <= 0) 
{ 
closesocket(connSock); 
return false; //タイムアウト接続せずに終了します
} 

ulnoblock = 0; 
ioctlsocket(connSock, FIONBIO, &ulnoblock); 

closesocket(connSock); 
return true; 
}
 
 

おすすめ

転載: blog.csdn.net/liuxing__jacker/article/details/132944195