shutdown() активирует select(), но errno остается 0, нет EBADF
Я пытаюсь отладить проблему в Linux, которая иногда приводит к тому, что мой TCP-сервер блокируется на сокете навсегда, хотя сокета уже нет. Я написал тестовый код, чтобы понять поведение close (), shutdown (), select () и recv().
У меня есть 2 потока - поток 1 блокирует на сокете, используя select (), и поток 2 вызывает завершение работы (SHUT_RDWR) на том же сокете. Когда shutdown () вызывается из потока 2, поток 1 просыпается, select () возвращает 1, но errno читает 0. Кроме того, после select () возвращает 1, вызывается recv() и возвращается 0, опять же, errno читает 0.
Если я изменю реализацию потока 2 на использование close () вместо shutdown (), select () никогда не проснется.
Если я изменю реализацию потока 2 на вызов shutdown (), за которым следует close (), select () возвращает 1 (errno остается 0), а затем recv() возвращает -1 и errno читает EBADF.
Мои вопросы:
Когда вызывается close (), должен ли select () не просыпаться? Я посмотрел справочную страницу close (), но не смог понять, что должно произойти.
Когда вызывается shutdown(SHUT_RDWR), должен ли select () возвращаться нормально, как я вижу? Или он должен вернуть 1 и установить errno в EBADF? Если я смотрю на справочную страницу select (), она говорит, что 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;
}