C C ++ - класс сокетов TCP: проблема получения - PullRequest
3 голосов
/ 25 февраля 2010

Я создал свой собственный класс Socket, чтобы иметь возможность отправлять и получать HTTP-запросы. Но у меня все еще есть некоторые проблемы. Следующий код (моя функция приема) по-прежнему глючит и иногда падает. Я попытался отладить его, но он должен быть где-то в арифметике указателя / управлении памятью.

int Socket::Recv(char *&vpszRecvd)
{
 //vpszRecvd = NULL;
 int  recvsize = 0;
 char TempBuf[1024];
 int  Result = 0;
 char* temp;


 do
 {
  memset(TempBuf, 0, sizeof(TempBuf));

  Result = recv( this->sSocket, TempBuf, sizeof(TempBuf) -1, 0 );
  if (recvsize == 0)
   recvsize = Result;

  if ( Result > 0 )
  {
   if ( vpszRecvd != NULL )
   {
    if (temp == NULL)
    {
     temp = (char*)calloc(recvsize + 1, sizeof(char));
    }
    else
    {
     realloc(temp, recvsize + 1);
    }
    if (temp == NULL)
     return 0;

    memcpy(temp, vpszRecvd, recvsize);
    realloc(vpszRecvd, recvsize + Result);

    if (vpszRecvd == NULL)
     return 0;

    memset(vpszRecvd, 0, recvsize + Result);
    memcpy(vpszRecvd, TempBuf, Result);
    memcpy(vpszRecvd + recvsize, TempBuf, Result);
    recvsize += Result; 
   }
   else
   {
    realloc(vpszRecvd, Result);

    if (vpszRecvd == NULL)
     return 0;

    memset(vpszRecvd, 0, Result);
    memcpy(vpszRecvd, TempBuf, Result);
    recvsize += Result;
   }
  }
  else if (  Result == 0 )
  {
   return recvsize;

  }
  else //if (  Result == SOCKET_ERROR )
  {
   closesocket(this->sSocket);
   this->sSocket = INVALID_SOCKET;
   return SOCKET_ERROR;
  }
 }
 while( Result > 0 );

 return recvsize;
}

Кто-нибудь видит что-нибудь, что может вызвать сбой, или у кого-нибудь есть лучший, быстрый, маленький и стабильный пример получения полного пакета через recv ()?

Я не могу использовать строки, хотя это должно быть сделано с помощью символов.

Спасибо за вашу помощь.

Ответы [ 2 ]

7 голосов
/ 25 февраля 2010

Вы не инициализируете temp, и, кроме того, ваш звонок на realloc неправильный. Должно быть:

temp = realloc (temp, recvsize+1);

Когда вы звоните realloc, как вы, вы выбрасываете новый адрес, и есть большая вероятность, что старый адрес теперь освобожден. Все ставки отменяются, когда вы пытаетесь разыменовать его.

Причина, по которой realloc возвращает новый адрес, заключается в том, что расширение буфера может потребовать его перемещения, если этот текущий блок окружен областью памяти (другими словами, он не может просто развернуться в свободный блок после него ). В этом случае на арене будет создан новый блок, содержимое которого будет перенесено из старого блока, а старый блок будет освобожден. Вы должны получить возвращаемое значение от realloc в случае, если это произойдет.

Имейте в виду, что realloc не имеет для возврата нового указателя, он может дать вам такой же указатель, если, например, после блока было достаточно свободного места, чтобы удовлетворить новый размер или, если вы уменьшаете размер.

Он также может возвращать NULL, если не может расширить блок, вы должны также следить за этим, особенно если:

temp = realloc (temp, newsize);

приведет к утечке памяти при возврате NULL (это не освобождает старый блок).

Несколько других вещей:

  • вам редко нужно использовать calloc, особенно в этом случае, так как вы все равно копируете память.
  • аналогично, вам не нужно memset порция памяти равной 0, если вы сразу же наберете memcpy поверх него.
  • при условии, что вы инициализировали temp до NULL, вы можете просто использовать realloc без его тестирования. Это потому, что realloc(NULL,7) идентичен malloc(7) - realloc вполне может начинаться с нулевого указателя.
  • , поскольку вам не нужно calloc, это только для образования - sizeof(char) это всегда 1 по определению.
  • Вы, кажется, делаете огромное количество ненужного копирования данных.

Почему бы нам не начать с чего-то более простого? Теперь, это полностью из моей головы, так что могут быть некоторые ошибки, но это, по крайней мере, отрезано от бегущего в памяти бегемота в вопросе :-), поэтому должно быть легче отлаживать.

Это в основном разбито на:

  • инициализировать пустое сообщение.
  • войти в бесконечный цикл.
    • получить сегмент.
    • если произошла ошибка, освободить все и вернуть ошибку.
    • если сегментов больше нет, вернуть текущее сообщение.
    • создать место для нового сегмента в конце сообщения.
    • если пространство не может быть создано, освободите все и верните пустое сообщение.
    • добавить сегмент к сообщению и настроить размер сообщения.

и код выглядит так:

int Socket::Recv(char *&vpszRecvd) {
    int  recvsize = 0;
    char TempBuf[1024];
    int  Result = 0;
    char *oldPtr;

    // Optional free current and initialise to empty.

    //if (vpszRecvd != NULL) free (vpszRecvd);
    vpszRecvd = NULL;

    // Loop forever (return inside loop on end or error).

    do {
        Result = recv( this->sSocket, TempBuf, sizeof(TempBuf) -1, 0 );

        // Free memory, close socket on error.

        if (Result < 0) {
            free (vpszRecvd);
            closesocket(this->sSocket);
            this->sSocket = INVALID_SOCKET;
            return SOCKET_ERROR;
        }

        // Just return data and length on end.

        if (Result == 0) {
            return recvsize;
        }

        // Have new data, use realloc to expand, even for initial malloc.

        oldPtr = vpszRecvd;
        vpszRecvd = realloc (vpszRecvd, recvsize + Result);

        // Check for out-of-memory, free memory and return 0 bytes.

        if (vpszRecvd == NULL) {
            free (oldPtr);
            return 0;
        }

        // Append it now that it's big enough and adjust the size.

        memcpy (&(vpszRecvd[recvsize], TempBuf, Result);
        recvsize += Result;
    } while (1);
}
0 голосов
/ 25 февраля 2010

У меня была именно эта проблема совсем недавно.

Realloc работает медленно. Recv это быстро. Несколько сотен перераспределений в секунду приведут к краху.

calloc () не только recvsize + 1, но и буфер на пару килобайт дополнительно. realloc () только тогда, когда буфер будет заполнен / переполнен, и даст ему еще несколько килобайт дополнительно на каждый realloc ().

ниже приведен фрагмент кода, который я использую для добавления данных в мой выходной поток, но ввод должен быть очень похожим. (примечание: buf_out_size - размер выделенного буфера, buf_out_len - объем данных, находящихся в данный момент в буфере.)

    void Netconsole::ParseOutput(int sock, std::string raw)
    {


        //if too small, realloc with raw.size() + BUFFSIZE.
        if (s[sock]->buf_out_len + raw.size() > s[sock]->buf_out_size)
        {
            s[sock]->buf_out_size += raw.size() + BUFFSIZE;
            s[sock]->buf_out = (char*) realloc( s[sock]->buf_out, s[sock]->buf_out_size);
        }

        // append new data to the end of the buffer.
        if(s[sock]->buf_out != NULL)
        {
            memcpy(s[sock]->buf_out + s[sock]->buf_out_len, raw.c_str(), raw.size());
            s[sock]->buf_out_len += raw.size();

        }
        else
        {
            s[sock]->ending = true;
    #if DEBUG_PRINT_TCP
            printf("%s TCP[%d] dies from out of memory, realloc error\r\n",Debug::MTimestamp(),sock);
    #endif
        }
    }
...