
![]() |
Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
|
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[216.73.216.198] |
![]() |
|
Сообщ.
#1
,
|
|
|
Всем здравствуйте !
такая вот проблемка .. есть сервер который принимает клиентские подключения и записывает их все в общую fd_set ну и слушает когда поступит подключение или прием данных через, соответственно, select .. так вот .. если ктото из клиентов сделал у себя closesocket то функция select так же как и приеме данных вернет буфер с откликнувшимися сокетами ... может кто знает ... вот как определить именно в том месте где select вернула управление что сокет не данные прислал а именно отключился, без использования recv .. дело в том что чтение данных уже проводится в потоках, а хотелось бы определить отключенный сокет еще до того как он отправился на обработку в поток ... есть ли вообще такая возможность? |
Сообщ.
#2
,
|
|
|
![]() ![]() if(FD_ISSET(sock, &fde)){ .... } а вообще в поиск по select + FD_ISSET |
Сообщ.
#3
,
|
|
|
FD_ISSET в любом случае вернет истину ... ведь сокет в массиве и так есть !!!
ну вот например так : (реальный код не могу выложить потому что он слишком громоздкий, много будет не по теме ) ![]() ![]() fd_set g__arr; void Init () { SOCKET sock = // ... создаем сокет в режиме listen на какойто порт FD_SET( sock, &g__arr ); } void Wait () { while( 1 ) { fd_set pWait; memcpy( &pWait, &g__arr, sizeof(fd_set) ); // вот тут ждем вечно int ret = select( &pWait, 0,0,0 ); // как только чтото пришло // вот тут например и обрабатываем все что нам пришло на порты которые слушаются for( int i = 0 ; i < pWait.fd_count ; i ++ ) { // если это попытка соедениться то делаем : SOCKET cl = accept( m_buf.fd_array[i], ... ); FD_SET( cl, &g__arr ); // если это был клиентский сокет на который должны были прийти какието данные ... // то читаем данные и обрабатывает // но если ктото из клиентских сокетов сделал closesocket то select вернет в pWait массив сокеты // которые сделали closesocket и ret будет равен количеству элементов в pWait .... // и вот как тут вот узнать что клиент именно разорвал соединение а не передает данные ???? } } } void main () { Init(); Wait(); } |
Сообщ.
#4
,
|
|
|
Какая ОС?
Если Windows - то функциональность select() недостаточна - и сетевое I/O не сможет эффективно обслуживаться. FD_ISSET - это и все, что можно сделать. Для Виндов надо использовать WSAAsyncSelect или WSAEventSelect Для Линуха - типа poll() 2.5.10. Функция poll() poll() является вариантом select(). Задается массив из nfds структур типа ![]() ![]() struct pollfd { int fd; /* описатель файла */ short events; /* запрошенные события */ short revents; /* возвращенные события */ }; и значения timeout в миллисекундах. Отрицательное значение указывает на бесконечный таймер. Поле fd содержит описатель открытого файла. Поле events - это входной параметр, указывающий на битовую маску событий, важных для приложения. Поле revents - это возвращаемый параметр, в который ядро помещает информацию о произошедших событиях, запрошенных или типа POLLERR, POLLHUP или POLLNVAL. (Эти три битовых флага не будут иметь смысла при использовании в поле events, поэтому будут установлены в поле revents, если соответствующее условие истинно). Если ни одно из запрошенных событий не случилось или не произошла ни одна из ошибок, то ядро ждет их появления до истечения срока таймера. Вот возможные биты, описанные в <sys/poll.h>: События ввода/вывода Событие Флаг poll() Когда происходит Чтение POLLIN Пришли новые данные. Чтение POLLIN Установка соединения завершена (для сокетов, ориентированных на соединения) Чтение POLLHUP Другая сторона инициировала запрос на разъединение. Чтение POLLHUP Соединение разорвано (только для протоколов, ориентированных на соединение). Если производится запись в сокет, то также посылается сигнал SIGPIPE. Запись POLLOUT Сокет имеет достаточно места в буфере передачи для записи в него новых данных. Чтение/запись POLLIN|POLLOUT Исходящий connect() завершен. Чтение/запись POLLERR Произошла асинхронная ошибка. Чтение/запись POLLHUP Другая сторона закрыла (shutdown) одно направление. Исключение POLLPRI Пришли неотложные данные. При этом посылается сигнал SIGURG. Здесь точно можно найти сетевое событие, которое вызвало сигнал..... |
Сообщ.
#5
,
|
|
|
к сожалению (как выясняется) пишу под windows!
честно говоря не думал что настолько большая разница.. но я вот думал что раз WSAAsyncSelect или WSAEventSelect умеют определять такие события то и без их помощи можно это определить ведь они, как мне кажется, являются оболочкой на winsock'овскими функциями типа select ... программа уже написана без использования WSA а тут такой глюк вылез ... а чтобы на асинхронные сокеты перейти много переписывать !! |
Сообщ.
#6
,
|
|
|
Цитата FD_ISSET в любом случае вернет истину ... ведь сокет в массиве и так есть !!! ![]() ![]() int ret = select( &pWait, 0,0,0 ); ![]() судя по куску кода, ты имеешь очень смутное представление о функции select и о том как с ней работать Вот маленький кусок кода из работающей программы и не забудь почитать хотя бы описание используемых тобой функций ![]() ![]() while(connected) { fd_set fdr, fde; FD_ZERO(&fdr); FD_ZERO(&fde); FD_SET(gw_socket, &fdr); FD_SET(gw_socket, &fde); int res = select(gw_socket + 1, &fdr, 0, &fde, 0); if (res > 0) { if (FD_ISSET(gw_socket, &fdr)) { //recv } if (FD_ISSET(gw_socket, &fde)) { //error - disconnect; } } } |
Сообщ.
#7
,
|
|
|
Идея распихать по нескольким наборам - она в принципе работает.
//error - disconnect; Но вот для определения ошибки на Error-сокете надо использовать getsockopt например: · Ошибка сокета, ожидающая обработки. Операция записи в сокет не блокируется и возвратит ошибку (-1) со значением переменной еrrnо, указывающей на конкретное условие ошибки. Эти ошибки, ожидающие обработки, можно также получить и сбросить, вызвав функцию getsockopt() с параметром сокета SO_ERROR. Но это для UNIX-систем. Там есть errno. Как для винды????????? |
Сообщ.
#8
,
|
|
|
![]() ![]() ![]() if (FD_ISSET(gw_socket, &fde)) А про винду + getsockopt + SO_ERROR, ничего толкового не скажу, но когда то переносил код из соляриса(там как раз по наличию ошибки(SO_ERROR) отвал делался), то в винде это уже не работало, ибо getsockopt возвращал в коде ошибки 0, даже когда на другой стороне уже закрыли соединение. |
Сообщ.
#9
,
|
|
|
Цитата popsa @ По мне if (FD_ISSET(gw_socket, &fde)) уже зватает чтобы закрывать соединение Логично. Т.е достаточно - но не необходимо. Теоретически может быть и иная причина помещения в набор.... Но - как я уже выше согласился - в первом приближении это работает |
Сообщ.
#10
,
|
|
|
чтото не работает ни один ни другой способ ... ни с использованием exceptfds в select'е
ни getsockopt + SO_ERROR ... я их кстати пробовал уже и при разрыве соединения в exceptfds всегда оставался пустым и соответственно способ - if (FD_ISSET(gw_socket, &fde)) не прокатил бы ... может при создании сокета какието опции нужно выставить ? я нашел другой способ ... но только для проверки сокетов которые ожидают данные: ![]() ![]() // ... // после того как select вернул управление int isListen; // проверяем не слушающий ли сокет getsockopt( cl_socket, SOL_SOCKET, SO_ACCEPTCONN, (char*)&isListen, sizeof(isListen); if( isListen == 0 ) { // проверяем состояние буфера чтения int nCountBytes; ioctlsocket( cl_socket, FIONREAD, (u_long*)&nCountBytes ); if( nCountBytes == 0 ) { // если попали сюда то сокет наверное отсоеденился // это всеравно что при выполнении recv будет возвращен 0 } } сколько не смотрел тем на форумах или исходников уже рабочих программ в основном все проверяют состояние сокета ожидающего прием данных именно попыткой чтения из него ну и если recv возвращает 0 то значит клиент закрыл соединение. вот подскажите , опытные люди, корректен ли этот способ или тут есть какието подводные камни ? по мне так этот способ будет даже относительно шустрее работать чем предложенный if (FD_ISSET(gw_socket, &fde)) .. |
Сообщ.
#11
,
|
|
|
Цитата чтото не работает ни один ни другой способ ... ни с использованием exceptfds в select'е показывай полный код ![]() Цитата ни getsockopt + SO_ERROR ![]() ![]() |
Сообщ.
#12
,
|
|
|
ну вот пожалуйста ... примерно так , проверяем ващ способ :
![]() ![]() // это все сервер #include <winsock2.h> #pragma comment(lib, "wsock32.lib") SOCKET sv_sock = INVALID_SOCKET; fd_set fd_buf; void Close( BOOL bForce = FALSE ) { if( ( sv_sock == INVALID_SOCKET || sv_sock == SOCKET_ERROR ) || bForce ) { closesocket ( sv_sock ); } } int _tmain(int argc, _TCHAR* argv[]) { WSADATA w_data; WSAStartup( MAKEWORD( 2, 0 ), &w_data ); sv_sock = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP ); if( sv_sock == SOCKET_ERROR ) { return 0; } int sock_value = 1; if ( setsockopt( sv_sock, SOL_SOCKET, SO_REUSEADDR,(char*)&sock_value, sizeof(sock_value)) == SOCKET_ERROR ) { Close(); return 0; } if ( setsockopt( sv_sock, IPPROTO_TCP, TCP_NODELAY,(char*)&sock_value, sizeof(sock_value)) == SOCKET_ERROR ) { Close(); return 0; } if( ioctlsocket( sv_sock, FIONBIO, (u_long *) &sock_value ) == SOCKET_ERROR ) { Close(); return 0; } sockaddr_in svaddr; svaddr.sin_family = AF_INET; svaddr.sin_port = htons(15555); svaddr.sin_addr.s_addr = ADDR_ANY; if( bind( sv_sock, (LPSOCKADDR)&svaddr, sizeof(SOCKADDR) ) == SOCKET_ERROR ) { Close(); return 0; } if ( listen( sv_sock, FD_SETSIZE ) == SOCKET_ERROR ) { Close(); return 0; } FD_ZERO( &fd_buf ); FD_SET( sv_sock, &fd_buf ); char radbuf[ 16 * 1024 ]; while( 1 ) { fd_set waitSet, exctSet; memcpy( &waitSet, &fd_buf, sizeof(fd_set) ); memcpy( &exctSet, &fd_buf, sizeof(fd_set) ); int ret = select( 0, &waitSet, 0, &exctSet, 0 ); if( ret <= 0 ) break; for( u_int fd = 0 ; fd < waitSet.fd_count ; fd ++ ) { SOCKET sc = waitSet.fd_array[ fd ]; int isListener; int dmSize = sizeof(isListener); if( getsockopt( sc, SOL_SOCKET, SO_ACCEPTCONN,(char*) &isListener, &dmSize) == SOCKET_ERROR ) { Close(); return 0; } if( isListener ) { // ктото зацепился int clAddrLen = sizeof(sockaddr_in); sockaddr_in cl_addr; SOCKET cl = accept( sc, (LPSOCKADDR)&cl_addr, &clAddrLen ); if( cl == INVALID_SOCKET ) continue; FD_SET( cl, &fd_buf ); continue; } // ктото из клиентов передает данные // или отвалился // анука проверимка !!! if( FD_ISSET( sc, &exctSet ) ) { FD_CLR( sc, &fd_buf ); closesocket( sc ); continue; } int read_size = recv ( sc, radbuf, 16 * 1024, 0 ); } } Close( TRUE ); WSACleanup(); return 0; } ![]() ![]() // а все это клиент #include <winsock2.h> #pragma comment(lib, "wsock32.lib") SOCKET cl_sock = INVALID_SOCKET; void Close( BOOL bForce = TRUE ) { if( ( cl_sock == INVALID_SOCKET || cl_sock == SOCKET_ERROR ) || bForce ) { closesocket ( cl_sock ); } } int _tmain(int argc, _TCHAR* argv[]) { WSADATA w_data; WSAStartup( MAKEWORD( 2, 0 ), &w_data ); cl_sock = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP ); if( cl_sock == SOCKET_ERROR ) { return 0; } sockaddr_in svaddr; svaddr.sin_family = AF_INET; svaddr.sin_port = htons(15555); svaddr.sin_addr.s_addr = inet_addr( "127.0.0.1" ); if( connect(cl_sock,(LPSOCKADDR)&svaddr,sizeof(SOCKADDR)) == SOCKET_ERROR ) { Close(); return 0; } Close( TRUE ); WSACleanup(); return 0; } начинаем трассировать !!! когда проходит connect на сервере select возвращает управление , ну и собственно добавляет полученное соединение но зато когда проходим через closesocket на клиенте ... select опять возвращает управление но exctSet.fd_count = 0 поэтому и FD_ISSET( sc, &exctSet ) вернет ложь !!! а вот исправленная версия сервера ![]() ![]() #include <winsock2.h> #pragma comment(lib, "wsock32.lib") SOCKET sv_sock = INVALID_SOCKET; fd_set fd_buf; void Close( BOOL bForce = FALSE ) { if( ( sv_sock == INVALID_SOCKET || sv_sock == SOCKET_ERROR ) || bForce ) { closesocket ( sv_sock ); } } int _tmain(int argc, _TCHAR* argv[]) { WSADATA w_data; WSAStartup( MAKEWORD( 2, 0 ), &w_data ); sv_sock = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP ); if( sv_sock == SOCKET_ERROR ) { return 0; } int sock_value = 1; if ( setsockopt( sv_sock, SOL_SOCKET, SO_REUSEADDR,(char*)&sock_value, sizeof(sock_value)) == SOCKET_ERROR ) { Close(); return 0; } if ( setsockopt( sv_sock, IPPROTO_TCP, TCP_NODELAY,(char*)&sock_value, sizeof(sock_value)) == SOCKET_ERROR ) { Close(); return 0; } if( ioctlsocket( sv_sock, FIONBIO, (u_long *) &sock_value ) == SOCKET_ERROR ) { Close(); return 0; } sockaddr_in svaddr; svaddr.sin_family = AF_INET; svaddr.sin_port = htons(15555); svaddr.sin_addr.s_addr = ADDR_ANY; if( bind( sv_sock, (LPSOCKADDR)&svaddr, sizeof(SOCKADDR) ) == SOCKET_ERROR ) { Close(); return 0; } if ( listen( sv_sock, FD_SETSIZE ) == SOCKET_ERROR ) { Close(); return 0; } FD_ZERO( &fd_buf ); FD_SET( sv_sock, &fd_buf ); char radbuf[ 16 * 1024 ]; while( 1 ) { fd_set waitSet; memcpy( &waitSet, &fd_buf, sizeof(fd_set) ); int ret = select( 0, &waitSet, 0, 0, 0 ); if( ret <= 0 ) break; for( u_int fd = 0 ; fd < waitSet.fd_count ; fd ++ ) { SOCKET sc = waitSet.fd_array[ fd ]; int isListener; int dmSize = sizeof(isListener); if( getsockopt( sc, SOL_SOCKET, SO_ACCEPTCONN,(char*) &isListener, &dmSize) == SOCKET_ERROR ) { Close(); return 0; } if( isListener ) { // ктото зацепился int clAddrLen = sizeof(sockaddr_in); sockaddr_in cl_addr; SOCKET cl = accept( sc, (LPSOCKADDR)&cl_addr, &clAddrLen ); if( cl == INVALID_SOCKET ) continue; FD_SET( cl, &fd_buf ); continue; } // ктото из клиентов передает данные // или отвалился // // зато вот тут вроде как все работает !!! u_long read_data_size; if( ioctlsocket(sc, FIONREAD, &read_data_size ) == SOCKET_ERROR ) { Close(); return 0; } if( read_data_size == 0 ) { FD_CLR( sc, &fd_buf ); closesocket( sc ); continue; } // // int read_size = recv ( sc, radbuf, 16 * 1024, 0 ); } } Close( TRUE ); WSACleanup(); return 0; } тут вроде как и цикл не нужно проходить для проверки , я имею ввиду FD_ISSET .. ведь DS_SETSIZE можно сделать и больше 64 .. просто возвращает размер данных в буфере сокета и все .. или всетаки тут есть какойто серьезный баг |
Сообщ.
#13
,
|
|
|
![]() Быстренько тебе набросал, как в моем представлении все это должно выглядеть, работает - не работает уж не знаю, отладчиком походишь отладишь зы это даже компилиться на солярисе ![]() ![]() ![]() typedef std::vector<int> CSocketList; CSocketList socket_list; int sv_sock = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP ); if( sv_sock == -1 ) { return(EXIT_FAILURE); } int sock_value = 1; if ( setsockopt( sv_sock, SOL_SOCKET, SO_REUSEADDR,(char*)&sock_value, sizeof(sock_value)) != 0) { return(EXIT_FAILURE); } if ( setsockopt( sv_sock, IPPROTO_TCP, TCP_NODELAY,(char*)&sock_value, sizeof(sock_value)) != 0) { return(EXIT_FAILURE); } sockaddr_in svaddr; svaddr.sin_family = AF_INET; svaddr.sin_port = htons(15555); svaddr.sin_addr.s_addr = INADDR_ANY; if( bind( sv_sock, (sockaddr *)&svaddr, sizeof(svaddr) ) != 0) { return(EXIT_FAILURE); } if ( listen( sv_sock, FD_SETSIZE ) != 0 ) { return(EXIT_FAILURE); } socket_list.push_back(sv_sock); bool working = true; while(working) { fd_set fdr, fde, fdw; FD_ZERO(&fdr); FD_ZERO(&fdw); FD_ZERO(&fde); const size_t socket_list_size = socket_list.size(); for(size_t i = 0; i < socket_list_size; ++i) { FD_SET(socket_list[i], &fdr); FD_SET(socket_list[i], &fdw); FD_SET(socket_list[i], &fde); } int r = select(0, &fdr, &fdw, &fde, 0); if(r > 0) { for(size_t i = 0; i < socket_list_size; ++i) { int isListener; int dmSize = sizeof (isListener); if(getsockopt(socket_list[i], SOL_SOCKET, SO_ACCEPTCONN, (char*) & isListener, &dmSize) != 0) { return (EXIT_FAILURE); } if(FD_ISSET(socket_list[i], &fdw)) { } if(FD_ISSET(socket_list[i], &fdr)) { if(!isListener) { //if recv failed - disconnected } else { int new_sd = accept(socket_list[i], 0, 0); if(new_sd > 0) socket_list.push_back(new_sd); } } if(FD_ISSET(socket_list[i], &fde)) { if(isListener) return(EXIT_FAILURE); else { close(socket_list[i]); socket_list[i] = -1; } } } for(int i = socket_list_size - 1; i > 0; i--) { if(socket_list[i] == -1) socket_list.erase(socket_list.begin() + i); } } } а не, ошибся с fdw, когда кто то присоединяеться срабатывает fdr и судя по докам из msdn Цитата In summary, a socket will be identified in a particular set when select returns if: readfds: If listen has been called and a connection is pending, accept will succeed. Data is available for reading (includes OOB data if SO_OOBINLINE is enabled). Connection has been closed/reset/terminated. writefds: If processing a connect call (nonblocking), connection has succeeded. Data can be sent. exceptfds: If processing a connect call (nonblocking), connection attempt failed. OOB data is available for reading (only if SO_OOBINLINE is disabled). по fde нельзя отвал определить. В общем сиди ковыряйся, потом расскажешь ![]() |
Сообщ.
#14
,
|
|
|
ну ладно ... мне мой способ нравится .. потом какнибудь попробую его еще под нагрузкой протестировать
и вообще тему закрывается тут целая куча способов ! пусть каждый по себе выбирает !! ![]() Всем участвовавшим спасибо !!! |