Это сбивало меня с толку пару дней, поэтому я решил прекратить бороться с этим и открыть его для более широкой аудитории.
У меня есть сервер, написанный на 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 */
}
}