socket always returns INVALID_SOCKET
i’m trying to create a multi threading server for a school project and i’m relying on a similar code that i wrote in the past but for some reason in the open function it always goes into the if statement and crashes. what stops it from creating the socket?
#pragma comment(lib, "Ws2_32.lib") #include #include #include #include #include #include #include #include #include void accept(); void open(); void close(); void bindAndListen(); #ifdef _DEBUG #include #define TRACE(msg, . ) printf(msg "\n", __VA_ARGS__); #else #define TRACE(msg, . ) printf(msg "\n", __VA_ARGS__); #define TRACE(msg, . ) #endif static const unsigned short PORT = 8826; static const unsigned int IFACE = 0; SOCKET _socket; void clientHandler(SOCKET client_socket); void close(); int main() < open(); >void bindAndListen() < struct sockaddr_in sa = < 0 >; sa.sin_port = htons(PORT); sa.sin_family = AF_INET; sa.sin_addr.s_addr = IFACE; if (bind(_socket, (struct sockaddr*) & sa, sizeof(sa)) == SOCKET_ERROR) throw std::exception(__FUNCTION__ " - bind"); TRACE("binded"); if (listen(_socket, SOMAXCONN) == SOCKET_ERROR) throw std::exception(__FUNCTION__ " - listen"); TRACE("listening. "); > void clientHandler(SOCKET client_socket) < std::cout void close() < TRACE(__FUNCTION__ " closing accepting socket"); try < closesocket(_socket); >catch (. ) <> > void open() < _socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (_socket == INVALID_SOCKET) throw std::exception(__FUNCTION__ " - socket"); >void accept() < SOCKET client_socket = accept(_socket, NULL, NULL); if (client_socket == INVALID_SOCKET) throw std::exception(__FUNCTION__); TRACE("Client accepted !"); // create new thread for client and detach from it std::thread tr(&clientHandler, _socket); tr.detach(); >
I wrote my first windows server just the other day, simply because I HAD to and Windows blows. Anyway, don’t you have to initialize with the DLL with WSA?learn.microsoft.com/en-us/windows/win32/api/winsock/…
Linux socket invalid socket
В ф-ии select есть сразу две проблемы.
Первая — это параметр nfds (первый параметр ф-ии select). Для Windows этот параметр не учитывается, для Unix это важный параметр который должен быть равен: максимальный дескриптор (из трех возможных fdset) плюс 1.
Вторая — второй, третий и четвертый параметры — fd_set.
Что представляет собой fd_set в Windows:
typedef struct fd_set < u_int fd_count; // how many are SET? SOCKET fd_array[FD_SETSIZE]; // an array of SOCKETs > fd_set;
Это просто массив и счетчик элементов в нем. В этот массив, как и в любой другой можно положить все что угодно. Даже то, что сокетом не является. В этом случае select просто вернет ошибку.
Что собой представляет fd_set в Linux и FreeBSD:
typedef struct fd_set < fd_mask fds_bits[howmany(FD_SETSIZE, NFDBITS)]; >fd_set;
Т.е. это всего лишь массив. Заполняется он так:
#define FD_SET(n, p) ((p)->fds_bits[(n)/NFDBITS] |= _fdset_mask(n))
Чем грозит такое отличие ? Например, под Windows можно написать следующий код:
SOCKET myset[1024]; for( int i = 0; i < 1024; i++ ) myset[i] = INVALID_SOCKET; // А чем еще инициализировать неинициализированные переменные ?
if( (ret = select( 0, &fdread, 0, 0, &tv )) > 0 ) < for( int i = 0; i < 1024; i++ ) if( FD_ISSET( myset[i], &fdread ) ) // do something >
И это будет работать.
В Linux и FreeBSD такой код сразу даст segmentation fault.
#define FD_ISSET(n, p) ((p)->fds_bits[(n)/NFDBITS] & _fdset_mask(n))
INVALID_SOCKET определен как -1, т.е. 0xFFFFFFFF
Например в FreeBSD NFDBITS отпределен как 32 ( 4*8 ). И мы пытаемся прочесть позицию 0x07FFFFFF в fds_bits[] из 32’ух возможных ((1024 + 31)/32).
Поэтому, тот же код с минимальными исправлениями для Linux и FreeBSD должен выглядеть так:
if( (ret = select( 0, &fdread, 0, 0, &tv )) > 0 ) < for( int i = 0; i < 1024; i++ ) if( myset[i] != INVALID_SOCKET ) if( FD_ISSET( myset[i], &fdread ) ) // do something >
Еще в Windows FD_ISSET определен через ф-ию:
#define FD_ISSET(fd, set) __WSAFDIsSet((SOCKET)(fd), (fd_set FAR *)(set))
И что-то подсказывает, что эта ф-ия защищена от сбоев по памяти и прочих неприятных вещей.
Кстати, INVALID_SOCKET для Linux и FreeBSD надо определять самому.
Первая проблема — это SIGPIPE. Сигнал, который посылается Unix системой приложению, если то пытается послать данные в сокет, соединение которого уже разорвано. В Windows в таком случае будет возвращена одна из ошибок.
Есть два метода борьбы с этим сигналом.
Первый — пригодный для Linux — установка флага (четвертый параметр ф-ии send() ) в MSG_NOSIGNAL.
Второй, пригодный для Linux и FreeBSD — установка обработчика сигнала, для SIGPIPE. Сам обработчик ничего не делает, просто при выходе из него программа продолжается дальше, а по ошибке, возвращаемой send, можно судить о разрыве соединения.
Вторая проблема — невозможность узнать, были ли реально отправлены данные. Теоретически это проблема не кросс-платформенного кода. В MSDN сказано: The successful completion of a send does not indicate that the data was successfully delivered.
Но практически, я ни разу не сталкивался с таким под Windows, и сразу столкнулся делая порт под FreeBSD.
Поэтому опишу проблему и решение здесь.
Разрыв соединения о котором не знают обе(или одна) стороны. Например, ваша программа под FreeBSD послала данные, ей вернулась ошибка 13 icmp — сокет будет принимать следующие данные для отправки, еще в течении некоторого времени. Они будут буфферизированы в исходящей очереди, и в конце концов утеряны, при закрытии соединения системой.
Один из способов борьбы — это поставить сокет в select(), во второй параметр (readfds). При возврате положительного числа попробовать прочесть 1 байт. Если recv вернет 0 — значит соединение было разорвано. (если вы не хотите читать из сокета, вызовите recv с флагом MSG_PEEK).
Может возникнуть ситуация когда данный метод не сработает. Например, FIN не доставлен, select с readfd вернет 0, а отправить нам пока нечего. О том что соединение разорвано мы так и не узнаем, пока не сделаем send (а он может, в структуре приложения, не сделан никогда). Параметр SO_KEEPALIVE мало пригоден. Для Linux, по умолчанию, детектирование соединения происходит раз в час, таймаут ожидания ответа — 15 минут. Т.е. 1 час 15 минут система будет считать что соединение есть.
Единственный способ борьбы — это реализация своего протокола подтверждения доставки пакета.
3. shutdown(), close() и closesocket()
В Windows принято, что после вызова closesocket() соединение закрывается. Так же, соединения закрываются при закрытии программы.
Система знает, какому процессу соответствуют сокеты, и закрывает их при смерти приложения. В Linix и FreeBSD это не так.
Необходимо явно сказать shutdown (посылка FIN) и close (разъединение дескриптора и сокета). Если этого не сделать, то после закрытия приложения сокеты еще будут висеть в системе некоторое время, в течение которого bind на «занятые» порты будет возвращать ошибку.
Такая же ситуация возникает если вы сделаете на серверной стороне приложения shutdown и close, закроете приложение, а на клиентской стороне эти ф-ии вызваны не будут. После этого Вы не сможете запустить серверное приложение в течении некоторого времени.
Методы борьбы — не известны. Только ждать.
В Windows есть только один механизм потоков, и он реализван в ядре. В Unix реализации потоков могут быть различны. Некоторые реализованны в пространстве ядра (posix thread для Linux и FreeBSD), другие могут быть в пространстве пользователя (у Рихтера они названы fibers).
Необходимо четко представлять чем Вы пользуетесь.
Потоки, реализованные в пространстве пользователя, имеют одну неприятную особенность — система о них не знает. Значит планировщик системы никогда не передаст управление другому потоку. Только процессу. Если же такой поток в процессе завис, то зависло и все приложение.
Простейший тест для определения в каком пространстве реализованы потоки (использован pthread, используйте другие ф-ии из выбранной вами библиотеки, аналогичные приведенным):
#include #include #include #include #include #include #include #include #include pthread_t th1; pthread_t th2; void* TH1(void* ) < printf( "TH1 %X\n", pthread_self() ); while( true ); > void* TH2(void* ) < int i; printf( "TH2 %X\n", pthread_self() ); for( i = 0; i < 10; i++ ) printf( "TH2 %X\n", pthread_self() ); sleep(5); for( i = 0; i < 10; i++ ) printf( "TH2 %X\n", pthread_self() ); > int main( int argc, char** argv ) < printf( "Main thread: %X\n", pthread_self() ); pthread_create( &th1, 0, TH1, 0 ); pthread_create( &th2, 0, TH2, 0 ); sleep( 600 ); >
Если Вы видите надпись TH2. менее чем 20 раз — значит потоки реализованы в пространстве пользователя.
Методы борьбы: переходить на pthread или не допускать зависания потоков.
В Windows, mutex созданый по умолчанию ведет себя рекурсивно. Т.е. сколько раз один и тот же поток вызвал для него lock, столько же раз должен быть вызван unlock.
pthread_mitex, по умолчанию, ведет себя иначе. Для того что бы его поведение соответствовало Windows mutex (по умолчанию) необходимо создать его следующим образом:
pthread_mutexattr_t attrs; pthread_mutexattr_init(&attrs); pthread_mutexattr_settype(&attrs, PTHREAD_MUTEX_RECURSIVE); pthread_mutex_init( &m_Mutex, &attrs );
В Windows системные исключения и исключения C++ смешаны (это точно, если вы пользуетесь MS VC). Например, сбой по памяти, попытка деления на 0, и пр. ловятся с помощью catch(. ). В Unix это не так (точно, если использовать gcc). В таких случаях приложению посылаются сигналы.
В большинстве случаев, после такого сигнала приложение можно только закрыть.
Windows код:
try < char buffer[1024]; strcpy( buffer, str ); > catch(. ) < // Внутреняя ошибка. Ну и черт с ней. Работаем дальше. >
try < char buffer[1024]; strcpy( buffer, str ); > catch(. ) < // Здесь мы никогда не будем. И скорей всего, приложение уже закрыто. Премии мне не видать. >
Методы борьбы: писать приложение так, что бы не допустить подобных ситуаций.
P.S. Все что касается сокетов — относится к TCP соединениям. Причем считается, что все используется по умолчанию. Т.е. без SO_LINGER и т.д.
P.P.S. Тема остается открытой для тех, кому есть что добавить.
Linux socket invalid socket
Друзья, имеется проблема при работе с сокетами под Linux’ами.
Задаём опции и подключаемся к удалённой машине по локальной сети:
COptionsSP options_w; /// класс для чтения конфигурационного файла, в котором задаются номера портов и т. д.
Ну, и, собственно, функции отсылки:
bool CConnection::send_packet_impl(const char *pHead, unsigned long ulHeadLen, const char *pData, unsigned long ulLen)
А теперь суть проблемы. Если код под Windows работает без проблем, то реализация для Linux страдает. Хотя функция select в send_to_service возвращает управление со значением больше 0, при попытке сразу же вызвать send валится исключение EAGAIN. Такое происходит при больших порциях отправляемых данных (например, при попытке отослать 500 Мбайт). В течение минуты сокет дохнет, а ядро шлёт connection timed out, от чего клиент напрочь отваливается. Что делаю не так?
///делаем неблокирующим
unsigned long ulNonBlockingMode = 1;
if (ioctlsocket(m_apClient->getSock(), FIONBIO, &ulNonBlockingMode) < 0)
It is possible to do non-blocking IO on sockets by setting the O_NONBLOCK flag on a socket file descriptor using fcntl(2). Then all operations that would block will (usually) return with EAGAIN (operation should be retried later); connect(2) will return EINPROGRESS error.
Добавлено 20.12.11, 07:16
да. и почему бы не использовать что-то готовое, вроде boost.asio?