На главную Наши проекты:
Журнал   ·   Discuz!ML   ·   Wiki   ·   DRKB   ·   Помощь проекту
ПРАВИЛА FAQ Помощь Участники Календарь Избранное RSS
msm.ru
  
    > Проблема с сокетами под Linux
      Друзья, имеется проблема при работе с сокетами под Linux'ами.

      Создаю сокет:

      ExpandedWrap disabled
        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;
        }


      Задаём опции и подключаемся к удалённой машине по локальной сети:

      ExpandedWrap disabled
        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;
        }


      Ну, и, собственно, функции отсылки:

      ExpandedWrap disabled
        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


      ExpandedWrap disabled
        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, от чего клиент напрочь отваливается. Что делаю не так?
        Цитата 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?
          На linux вызываю fcntl. boost::asio, увы, использовать не могу, так как код должен работать на kernel 2.4.

          По цитате из дока так и не понял, что не так делаю. Вызываю же select перед каждым вызовом send, чтобы EAGAIN как раз не генерировался, так как сокет уже готов для записи и, по идее, отправил все данные.
          Сообщение отредактировано: Stuart -
            Цитата 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.
            Сообщение отредактировано: niXman -
              На boost'ы времени нет переводить программу сейчас. Ошибку понял, только как исправить? Пробовал select и после send вставлять в блоке ошибки EAGAIN, управление возвращал только после выхода из select со значением больше 0. То есть буфер очищался и был готов принять следующий блок данных.

              Да, если поставить тупой sleep в блоке EAGAIN на секунд 5, проблем нет... Но всё работает очень медленно, и такой код мне не нравится.
                Цитата Stuart @
                На boost'ы времени нет переводить программу сейчас.

                так совсем недавно причина неиспользования boost.asio была другой.

                Цитата Stuart @
                как исправить?

                выбросить select нафег, и в цикле, пока не будет отослана сумма байт всего буфера, проверять errno и повторять.
                ExpandedWrap disabled
                  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;
                   }

                только тут еще сделай нормальные проверки на допустимые ошибки, естественно. а то мне влом.
                Сообщение отредактировано: niXman -
                  Наверное, имели в виду if (errno != EAGAIN) summ += n?
                    если send() возвращает >= 0, значит ошибки не произошло. но errno может установится в EAGAIN, и это не означает что произошла ошибка.
                    Сообщение отредактировано: niXman -
                      Цитата niXman @
                      если send() возвращает >= 0, значит ошибки не произошло. но errno может установится в EAGAIN, и это не означает что произошла ошибка.
                      Разве send() не вернёт -1 вместе с EAGAIN?
                      Сообщение отредактировано: negram -
                        С равенством не приходит даже 1 кбайт. :( Так как n == -1.
                        Сообщение отредактировано: Stuart -
                          Цитата Stuart @
                          С равенством не приходит даже 1 кбайт.

                          покажи значения errno и n после попытки отправить 1кб.
                            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 (так до конца)
                              Stuart, ну так напиши условие обработки ошибок правильно. я ж от балды написал. ради примера на что акцентировать внимание.
                              к тому же, еще и явно указал -
                              Цитата niXman @
                              еще сделай нормальные проверки на допустимые ошибки, естественно. а то мне влом.


                              Добавлено
                              ошибкой в твоем случае будет n == -1.

                              или ты совсем маны не читаешь?

                              Добавлено
                              Цитата Stuart @
                              errno == 11

                              это код EAGAIN ?
                                Да, 11 - это код EAGAIN. Обработчик ошибок есть, но кроме 11 ошибки больше никаких вообще. Не понятно, почему даже 1 кбайт не приходит.
                                  Цитата Stuart @
                                  Не понятно, почему даже 1 кбайт не приходит.

                                  что значит "не приходит" ?
                                    В recv данных нет. С моей логикой доходили, но при больших объёмах данных сокет отваливается.
                                      Покажи лучше получившийся код...
                                        ExpandedWrap disabled
                                          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;
                                          }
                                        Сообщение отредактировано: Stuart -
                                          Цитата Stuart @
                                          ExpandedWrap disabled
                                                    n = send( m_apClient->getSock(), pData + total, ulLen - total, 0 );
                                                    if ( errno == EAGAIN )
                                                    {
                                                        total += n;
                                                    }

                                          по-моему, правильнее такой вариант:
                                          ExpandedWrap disabled
                                               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 - по желанию, иногда лучше проверить что пришло
                                            Если сделать так, то возникнет ошибка, о которой писал в начале темы: если подать на вход функции достаточно большое количество данных (около 300 Мбайт на локальном хосте и всего 100 Кбайт по локальной сети), то сокет просто отваливается по таймауту.
                                              Я делал отсылку всё-таки с использованием select. Подгоняя к коду negram'a как-то так получается:
                                              ExpandedWrap disabled
                                                   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;
                                                Друзья, спасибо всем за помощь. Хотя, к сожалению, ничего кроме больших sleep'ов после send'а не помогает. :( Видимо, придётся переводить на boost::asio.
                                                  Stuart, если есть желание, напиши компилябельный+минимальный код клиента и сервера воспроизводящий ошибку. мне влом писать.
                                                  ошибку исправлю.
                                                    Цитата Stuart @
                                                    Хотя, к сожалению, ничего кроме больших sleep'ов после send'а не помогает. :(

                                                    Попробуй взять тест отсюда: RSDN
                                                    Просто интересно - будет работать или нет ?
                                                    1 пользователей читают эту тему (1 гостей и 0 скрытых пользователей)
                                                    0 пользователей:


                                                    Рейтинг@Mail.ru
                                                    [ Script execution time: 0,0612 ]   [ 15 queries used ]   [ Generated: 18.07.25, 01:09 GMT ]