Почему мой серверный сокет не производит активность в select (), когда клиентская сторона соединения закрыта? - PullRequest
0 голосов
/ 21 августа 2011

Это сбивало меня с толку пару дней, поэтому я решил прекратить бороться с этим и открыть его для более широкой аудитории.

У меня есть сервер, написанный на C, который создает рабочий поток длякаждый клиент (ожидается, что количество клиентов будет очень небольшим).С каждым потоком связаны только два файловых дескриптора, один из которых - сокет, поэтому я решил использовать select () для простоты.Клиенты никогда не отправляют данные на сервер, но я установил бит активности для дескриптора файла сокета в аргументе readfds перед вызовом select () в качестве средства определения, когда клиент закрыл соединение.

КлиентыЭто все экземпляры Java-программы, которая, по-видимому, должным образом открывает соединение с потоковым сокетом (мой низкоуровневый опыт работы с сетью на Java не так силен, и я не писал клиент).В моей тестовой среде я запускаю программу Java из экземпляра редактора Eclipse и уничтожаю ее, используя кнопку остановки для имитации выключения.

Сервер и клиент работают под Linux.

Связь ведется в соответствии с моими ожиданиями, кроме случаев, когда клиент выключается.В этом случае я ожидал бы, что вызов метода select () в рабочем потоке сервера вернется с битом готовности, связанным с дескриптором файла сокета, установленным в аргументе readfds, после чего вызов read () должен вернуть 0 байтов,показывая, что узел закрыл соединение.

Что я вижу, так это то, что ожидаемое поведение происходит случайным образом, в то время как в других случаях вызов select () не возвращается и сервер в конечном итоге получает (errno == EPIPE) после того, как write () возвращает -1, когдау него есть данные, которые он решает отправить мертвому клиенту.В частности, первое подключение к серверу всегда ведет себя корректно, а второе - всегда сбой.Это на самом деле не блокирует мой прогресс, потому что сервер просто регистрирует ошибку и очищает соединение, когда обнаруживает условие, но это раздражает меня и заставляет задуматься, есть ли какой-то тонкий момент, о котором я здесь забываю, потому что это былоЯ давно программировал на этом уровне.

РЕДАКТИРОВАТЬ: код разбросан по разным маленьким частям по нескольким блокам перевода, поэтому я постараюсь свести его к чему-то, что доступно для удаленного чтения (обратите внимание, что много кода проверки ошибок также было удалено):

/* A chunk of code related to the handing of a listen socket */
int clientSockFd = accept(listenerFd, &addr, &addrlen);
int optVal = 1;
setsockopt(clientSockFd, IPPROTO_TCP, TCP_NODELAY, &optVal, sizeof(optVal));
ThreadDataStruct *useful = (ThreadDataStruct *)malloc(sizeof(useful));
/* Add in some useful stuff */
useful->fd = clientSockFd;
pthread_create(&newThreadId, NULL, workerThread, (void *)useful);
/* End of listener handling code */

/* ... */

static void *workerThread(void *arg) {
    int clientSockFd = ((ThreadDataStruct *)arg)->fd;
    /* Unpack some other useful stuff from arg */
    int maxFd = LargestFdUsedByThisThread + 1;
    while (1) {
        fd_set readableReady;
        FD_ZERO(&readableReady);
        FD_SET(clientSockFd, &readableReady);
        int readyFdCount = select(maxFd, &readableReady, NULL, NULL, NULL);
        if (FD_ISSET(clientSockFd, &readableReady)) {
            /* Clean up various data structures associated with the thread */
            return NULL;
        }
        /* Do something else useful but irrelevant to this problem */
    }
}

1 Ответ

1 голос
/ 27 августа 2011

Три наиболее вероятные причины этой проблемы:

Что-то не так в вызове select. Например, если LargestFdUsedByThisThread меньше clientSockFd,select вызов не разблокируется.

Соединение никогда не закрывается. Например, если другой процесс также имел ссылку на базовое TCP-соединение, уничтожение клиентского процесса не приведет к закрытию соединения.до тех пор, пока этот другой процесс все еще имеет ссылку на него.Обычно это происходит, если другой процесс fork отключил клиентский процесс после того, как он принял соединение и не закрыл свой дескриптор соединения.

У вас проблема с многопоточностью. Я заметил, что вы передаете указатель на вновь созданный поток.Если вы затем измените данные в основном потоке, вновь созданный поток может читать данные новые , а не данные во время их создания.Это может привести к тому, что поток увидит неправильное значение для clientSockFd.Хороший способ избежать этой ошибки:

1) Принять соединение и подготовиться к созданию потока.
2) Выделить объект для хранения параметров, необходимых для потока.
3) Создайте поток, передав ему указатель на этот объект.
4) В новом потоке освободите объект параметра, когда закончите с ним.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...