ソケットがすでになくなっているにもかかわらず、TCPサーバーがソケットを永久にブロックすることがあるLinuxの問題をデバッグしようとしています。close()、shutdown()、select()、recv() の動作を理解するためのテスト コードを作成しました。
2 つのスレッドがあります。スレッド 1 は select() を使用してソケット上でブロックし、スレッド 2 は同じソケット上で shutdown(SHUT_RDWR) を呼び出します。スレッド 2 から shutdown() が呼び出されると、スレッド 1 がウェイクアップし、select() は 1 を返しますが、errno は 0 を読み取ります。また、select() が 1 を返した後、recv() が呼び出されて 0 を返し、再び errno が 0 を読み取ります。
スレッド 2 の実装を shutdown() の代わりに close() を使用するように変更すると、select() が起動しなくなります。
スレッド 2 の実装を変更して、shutdown() に続いて close() を呼び出すと、select() は 1 を返し (errno は 0 のまま)、recv() は -1 を返し、errno は EBADF を読み取ります。
私の質問:
close() が呼び出されたとき、select() は起きないようにすべきですか? close() の man ページを調べましたが、どうなるかわかりませんでした。
shutdown(SHUT_RDWR) が呼び出されたとき、select() は私が見ているように正常に戻る必要がありますか? それとも、1 を返して errno を EBADF に設定する必要がありますか? select() のマニュアル ページを見ると、有効なオープン ファイル記述子ではないファイル記述子セットが 1 つ以上のファイル記述子セットで指定されている場合、errno が EBADF に設定されていることがわかります。shutdown() は TCP FIN をリモート側に送信するだけで、実際にはソケット fd を閉じていないように見えますか?
shutdown() に続いて close() を呼び出すと、役立つようです。これは close() を単独で呼び出すのとどう違うのですか?
注: あるスレッドでソケットをブロックし、別のスレッドから同じソケットを閉じるのは適切な設計ではないことは理解していますが、レガシー コードベースを扱っているため、この設計を変更することはできません。shutdown() を呼び出してから close() を呼び出すように調整することしかできませんが、機能しているように見えますが、基本的な理解が正しいことを確認したいと思います。
前もって感謝します。
テストコードは次のとおりです。
スレッド 1:
#include "sys/socket.h"
#include "net/if.h"
#include "linux/sockios.h"
#include "netinet/in.h"
#include "fcntl.h"
#include "signal.h"
#include "errno.h"
#include "strings.h"
#include "pthread.h"
#include "errno.h"
void* f1(void* p)
{
int f1_ret, bytes, ret;
struct sockaddr_in f1so1_addr;
fd_set read_f1, error_fds;
char f1buf[20];
struct sockaddr clientInfo;
socklen_t clientAddrLen = sizeof(struct sockaddr);
f1so1 = socket(AF_INET, SOCK_STREAM, 0);
printf("f1: open fd's - f1fd1 = %d, f1so1 = %d\n", f1fd1, f1so1);
f1so1_addr.sin_family = AF_INET;
f1so1_addr.sin_addr.s_addr = htonl(INADDR_ANY);
//ret = inet_aton("10.20.30.100", &(f1so1_addr.sin_addr));
f1so1_addr.sin_port = htons(7777);
ret = bind(f1so1, (const struct sockaddr*) &f1so1_addr, sizeof(f1so1_addr));
printf("f1: bind returned %x, errno = %x\n", ret, errno);
listen(f1so1, 5);
printf("f1- listening on f1so1, blocking on accept\n");
f1so2 = accept(f1so1, &clientInfo, &clientAddrLen);
printf("f1- accept returned %x, errno = %x\n", f1so2, errno);
if(errno)
{
printf("f1: accept failed, return...\n");
return;
}
FD_ZERO(&read_f1);
FD_SET(f1so2, &read_f1);
FD_ZERO(&error_fds);
FD_SET(f1so2, &error_fds);
printf("f1: start loop\n");
while (1)
{
printf("f1: call select _read and error__ - cBLOCKING\n");
errno = 0;
FD_ZERO(&read_f1);
FD_SET(f1so2, &read_f1);
FD_ZERO(&error_fds);
FD_SET(f1so2, &error_fds);
f1_ret = select(f1so2+1, &read_f1, 0, &error_fds, 0);
printf("f1: select returned = %x, errno = %x\n", f1_ret, errno);
if (errno)
{
printf("f1: select failed...\n");
}
else if (f1_ret)
{
if (FD_ISSET(f1so2,&error_fds))
{
printf("f1: error on socket %x\n",f1so2);
}
if (FD_ISSET(f1so2,&read_f1))
{
sleep(1);
bytes = recv(f1so2, f1buf, 20, 0);
printf("f1: errno after recv = %x\n", errno);
if (errno)
{
printf("!!!recv failed!!!\n");
return;
}
printf("f1: read from socket %x, bytes = %x, data = %s\n",f1so2, bytes, f1buf);
}
}
}
printf("f1: exiting\n");
}
スレッド 2:
#include "stdio.h"
#include "pthread.h"
#include "linux/socket.h"
#include "sys/socket.h"
#include "net/if.h"
#include "linux/sockios.h"
#include "netinet/in.h"
#include "fcntl.h"
#include "signal.h"
#include "errno.h"
#include "strings.h"
extern int f1so2;
void f2()
{
int f2_ret, i, choice;
struct sockaddr_in f2so2_addr;
fd_set read_f2;
char f2buf[20];
struct sigaction f2_act;
while (1)
{
printf("f2: 1: close socket, 2: shutdown socket, 2: exit f2\n");
scanf("%d", &choice);
if (choice == 1)
{
f2_ret = close(f1so2);
printf("f2: close on socket %x returned %x, errno = %x\n", f1so2, f2_ret, errno);
}
else if (choice == 2)
{
f2_ret = shutdown(f1so2, SHUT_RDWR);
printf("f2: shutdown on socket %x returned %x, errno = %x\n", f1so2, f2_ret, errno);
}
else if (choice == 3)
{
continue;
}
}
printf("f2: exiting\n");
return;
}