クロスプラットフォーム(Windows / Linux)の最下層の実装の違いと落とし穴を選択する

目的:
selectに関しては、誰もが知っているかもしれませんが、使用の過程でまだいくつかの隠れた落とし穴があります。この記事では、主に、selectを使用してクロスプラットフォーム通信ライブラリコンポーネントを実装する過程で発生する問題について説明します。異なるプラットフォーム(Windows、Linux)間でのselectの基本的な実装の違いにより、不可解なクラッシュが発生しました。

問題の説明:
Linuxプラットフォームでselectによって実装されたネットワーク通信ライブラリは、高い同時実行性で不可解にクラッシュしますが、Windowsプラットフォームでは問題はありません。Linuxfd_setデータ構造を読み取った後、答えが見つかりました。説明は次のとおりです。

WindowsおよびLinuxプラットフォームでのfd_setのデータ構造:

1 windows平台fd_set结构体如下:

typedef struct fd_set {
    
    
        u_int fd_count;               /* how many are SET? */
        SOCKET  fd_array[FD_SETSIZE];   /* an array of SOCKETs */
} fd_set;

2 linux平台fd_set结构体如下:

文件位置:/usr/include/i386-linux-gnu/sys/select.h
#undef _FD_SETSIZE
#define _FD_SETSIZE        1024

typedef long int __fd_mask;
#define __NFDBITS	(8 * (int) sizeof (__fd_mask))
typedef struct
{
    
    
#ifdef __USE_XOPEN
	__fd_mask fds_bits[__FD_SETSIZE / __NFDBITS];
#define __FDS_BITS(set) ((set)->fds_bits)
#else
	__fd_mask __fds_bits[__FD_SETSIZE / __NFDBITS];
#define __FDS_BITS(set) ((set)->__fds_bits)
#endif
} fd_set;

上記の構造から、Windowsプラットフォームのfd_set構造は基本的にソケットの配列であることがわかりますが、Linuxプラットフォームでは異なります。fd_setは実際にはビットマップマッピングテーブルです。各ソケットは、対応するソケットにマッピングすることでイベントを設定します。ビット(例:Fd = 3は2ビットにマップされます; fd = 1000、次に999番目のビットにマップされます);つまり、fd_setによって同時に処理される接続の数は、fd_setビットマップのサイズによって制限されます。 linux、sizeof(fd_set)のサイズ128byte、つまり1024bitなので、プッシュできます。デフォルトでは、linuxシステムでのselectは、1024-3 = 1021fd接続の処理のみを通知できます。現時点では、多くの人がLinuxカーネルプロセスを調整できると言うかもしれませんファイル記述子の方法はこの制限を破りますこれを言った人は同じであると推定されます(これを言う根本的な理由はプロセスファイル記述子の概念を混乱させることです制限を選択してfd_set実装の制限を選択するか、単にこれを理解しないでください。2つの概念は、他の人がそれを当然のことと思っていると言っているだけです。)テスト後、プロセスファイル記述子の制限を調整しても役に立たないことがわかります。 、sizeof(fd_set)はまだ128に等しい。
ここに画像の説明を挿入プロセスファイル記述子の制限を調整した後、fd_setサイズテストのスクリーンショット

結論:
実際、プロセスファイル記述子の制限を調整した後、select1024同時番号テストを実際に突破できるかどうかを確認するのは非常に簡単で、1024ファイルを開くだけで済みます(Linuxシステムでは、 -プロセスファイルfdとソケットはシステムで同じです)はい、開いているすべてのfdはプロセスfdクォータを占有します)、次にfd = 1025になるようにソケットを開き、次の構造を使用してテストします。

typedef struct
{
    
    
	fd_set test_fd_set;
	int c;
}my_fd_set;

int main(int argc, char *argv[]) {
    
    
	int port = atoi(argv[1]);
	
	for(int i = 0; i<port; ++i)
		FILE * fp=fopen("noexist","a+");
		
	int sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if (sockfd < 0) {
    
    return -1;}
	struct sockaddr_in addr;
	memset(&addr, 0, sizeof(struct sockaddr_in));
	addr.sin_family = AF_INET;
	addr.sin_port = htons(port);
	addr.sin_addr.s_addr = INADDR_ANY;
	if (bind(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_in)) < 0) {
    
    return 2}
	if (listen(sockfd, 5) < 0) {
    
    return 3;}
	
	my_fd_set test_rfds;
	test_rfds.c = 0;
	FD_ZERO(&(test_rfds.test_fd_set));
	FD_SET(sockfd, &(test_rfds.test_fd_set));
	printf("sockfd,  rfds.c: %d:%d\n", sockfd, test_rfds.c);
	
	return 0;
}

テスト結果は次のとおりです
ここに画像の説明を挿入。fd> 1024の場合、fd_setの直後のメモリスペースは書き込まれたデータによって破壊されます。このスペースが他の場所で使用されると、クラッシュするか、データプロセスのデータの一部が破壊されます。多くのプログラムがクラッシュした後、根本的な問題を見つけて分析することが難しい理由でもあります(メモリの書き込みが悪いと想像してください。数か月後、他の場所で問題が発生することがあります。アクセス時に見つけるのが非常に難しい)

おすすめ

転載: blog.csdn.net/wangrenhaioylj/article/details/108035459
おすすめ