
![]() |
Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
|
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[216.73.216.207] |
![]() |
|
Сообщ.
#1
,
|
|
|
Друзья, имеется проблема при работе с сокетами под Linux'ами.
Создаю сокет: ![]() ![]() bool CTcpClient::createSocket() { SOCKET sock = socket(AF_INET, SOCK_STREAM, 0); if(sock == INVALID_SOCKET) { LOG_SOCKET_ERROR("create socket failed"); return false; } int opt; int optlen = sizeof(opt); int iRet; opt = 64*1024; iRet = setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (char*)&opt, optlen); if (iRet != 0) { LOG_SOCKET_ERROR("setsockopt SO_SNDBUF failed"); return false; } iRet = setsockopt( sock, SOL_SOCKET, SO_RCVBUF, (char*)&opt, optlen ); if (iRet != 0) { LOG_SOCKET_ERROR("setsockopt SO_RCVBUF failed"); return false; } setSock(sock); /// возвращаю созданный сокет в класс CTcpClient return true; } Задаём опции и подключаемся к удалённой машине по локальной сети: ![]() ![]() void CConnection::connect_to_service() { COptionsSP options_w; /// класс для чтения конфигурационного файла, в котором задаются номера портов и т. д. TMultiConfigWraper multi_options; if (INodeSP parent = m_wpParent.lock()) options_w = multi_options->getOptions(parent->GetCfgFile()); else options_w = multi_options->getOptions(""); m_conn_state = connecting; /* enum EConnState { connecting, worked, aborted }; EConnState m_conn_state; */ fd_set set; struct timeval timeout; int numsocks = 0; timeout.tv_sec = 0; timeout.tv_usec = 100000; // к кому подключаемся unsigned short usPort = options_w->getListenPort(); SOCKADDR_IN ServAddr; ServAddr.sin_family = AF_INET; ServAddr.sin_port = htons(usPort); ServAddr.sin_addr.s_addr = GetAddress(); for (unsigned long i = 0; i < m_conn_try; ++i) { if (connect(m_apClient->getSock(), (SOCKADDR *)&ServAddr, sizeof(SOCKADDR_IN)) != SOCKET_ERROR) /// boost::shared_ptr<CTcpClient> m_apClient; { FD_ZERO(&set); FD_SET(m_apClient->getSock(), &set); numsocks = select(m_apClient->getSock(), 0, &set, 0, &timeout ); if (numsocks == SOCKET_ERROR) { LOG_SOCKET_ERROR("select failed"); work_tools::sleep(100); /* inline void sleep(int ms) { boost::xtime delay; to_time(ms, delay); boost::thread().sleep(delay); } */ continue; } SOCKADDR_IN local_addr; int len = sizeof(SOCKADDR_IN); if (getsockname( m_apClient->getSock(), (SOCKADDR *)&local_addr, (socklen_t*)&len) == SOCKET_ERROR) { LOG_SOCKET_ERROR("getsockname failed"); } ///делаем неблокирующим unsigned long ulNonBlockingMode = 1; if (ioctlsocket(m_apClient->getSock(), FIONBIO, &ulNonBlockingMode) < 0) { LOG_SOCKET_ERROR("ioctlsocket failed"); } m_apClient->setPort(ntohs(local_addr.sin_port)); // можно отправлять m_connect_event.set(connected); m_conn_state = worked; return; } else { LOG_SOCKET_ERROR("connect failed"); } work_tools::sleep(100); } m_conn_state = aborted; } Ну, и, собственно, функции отсылки: ![]() ![]() bool CConnection::send_packet_impl(const char *pHead, unsigned long ulHeadLen, const char *pData, unsigned long ulLen) { if( !m_apClient ) return false; #ifdef WIN32 DWORD sended = 0; WSABUF wbuf[2]; wbuf[0].buf = const_cast<char*>(pHead); wbuf[0].len = ulHeadLen; wbuf[1].buf = const_cast<char*>(pData); wbuf[1].len = ulLen; while (true) { if(WSASend(m_apClient->getSock(), wbuf, 2, &sended, 0, NULL, NULL ) < 0) { LOG_SOCKET_ERROR("send failed"); if(WSAGetLastError() == WSAECONNRESET) { m_conn_state = connecting; return false; } work_tools::sleep(5); continue; } if(sended < ulHeadLen + ulLen) LOG( "data loss"); return true; } #else send_to_service( pHead, ulHeadLen ); send_to_service( pData, ulLen ); #endif } #ifndef WIN32 ![]() ![]() bool CConnection::send_to_service(const char *pData, unsigned long ulLen) { if( !m_apClient ) return false; unsigned long total = 0; int n; while(total < ulLen) { struct timeval tv; int retval; tv.tv_sec = 1; tv.tv_usec = 0; fd_set rfds; FD_ZERO(&rfds); FD_SET(m_apClient->getSock(), &rfds); retval = select(m_apClient->getSock() + 1, NULL, &rfds, NULL, &tv); if (retval > 0) /// есть дескрипторы, готовые принять данные { n = send(m_apClient->getSock(), pData + total, ulLen - total, MSG_CONFIRM); if (n == SOCKET_ERROR) { if (total > 0) LOG_ERROR("send failed on pos" << total); LOG_SOCKET_ERROR("send failed"); if (errno == EWOULDBLOCK) { work_tools::sleep(100); /// почему-то всё время заходит сюда } if(errno == ECONNRESET) { m_conn_state = connecting; return false; } } else { total += n; } } if (retval == 0) /// вернули управление по таймауту, дескрипторов нет { work_tools::sleep(100); } if (retval < 0) { if ( errno == EWOULDBLOCK ) { work_tools::sleep(100); } if(errno == ECONNRESET) { m_conn_state = connecting; return false; } } } } А теперь суть проблемы. Если код под Windows работает без проблем, то реализация для Linux страдает. Хотя функция select в send_to_service возвращает управление со значением больше 0, при попытке сразу же вызвать send валится исключение EAGAIN. Такое происходит при больших порциях отправляемых данных (например, при попытке отослать 500 Мбайт). В течение минуты сокет дохнет, а ядро шлёт connection timed out, от чего клиент напрочь отваливается. Что делаю не так? |
Сообщ.
#2
,
|
|
|
Цитата Stuart @ ///делаем неблокирующим unsigned long ulNonBlockingMode = 1; if (ioctlsocket(m_apClient->getSock(), FIONBIO, &ulNonBlockingMode) < 0) а где этот же фрагмент, но для линукс? ioctlsocket на линуксах никогда не встречал. Цитата Stuart @ при попытке сразу же вызвать send валится исключение EAGAIN. а чего вы ожидали? >man 7 socket Цитата 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. Добавлено да. и почему бы не использовать что-то готовое, вроде boost.asio? |
Сообщ.
#3
,
|
|
|
На linux вызываю fcntl. boost::asio, увы, использовать не могу, так как код должен работать на kernel 2.4.
По цитате из дока так и не понял, что не так делаю. Вызываю же select перед каждым вызовом send, чтобы EAGAIN как раз не генерировался, так как сокет уже готов для записи и, по идее, отправил все данные. |
Сообщ.
#4
,
|
|
|
Цитата Stuart @ код должен работать на kernel 2.4 будет работать: http://www.boost.org/doc/libs/1_48_0/doc/html/boost_asio/overview/implementation.html Цитата Stuart @ Вызываю же select перед каждым вызовом send ну и зачем перед? ну допустим, select тебе сообщил что сокет готов. ты в него пихаешь 32к, и сокет тебе может вернуть EAGAIN. чем тебе select тут поможет? да и вообще, не нужен тут select. |
Сообщ.
#5
,
|
|
|
На boost'ы времени нет переводить программу сейчас. Ошибку понял, только как исправить? Пробовал select и после send вставлять в блоке ошибки EAGAIN, управление возвращал только после выхода из select со значением больше 0. То есть буфер очищался и был готов принять следующий блок данных.
Да, если поставить тупой sleep в блоке EAGAIN на секунд 5, проблем нет... Но всё работает очень медленно, и такой код мне не нравится. |
Сообщ.
#6
,
|
|
|
Цитата Stuart @ На boost'ы времени нет переводить программу сейчас. так совсем недавно причина неиспользования boost.asio была другой. Цитата Stuart @ как исправить? выбросить select нафег, и в цикле, пока не будет отослана сумма байт всего буфера, проверять errno и повторять. ![]() ![]() const char* ptr = ... const int len = ... int summ = 0; do { int n = send(sock, ptr+summ, len-summ, 0); if ( errno == EAGAIN ) { summ += n; } } while ( summ < len ); Добавлено Цитата niXman @ if ( errno == EAGAIN ) { summ += n; } только тут еще сделай нормальные проверки на допустимые ошибки, естественно. а то мне влом. |
Сообщ.
#7
,
|
|
|
Наверное, имели в виду if (errno != EAGAIN) summ += n?
|
Сообщ.
#8
,
|
|
|
если send() возвращает >= 0, значит ошибки не произошло. но errno может установится в EAGAIN, и это не означает что произошла ошибка.
|
![]() |
Сообщ.
#9
,
|
|
Цитата niXman @ Разве send() не вернёт -1 вместе с EAGAIN? если send() возвращает >= 0, значит ошибки не произошло. но errno может установится в EAGAIN, и это не означает что произошла ошибка. |
Сообщ.
#10
,
|
|
|
С равенством не приходит даже 1 кбайт.
![]() |
Сообщ.
#11
,
|
|
|
Цитата Stuart @ С равенством не приходит даже 1 кбайт. покажи значения errno и n после попытки отправить 1кб. |
Сообщ.
#12
,
|
|
|
1 кбайт просто не приходит, в errno == EAGAIN не заходит. Заходит при посылке 100 Кбайт 2 раза:
errno == 11, n == -1 errno == 11, n == 0 Если послать метр: errno == 11, n == 19 errno == 11, n == 49133 errno == 11, n == 19 errno == 11, n == 49133 errno == 11, n == 19 errno == 11, n == 49133 errno == 11, n == 19 errno == 11, n == 16449 errno == 11, n == -1 (так до конца) |
Сообщ.
#13
,
|
|
|
Stuart, ну так напиши условие обработки ошибок правильно. я ж от балды написал. ради примера на что акцентировать внимание.
к тому же, еще и явно указал - Цитата niXman @ еще сделай нормальные проверки на допустимые ошибки, естественно. а то мне влом. Добавлено ошибкой в твоем случае будет n == -1. или ты совсем маны не читаешь? Добавлено Цитата Stuart @ errno == 11 это код EAGAIN ? |
Сообщ.
#14
,
|
|
|
Да, 11 - это код EAGAIN. Обработчик ошибок есть, но кроме 11 ошибки больше никаких вообще. Не понятно, почему даже 1 кбайт не приходит.
|
Сообщ.
#15
,
|
|
|
Цитата Stuart @ Не понятно, почему даже 1 кбайт не приходит. что значит "не приходит" ? |
Сообщ.
#16
,
|
|
|
В recv данных нет. С моей логикой доходили, но при больших объёмах данных сокет отваливается.
|
![]() |
Сообщ.
#17
,
|
|
Покажи лучше получившийся код...
|
Сообщ.
#18
,
|
|
|
![]() ![]() bool CConnection::send_to_service( const sinom_char *pData, sinom_ulong ulLen ) { if( !m_apClient ) return false; unsigned long total = 0; int n; do { n = send( m_apClient->getSock(), pData + total, ulLen - total, 0 ); if ( errno == EAGAIN ) { total += n; } if (n == SOCKET_ERROR) { if (errno == ECONNRESET) { m_conn_state = connecting; return false; } LOG("error: " << errno << " "); } } while (total < ulLen); return true; } |
![]() |
Сообщ.
#19
,
|
|
Цитата Stuart @ ![]() ![]() n = send( m_apClient->getSock(), pData + total, ulLen - total, 0 ); if ( errno == EAGAIN ) { total += n; } по-моему, правильнее такой вариант: ![]() ![]() n = send( m_apClient->getSock(), pData + total, ulLen - total, 0 ); if (n == -1) { if ( errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR ) { continue; // не получилось отправить - сигнал или недостаточно места в буфере } else { break; // а вот это уже ошибка. оправить, да, не получилось } } total += n; * обработка EINTR - по желанию, иногда лучше проверить что пришло |
Сообщ.
#20
,
|
|
|
Если сделать так, то возникнет ошибка, о которой писал в начале темы: если подать на вход функции достаточно большое количество данных (около 300 Мбайт на локальном хосте и всего 100 Кбайт по локальной сети), то сокет просто отваливается по таймауту.
|
Сообщ.
#21
,
|
|
|
Я делал отсылку всё-таки с использованием select. Подгоняя к коду negram'a как-то так получается:
![]() ![]() int timeout_counter = 100; ... n = send( m_apClient->getSock(), pData + total, ulLen - total, 0 ); if (n == -1) { if ( errno == EAGAIN || errno == EWOULDBLOCK) { // не получилось отправить - недостаточно места в буфере fd_set fd; FD_ZERO(&fd); FD_SET(m_socket, &fd); timeval tv = {0, 10000}; int nfds = m_apClient->getSock(); if( select(nfds + 1, 0, &fd, 0, &tv) != SOCKET_ERROR ) { if( !FD_ISSET(m_socket, &fd) ) { if( --timeout_counter == 0 ) break; //timeout } else timeout_counter = 100; continue; } } else if( errno == EINTR ) continue;// не получилось отправить - сигнал break; // а вот это уже ошибка. оправить, да, не получилось } total += n; |
Сообщ.
#22
,
|
|
|
Друзья, спасибо всем за помощь. Хотя, к сожалению, ничего кроме больших sleep'ов после send'а не помогает.
![]() |
Сообщ.
#23
,
|
|
|
Stuart, если есть желание, напиши компилябельный+минимальный код клиента и сервера воспроизводящий ошибку. мне влом писать.
ошибку исправлю. |
Сообщ.
#24
,
|
|
|
Цитата Stuart @ Хотя, к сожалению, ничего кроме больших sleep'ов после send'а не помогает. ![]() Попробуй взять тест отсюда: RSDN Просто интересно - будет работать или нет ? |