Я использую epoll, как я полагаю, типичным способом для сокетов TCP (в основном на этом примере , но немного адаптирован к C ++); один основной прослушивающий сокет привязан к порту, и каждый новый сокет подключения (от accept ()) также добавляется для оповещений, когда он готов для recv (). Я создал тестовый скрипт, который в основном забивает его соединениями и отправляет / получает. Когда подключен какой-либо один клиент, он будет работать безупречно, бесконечно.
Однако добавление второго тестового клиента одновременно приведет к зависанию одного из них. После пары дней отладки я, наконец, решил просто выдать идентификатор сокета, с которым он работает, в файл, и я озадачен тем, что нашел.
Когда запускается один скрипт, я получаю только поток, в данном случае, 6. Однако, когда запускается второй скрипт, я получаю поток 7. Просто 7. И он остается на 7 , исключительно общение со вторым клиентом, полностью игнорируя первого, пока первый не достигнет своего таймаута и не закроется. (Затем, когда клиент 2 переподключается, вместо этого он получает идентификатор 6).
Стоит отметить, что этот тестовый сценарий не использует постоянное соединение, он отключается и повторно подключается после того, как несколько сообщений перемещаются назад и вперед (для более точного моделирования). Но даже через это клиент 1 игнорируется. Если я установлю тайм-аут достаточно высоким, чтобы у клиента 2 действительно было время на выход, он все равно не возобновит работу с клиентом 1, поскольку все, что он ожидал, просто теряется.
Является ли это нормальным поведением для epoll (или сокетов в целом) полностью отказаться от предыдущего задания при появлении нового? Есть ли какая-то опция, которую я должен указать?
РЕДАКТИРОВАТЬ: Это столько кода, сколько я могу показать; Я не обязательно ожидаю «это то, что вы сделали неправильно», скорее «это некоторые вещи, которые сломают / исправят подобную ситуацию».
#define EVENTMODE (EPOLLIN | EPOLLET | EPOLLRDHUP | EPOLLHUP)
#define ERRCHECK (EPOLLERR | EPOLLHUP | EPOLLRDHUP)
//Setup event buffer:
struct epoll_event* events = (epoll_event*)calloc(maxEventCount, sizeof(event));
//Setup done, main processing loop:
int iter, eventCount;
while (1) {
//Wait for events indefinitely:
eventCount = epoll_wait(pollID, events, maxEventCount, -1);
if (eventCount < 0) {
syslog(LOG_ERR, "Poll checking error, continuing...");
continue;
}
for (iter = 0; iter<eventCount; ++iter) {
int currFD = events[iter].data.fd;
cout << "Working with " << events[iter].data.fd << endl;
if (events[iter].events & ERRCHECK) {
//Error or hangup:
cout << "Closing " << events[iter].data.fd << endl;
close(events[iter].data.fd);
continue;
} else if (!(events[iter].events & EPOLLIN)) {
//Data not really ready?
cout << "Not ready on " << events[iter].data.fd << endl;
continue;
} else if (events[iter].data.fd == socketID) {
//Event on the listening socket, incoming connections:
cout << "Connecting on " << events[iter].data.fd << endl;
//Set up accepting socket descriptor:
int acceptID = accept(socketID, NULL, NULL);
if (acceptID == -1) {
//Error:
if (!(errno == EAGAIN || errno == EWOULDBLOCK)) {
//NOT just letting us know there's nothing new:
syslog(LOG_ERR, "Can't accept on socket: %s", strerror(errno));
}
continue;
}
//Set non-blocking:
if (setNonBlocking(acceptID) < 0) {
//Error:
syslog(LOG_ERR, "Can't set accepting socket non-blocking: %s", strerror(errno));
close(acceptID);
continue;
}
cout << "Listening on " << acceptID << endl;
//Add event listener:
event.data.fd = acceptID;
event.events = EVENTMODE;
if (epoll_ctl(pollID, EPOLL_CTL_ADD, acceptID, &event) < 0) {
//Error adding event:
syslog(LOG_ERR, "Can't edit epoll: %s", strerror(errno));
close(acceptID);
continue;
}
} else {
//Data on accepting socket waiting to be read:
cout << "Receive attempt on " << event.data.fd << endl;
cout << "Supposed to be " << currFD << endl;
if (receive(event.data.fd) == false) {
sendOut(event.data.fd, streamFalse);
}
}
}
}
РЕДАКТИРОВАТЬ: Код был пересмотрен, и удаление запуска по краю действительно остановит epoll от блокировки на одном клиенте. У него все еще есть проблемы с клиентами, не получающими данные; идет отладка, чтобы увидеть, это та же проблема или что-то еще.
РЕДАКТИРОВАТЬ: Кажется, это та же ошибка в другом костюме. Он пытается получить на втором сокете, но дальнейшая регистрация сообщает, что он фактически срабатывает EWOULDBLOCK почти каждый раз. Интересно, что журналы сообщают о гораздо большей активности, чем это оправдано - более 150 000 строк, когда я ожидаю около 60 000. Удаление всех строк «Будет блокировать» уменьшает его примерно до ожидаемого числа ... и вот, результирующие строки создают точно такой же шаблон. Помещение спускового механизма обратно в останавливает поведение блокирующего блока, по-видимому, предотвращая его вращение колес настолько быстро, насколько это возможно без видимой причины. Все еще не решает исходную проблему.
РЕДАКТИРОВАТЬ: Просто чтобы покрыть мои базы, я решил сделать больше отладки на отправляющей стороне, так как зависший клиент, очевидно, ждет сообщения, которое он никогда не получит. Однако я могу подтвердить, что сервер отправляет ответ на каждый запрос, который он обрабатывает; запрос зависшего клиента просто полностью теряется и поэтому никогда не отвечает.
Я также удостоверился, что мой цикл приема читает до тех пор, пока он фактически не достигнет EWOULDBLOCK (это обычно не нужно, потому что первые два байта моего заголовка сообщения содержат размер сообщения), но это ничего не изменило.
'NED EDIT: Я, вероятно, должен уточнить, что эта система использует формат запроса / ответа, а получение, обработка и отправка выполняются за один раз.Как вы можете догадаться, это требует чтения приемного буфера, пока он не опустеет, что является основным требованием для режима запуска по фронту.Если полученное сообщение является неполным (что никогда не должно происходить), сервер в основном возвращает ложное значение клиенту, что, хотя технически ошибка все равно позволит клиенту продолжить другой запрос.
Отладка подтвердила, что клиентзависание отправит запрос и будет ждать ответа, но этот запрос никогда не вызывает ничего в epoll - он полностью игнорирует первого клиента после подключения второго.
Я также удалилпопытка получить сразу после принятия;за сто тысяч попыток он не был готов один раз.
Больше РЕДАКТИРОВАТЬ: Хорошо, хорошо - если есть одна вещь, которая может подтолкнуть меня к произвольному заданию, это ставит под сомнение мои способности.Итак, вот функция, где все должно идти не так:
bool receive(int socketID)
{
short recLen = 0;
char buff[BUFFERSIZE];
FixedByteStream received;
short fullSize = 0;
short diff = 0;
short iter = 0;
short recSoFar = 0;
//Loop through received buffer:
while ((recLen = read(socketID, buff, BUFFERSIZE)) > 0) {
cout << "Receiving on " << socketID << endl;
if (fullSize == 0) {
//We don't know the size yet, that's the first two bytes:
fullSize = ntohs(*(uint16_t*)&buff[0]);
if (fullSize < 4 || recLen < 4) {
//Something went wrong:
syslog(LOG_ERR, "Received nothing.");
return false;
}
received = FixedByteStream(fullSize);
}
diff = fullSize - recSoFar;
if (diff > recLen) {
//More than received bytes left, get them all:
for (iter=0; iter<recLen; ++iter) {
received[recSoFar++] = buff[iter];
}
} else {
//Less than or equal to received bytes left, get only what we need:
for (iter=0; iter<diff; ++iter) {
received[recSoFar++] = buff[iter];
}
}
}
if (recLen < 0 && errno == EWOULDBLOCK) {
cout << "Would block on " << socketID << endl;
}
if (recLen < 0 && errno != EWOULDBLOCK) {
//Had an error:
cout << "Error on " << socketID << endl;
syslog(LOG_ERR, "Connection receive error: %s", strerror(errno));
return false;
} else if (recLen == 0) {
//Nothing received at all?
cout << "Received nothing on " << socketID << endl;
return true;
}
if (fullSize == 0) {
return true;
}
//Store response, since it needs to be passed as a reference:
FixedByteStream response = process(received);
//Send response:
sendOut(socketID, response);
return true;
}
Как видите, она не может зацикливаться после возникновения ошибки.Я не очень часто использую C ++, но я достаточно долго кодировал, чтобы проверять такие ошибки, прежде чем обращаться за помощью.
bool sendOut(int socketID, FixedByteStream &output)
{
cout << "Sending on " << socketID << endl;
//Send to socket:
if (write(socketID, (char*)output, output.getLength()) < 0) {
syslog(LOG_ERR, "Connection send error: %s", strerror(errno));
return false;
}
return true;
}
Что если это EWOULDBLOCK?Так же, как если бы моя материнская плата таяла - я это исправлю.Но этого еще не произошло, поэтому я не собираюсь это исправлять, я просто уверен, что знаю, нужно ли это когда-либо исправлять.
И нет, process () не делает что-нибудь с сокетами, он принимает и возвращает только массив символов фиксированной длины.Опять же, эта программа отлично работает с одним клиентом, но не с двумя или более.
Последнее редактирование: После еще большей отладки я нашел источник проблемы.Я просто пойду и отвечу сам.