【问题标题】:Socket data corruption套接字数据损坏
【发布时间】:2014-08-10 15:34:27
【问题描述】:

我正在开发一个简单的套接字客户端,它向服务器发送一个简单的字母“p”,然后从服务器读取响应。除了一个令人困惑的问题外,它正在充分发挥作用。第一次读取套接字时(它发生在循环中),数据是乱码和损坏的,结果像“ÿýÿû”和“μÞv”。在第一次响应后收到的所有数据都很好并且有效。

我用来接收的代码是:

int n;
char buffer[256];
bzero(buffer,256);
strcpy(buffer, "p");
n = write(manSock,buffer,256);
if (n < 0)
{
 error("ERROR writing to management server");
}
bzero(buffer,256);
n = read(manSock,buffer,256);
if (n < 0)
{
 error("ERROR reading from management server");
}
return buffer;

manSock 是套接字文件描述符。

关于为什么会发生这种情况的任何想法?

【问题讨论】:

  • 除了缓冲区,你是返回长度吗?或者您是否尝试在打印之前在末尾添加“\0”?缓冲区[n] = '\0';
  • 这是函数的一部分吗?如果是这样,那么返回局部变量buffer 的地址是未定义的行为。请编辑您的帖子以表明这确实是您发布的函数调用
  • 另外,你可能想澄清为什么你有 C++ 标签而不是 C(只是为了消除混淆)。
  • OT:总是将read()/write() 的结果与函数被告知读/写的数量进行比较!相关:stackoverflow.com/a/24474150/694576
  • 还有read()write()返回ssize_t而不是int

标签: c sockets


【解决方案1】:

这似乎不是与套接字有关的问题,而是与内存管理有关的问题。

您似乎返回了一个指向内存的指针,该指针仅在函数本地有效。

假设您的“真实”代码如下所示

char * foo(void)
{
  char buffer[256];

  /* read into buffer */

  return buffer;
}

void bar (void)
{
  char * p = foo();
  printf("%s\n", p);
}

那么p 指的是foo() 返回后的无效内存,因为在foo() 返回时缓冲区已被隐式释放。

解决这个问题

  • 使用malloc(), calloc()strdup()动态分配foo()中的buffer

    char * foo(void)
    {
      char * buffer = malloc(256);
      memset(buffer, 0, 256);
      /* read into buffer */
      return buffer;
    }
    

    char * foo(void)
    {
      char * buffer = calloc(256, sizeof(*buffer));
      /* read into buffer */
      return buffer;
    }
    

    char * foo(void)
    {
      char buffer[256] = {0};
      /* read into buffer */
      return strdup(buffer);
    }
    
  • 或将对bar()(或更高版本)中分配的缓冲区的引用传递给foo()

    void foo(char * buffer)
    {
       /* read into where buffer points */
    }
    
    void bar(void)
    {
      char buffer[256] = {0};
      foo(buffer);
      /* print buffer */
    }
    

【讨论】:

    【解决方案2】:

    您只需要发送缓冲区的长度(strlen)。最佳做法是始终在实际发送数据之前发送缓冲区的长度。

    int len;
    len = strlen(buffer);
    n = write(manSock,&len , sizeof(int));
    if (n < 0)
    {
     error("ERROR writing to len management server");
    }
    n = write(manSock,buffer,strlen(buffer));
    if (n < 0)
    {
     error("ERROR writing to management server");
    }
    bzero(buffer,256);
    n = read(manSock,&len,sizeof(int));
    if (n < 0)
    {
      error("ERROR reading len from management server");
    }
    n = read(manSock,buffer,len);
    if (n < 0)
    {
      error("ERROR reading from management server");
    }
    

    【讨论】:

    • 在实际发送数据之前始终发送缓冲区长度的最佳实践”这只是应用程序级协议的各种方法中的一种可能性。另一种方法是终止由终结器(例如'\0''\n'\r\n' 或...)发送的每个“消息”,并让接收方读取直到找到该终结器。
    • 将来,如果您希望更改发送多个字符串,发送长度将有助于解析多个字符串。您还可以在不更改应用程序协议的情况下使用其他编码类型,如 UTF-8、UTF-16。协议也是安全的,因为即使没有 NULL 终止,它也不会导致缓冲区溢出问题。
    • "...它不会导致缓冲区溢出问题" 这在很大程度上取决于它的实现方式。 ;-) 然而,我不想表达你关于应用程序级协议的建议在任何情况下都是坏的、无用的或错误的...... - 我只想提一下,还有多种其他方法可以做到这一点。哪一个高度取决于用例。
    【解决方案3】:

    您现在的代码(假设它是一个函数)很糟糕,即使您接受了其他答案和 cmets 中关于 writeread 函数的建议。

    原因是您返回的是一个本地定义的数组(实际上是第一个元素的地址)buffer,这样做是未定义的行为。因此,如果您正在检索返回值,buffer 将不再有效,并且可能包含垃圾数据,即使您在函数中使用有效数据填充它也是如此。

    C++ 标记已被删除,但如果您真的使用 C++,您可以随时将结果复制到 std::string 并返回:

    std::string someFunc()
    {
        int n;
        char buffer[256];
        //.. your code goes here
        //...
        return std::string(buffer, len); // where len is the number of characters read
    }
    

    如果您使用的是 C,则让用户将缓冲区传递给您,然后您将其填充到函数中。不要创建本地数组并返回它们——这是底线。

    【讨论】:

      【解决方案4】:

      您的 API 设计完全错误

      1. 您返回的是本地缓冲区的地址,该地址不存在并且可能在调用后被覆盖。
      2. 您丢弃了 recv() 返回的长度,因此调用者无法知道缓冲区的有效量,即使您以某种方式修复了 (1)。

      您需要调用者来提供缓冲区,并且您需要返回长度。这使您的方法签名看起来非常像recv()。换句话说,您可能根本不需要这种方法。调用者可以调用 recv()。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-05-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多