У меня есть многопоточный сервер (пул потоков), который обрабатывает большое количество запросов (до 500/сек для одного node), используя 20 потоков. Там есть поток прослушивателя, который принимает входящие соединения и ставит их в очередь для обрабатываемых потоков обработчиков. После того, как ответ готов, потоки затем выписываются клиенту и закрывают сокет. Кажется, все было хорошо до недавнего времени, тестовая клиентская программа начала свисать случайно после прочтения ответа. После многократного копания кажется, что close() с сервера фактически не отключает сокет. Я добавил некоторые отладочные отпечатки в код с номером дескриптора файла, и я получаю этот тип вывода.
Processing request for 21
Writing to 21
Closing 21
Возвращаемое значение close() равно 0, или будет напечатан другой отладочный оператор. После этого вывода с зависающим клиентом lsof показывает установленное соединение.
SERVER 8160 root 21u IPv4 32754237 TCP localhost: 9980- > localhost: 47530 (ESTABLISHED)
КЛИЕНТ 17747 root 12u IPv4 32754228 TCP localhost: 47530- > localhost: 9980 (ESTABLISHED)
Как будто сервер никогда не отправляет последовательность завершения клиенту, и это состояние зависает до тех пор, пока клиент не будет убит, оставив серверную сторону в состоянии ожидания
SERVER 8160 root 21u IPv4 32754237 TCP localhost: 9980- > localhost: 47530 (CLOSE_WAIT)
Кроме того, если клиент имеет указанный тайм-аут, он будет тайм-аут вместо того, чтобы висит. Я также могу запустить вручную
call close(21)
на сервере из gdb, а затем клиент отключится. Это случается, возможно, когда-то в 50 000 запросов, но может не произойти в течение длительных периодов.
Версия для Linux: 2.6.21.7-2.fc8xen Версия Centos: 5.4 (Final)
действия сокета следующие
SERVER:
int client_socket; struct sockaddr_in client_addr; socklen_t client_len = sizeof (client_addr);
while(true) {
client_socket = accept(incoming_socket, (struct sockaddr *)&client_addr, &client_len);
if (client_socket == -1)
continue;
/* insert into queue here for threads to process */
}
Затем поток поднимает сокет и формирует ответ.
/* get client_socket from queue */
/* processing request here */
/* now set to blocking for write; was previously set to non-blocking for reading */
int flags = fcntl(client_socket, F_GETFL);
if (flags < 0)
abort();
if (fcntl(client_socket, F_SETFL, flags|O_NONBLOCK) < 0)
abort();
server_write(client_socket, response_buf, response_length);
server_close(client_socket);
server_write и server_close.
void server_write( int fd, char const *buf, ssize_t len ) {
printf("Writing to %d\n", fd);
while(len > 0) {
ssize_t n = write(fd, buf, len);
if(n <= 0)
return;// I don't really care what error happened, we'll just drop the connection
len -= n;
buf += n;
}
}
void server_close( int fd ) {
for(uint32_t i=0; i<10; i++) {
int n = close(fd);
if(!n) {//closed successfully
return;
}
usleep(100);
}
printf("Close failed for %d\n", fd);
}
КЛИЕНТ:
Клиентская сторона использует libcurl v 7.27.0
CURL *curl = curl_easy_init();
CURLcode res;
curl_easy_setopt( curl, CURLOPT_URL, url);
curl_easy_setopt( curl, CURLOPT_WRITEFUNCTION, write_callback );
curl_easy_setopt( curl, CURLOPT_WRITEDATA, write_tag );
res = curl_easy_perform(curl);
Ничего необычного, просто базовое соединение. Клиент зависает в tranfer.c(в libcurl), потому что сокет не воспринимается как закрытый. Он ждет больше данных с сервера.
Вещи, которые я пробовал до сих пор:
Отключение до закрытия
shutdown(fd, SHUT_WR);
char buf[64];
while(read(fd, buf, 64) > 0);
/* then close */
Настройка SO_LINGER для принудительного закрытия через 1 секунду
struct linger l;
l.l_onoff = 1;
l.l_linger = 1;
if (setsockopt(client_socket, SOL_SOCKET, SO_LINGER, &l, sizeof(l)) == -1)
abort();
Это не имело никакого значения. Любые идеи были бы с благодарностью.
EDIT. Это оказалось проблемой безопасности потоков внутри библиотеки очередей, в результате чего сокет обрабатывался ненадлежащим образом несколькими потоками.