C C++ - TCP Socket 类:接收问题

3

我自己写了一个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()接收完整数据包?

我不能使用字符串,必须使用字符。

感谢您的帮助。


memcpy(vpszRecvd, TempBuf, Result); 我现在会进行编辑。 - maxedmelon
3
这个问题标记为 C++。因此,通过使用 std::vector<char> 缓冲区来摆脱所有内存问题。可以使用其 resize() 成员函数设置其大小,并且如果需要将 char* 传递给 C 函数,则使用 &v[0](或 &v.begin()):recv(this-Socket,&v [0],v.size(),0) - sbi
就像我说的,我不能用向量来做这件事,必须使用字符来完成。向量 / 字符串在这里会很好 :-( - maxedmelon
你用vector<char>做不了什么事情,而用char*可以做到呢?我只是好奇。 - ereOn
2个回答

7

您没有初始化temp,而且您调用realloc的方式是错误的。正确的方式应该是:

temp = realloc (temp, recvsize+1);

当您像这样调用realloc时,您会丢弃新地址,并且有很大的可能性旧地址现在已被释放。当您尝试引用它时,所有赌注都关闭了。 realloc返回新地址的原因是,如果当前块被包围在内存区域中,则必须将缓冲区扩展到新位置(换句话说,它不能仅扩展到其后面的空闲块)。在这种情况下,将在竞技场中创建一个新块,将内容从旧块转移并释放旧块。您必须获取realloc的返回值以防发生这种情况。
请记住,realloc不一定要返回新指针,例如,如果块后面有足够的空闲空间来满足新大小或者如果您正在减小大小,则可以给您相同的指针。
它也可以返回NULL,如果无法扩展块,则应特别注意,因为:
temp = realloc (temp, newsize);

当返回NULL时,它不会释放旧块,这将导致内存泄漏。

还有一些其他事情:

  • 你很少需要使用 calloc ,特别是在这种情况下,因为你仍然要复制内存。
  • 同样,如果你立即要 memcpy ,则不需要将内存块 memset 为0。
  • 只要将 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);
}

现在已经改变了。它显示了2663个接收字节(最多应该是218或400),以及一个空字符串。对于普通的HTTP请求来说,这看起来不太好。 - maxedmelon
@maxedmelon,你改了 所有 的realloc吗? - paxdiablo
谢谢,有一些小错别字,但现在它像魔法一样工作! - maxedmelon
@maxedmelon,哪些是错别字?我也可以修复代码。我在早期的评论中看到了两个错别字和realloc拼写错误,我已经修复了它们。 - paxdiablo
vpszRecvd = (char*)realloc(vpszRecvd, recvsize + Result); - maxedmelon
+1 我希望这个网站上的所有答案都像这个一样经过深思熟虑和信息丰富。如果可以的话,我会给它 +2 的评价。 - Josh

0

我最近也遇到了这个问题。

realloc很慢,而recv很快。每秒数百个realloc会导致崩溃。

在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
        }
    }

听起来不错。你有示例代码吗?我猜我的代码有很多错误,有几个东西会崩溃。 我甚至没有找到一个基于字符接收整个数据包的接收代码。 - maxedmelon
为什么每秒几百次的realloc会导致崩溃? - sth
嗯,我不这么认为,@SF。如果你不够快,TCP实际上会等待你。接收只取决于你调用它的速度。 - paxdiablo
无论如何,我添加了一些额外的字节,现在不会崩溃了,但返回空字符数组或纯屎... - maxedmelon
@maxedmelon,正如你所说的那样,它返回了奶牛粪便,这是因为realloc - 请看我的答案。 - paxdiablo
当然,我错过了buffer = realloc(buffer,...);的需要 - 肯定丢弃新地址是没有帮助的。至于大量调用realloc(),+BUFFSIZE在我的情况下解决了崩溃问题。 - SF.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接