この記事では、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 socket;
  // ......(中间代码省略)
 
  int optval = 1;
  int nRet = setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (const char *)&optval, 
                                  sizeof(optval));
  if (nRet != 0)
       return;
 
  tcp_keepalive alive;
  alive.onoff = TRUE;
  alive.keepalivetime = 10*1000;
  alive.keepaliveinterval = 2*1000;
 
  DWORD dwBytesRet = 0;
  nRet = WSAIoctl(socket, SIO_KEEPALIVE_VALS, &alive, sizeof(alive), NULL, 0, 
                            &dwBytesRet, NULL, NULL);
  if (nRet != 0)
      return;

上記のコードからわかるように、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 - parameters to create context with
 *
 * This is also used to create vhosts.... if LWS_SERVER_OPTION_EXPLICIT_VHOSTS
 * is not given, then for backwards compatibility one vhost is created at
 * context-creation time using the info from this struct.
 *
 * If LWS_SERVER_OPTION_EXPLICIT_VHOSTS is given, then no vhosts are created
 * at the same time as the context, they are expected to be created afterwards.
 *
 * @port:	VHOST: Port to listen on... you can use CONTEXT_PORT_NO_LISTEN to
 *		suppress listening on any port, that's what you want if you are
 *		not running a websocket server at all but just using it as a
 *		client
 * @iface:	VHOST: NULL to bind the listen socket to all interfaces, or the
 *		interface name, eg, "eth2"
 *		If options specifies LWS_SERVER_OPTION_UNIX_SOCK, this member is
 *		the pathname of a UNIX domain socket. you can use the UNIX domain
 *		sockets in abstract namespace, by prepending an @ symbole to the
 *		socket name.
 * @protocols:	VHOST: Array of structures listing supported protocols and a protocol-
 *		specific callback for each one.  The list is ended with an
 *		entry that has a NULL callback pointer.
 *		It's not const because we write the owning_server member
 * @extensions: VHOST: NULL or array of lws_extension structs listing the
 *		extensions this context supports.  If you configured with
 *		--without-extensions, you should give NULL here.
 * @token_limits: CONTEXT: NULL or struct lws_token_limits pointer which is initialized
 *		with a token length limit for each possible WSI_TOKEN_***
 * @ssl_cert_filepath:	VHOST: If libwebsockets was compiled to use ssl, and you want
 *			to listen using SSL, set to the filepath to fetch the
 *			server cert from, otherwise NULL for unencrypted
 * @ssl_private_key_filepath: VHOST: filepath to private key if wanting SSL mode;
 *			if this is set to NULL but sll_cert_filepath is set, the
 *			OPENSSL_CONTEXT_REQUIRES_PRIVATE_KEY callback is called
 *			to allow setting of the private key directly via openSSL
 *			library calls
 * @ssl_ca_filepath: VHOST: CA certificate filepath or NULL
 * @ssl_cipher_list:	VHOST: List of valid ciphers to use (eg,
 * 			"RC4-MD5:RC4-SHA:AES128-SHA:AES256-SHA:HIGH:!DSS:!aNULL"
 * 			or you can leave it as NULL to get "DEFAULT"
 * @http_proxy_address: VHOST: If non-NULL, attempts to proxy via the given address.
 *			If proxy auth is required, use format
 *			"username:password@server:port"
 * @http_proxy_port:	VHOST: If http_proxy_address was non-NULL, uses this port at
 * 			the address
 * @gid:	CONTEXT: group id to change to after setting listen socket, or -1.
 * @uid:	CONTEXT: user id to change to after setting listen socket, or -1.
 * @options:	VHOST + CONTEXT: 0, or LWS_SERVER_OPTION_... bitfields
 * @user:	CONTEXT: optional user pointer that can be recovered via the context
 *		pointer using lws_context_user
 * @ka_time:	CONTEXT: 0 for no keepalive, otherwise apply this keepalive timeout to
 *		all libwebsocket sockets, client or server
 * @ka_probes:	CONTEXT: if ka_time was nonzero, after the timeout expires how many
 *		times to try to get a response from the peer before giving up
 *		and killing the connection
 * @ka_interval: CONTEXT: if ka_time was nonzero, how long to wait before each ka_probes
 *		attempt
 * @provided_client_ssl_ctx: CONTEXT: If non-null, swap out libwebsockets ssl
 *		implementation for the one provided by provided_ssl_ctx.
 *		Libwebsockets no longer is responsible for freeing the context
 *		if this option is selected.
 * @max_http_header_data: CONTEXT: The max amount of header payload that can be handled
 *		in an http request (unrecognized header payload is dropped)
 * @max_http_header_pool: CONTEXT: The max number of connections with http headers that
 *		can be processed simultaneously (the corresponding memory is
 *		allocated for the lifetime of the context).  If the pool is
 *		busy new incoming connections must wait for accept until one
 *		becomes free.
 * @count_threads: CONTEXT: how many contexts to create in an array, 0 = 1
 * @fd_limit_per_thread: CONTEXT: nonzero means restrict each service thread to this
 *		many fds, 0 means the default which is divide the process fd
 *		limit by the number of threads.
 * @timeout_secs: VHOST: various processes involving network roundtrips in the
 *		library are protected from hanging forever by timeouts.  If
 *		nonzero, this member lets you set the timeout used in seconds.
 *		Otherwise a default timeout is used.
 * @ecdh_curve: VHOST: if NULL, defaults to initializing server with "prime256v1"
 * @vhost_name: VHOST: name of vhost, must match external DNS name used to
 *		access the site, like "warmcat.com" as it's used to match
 *		Host: header and / or SNI name for SSL.
 * @plugin_dirs: CONTEXT: NULL, or NULL-terminated array of directories to
 *		scan for lws protocol plugins at context creation time
 * @pvo:	VHOST: pointer to optional linked list of per-vhost
 *		options made accessible to protocols
 * @keepalive_timeout: VHOST: (default = 0 = 60s) seconds to allow remote
 *		client to hold on to an idle HTTP/1.1 connection
 * @log_filepath: VHOST: filepath to append logs to... this is opened before
 *		any dropping of initial privileges
 * @mounts:	VHOST: optional linked list of mounts for this vhost
 * @server_string: CONTEXT: string used in HTTP headers to identify server
 *		software, if NULL, "libwebsockets".
 */
struct lws_context_creation_info {
	int port;					/* VH */
	const char *iface;				/* VH */
	const struct lws_protocols *protocols;		/* VH */
	const struct lws_extension *extensions;		/* VH */
	const struct lws_token_limits *token_limits;	/* context */
	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;					/* context */
	int uid;					/* context */
	unsigned int options;				/* VH + context */
	void *user;					/* context */
	int ka_time;					/* context */
	int ka_probes;					/* context */
	int ka_interval;				/* context */
#ifdef LWS_OPENSSL_SUPPORT
	SSL_CTX *provided_client_ssl_ctx;		/* context */
#else /* maintain structure layout either way */
	void *provided_client_ssl_ctx;
#endif
	short max_http_header_data;			/* context */
	short max_http_header_pool;			/* context */
	unsigned int count_threads;			/* context */
	unsigned int fd_limit_per_thread;		/* context */
	unsigned int timeout_secs;			/* VH */
	const char *ecdh_curve;				/* VH */
	const char *vhost_name;				/* VH */
	const char * const *plugin_dirs;		/* context */
	const struct lws_protocol_vhost_options *pvo;	/* VH */
	int keepalive_timeout;				/* VH */
	const char *log_filepath;			/* VH */
	const struct lws_http_mount *mounts;		/* VH */
	const char *server_string;			/* context */
	/* Add new things just above here ---^
	 * This is part of the ABI, don't needlessly break compatibility
	 *
	 * The below is to ensure later library versions with new
	 * members added above will see 0 (default) even if the app
	 * was not built against the newer headers.
	 */
	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 = 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);
 
    return 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;
	struct tcp_keepalive alive;
	int protonbr;
#ifndef _WIN32_WCE
	struct protoent *tcp_proto;
#endif
 
	if (vhost->ka_time) {
		/* enable keepalive on this socket */
		// 先调用setsockopt打开发送心跳包(设置)选项
		optval = 1;
		if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE,
					     (const char *)&optval, optlen) < 0)
			return 1;
 
		alive.onoff = TRUE;
		alive.keepalivetime = vhost->ka_time*1000;
		alive.keepaliveinterval = vhost->ka_interval*1000;
 
		if (WSAIoctl(fd, SIO_KEEPALIVE_VALS, &alive, sizeof(alive),
					      NULL, 0, &dwBytesRet, NULL, NULL))
			return 1;
	}
 
	/* Disable Nagle */
	optval = 1;
#ifndef _WIN32_WCE
	tcp_proto = getprotobyname("TCP");
	if (!tcp_proto) {
		lwsl_err("getprotobyname() failed with error %d\n", LWS_ERRNO);
		return 1;
	}
	protonbr = tcp_proto->p_proto;
#else
	protonbr = 6;
#endif
 
	setsockopt(fd, protonbr, TCP_NODELAY, (const char *)&optval, optlen);
 
	/* We are nonblocking... */
	ioctlsocket(fd, FIONBIO, &optl);
 
	return 0;
}

 Information Direct: Linux カーネル ソース コード テクノロジ学習ルート + ビデオ チュートリアル カーネル ソース コード

Learning Express: Linux カーネル ソース コード メモリ チューニング ファイル システム プロセス管理 デバイス ドライバー/ネットワーク プロトコル スタック

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 が返された場合は、接続が成功したことを示します。

With a nonblocking socket, the connection attempt cannot be completed immediately. In this case, connect will return SOCKET_ERROR, and WSAGetLastError will return WSAEWOULDBLOCK. In this case, there are three possible scenarios:

Use the select function to determine the completion of the connection request by checking to see if the socket is writeable.

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

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 = socket(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);
 
	// 将套接字设置为非阻塞式的,为下面的select做准备
	unsigned long ulnoblock = 1;
	ioctlsocket(connSock, FIONBIO, &ulnoblock);
 
	// 发起connnect,该接口立即返回
	connect(connSock, (sockaddr*)&devAddr, sizeof(devAddr));
 
	FD_SET writefds;
	FD_ZERO(&writefds);
	FD_SET(connSock, &writefds);
 
    // 设置连接超时时间为1秒
	timeval tv;
	tv.tv_sec = 1; //超时1s
	tv.tv_usec = 0;
	// The select function returns the total number of socket handles that are ready and contained 
	// in the fd_set structures, zero if the time limit expired, or SOCKET_ERROR(-1) if an error occurred. 
	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/youzhangjing_/article/details/132901678