【问题标题】:C Socket stuck in read when sending from x86 to x64从x86发送到x64时,C Socket卡在读取状态
【发布时间】:2013-12-01 02:55:19
【问题描述】:

我正在为班级项目编写一个小型电子邮件服务器/客户端。当我在同一台机器上运行客户端和服务器时,一切正常。当我在 x86 机器上运行服务器并在 x64 机器上运行客户端时,我陷入了阅读困境。当我在 x64 机器上运行服务器并在 x86 机器上运行客户端时,读取完成,但是当我打印字符串时,它是空白的。以下是我用来阅读的功能

/*----------------------------------------------------------------*/
int readn(int sd, char *buf, int n) {
    printf("readn via utils. %d, %s, %d\n", sd, buf, n);
    int toberead;
    char * ptr;

    toberead = n;
    ptr = buf;
    while (toberead > 0) {
        int byteread;
        fprintf(stderr, "toberead: %d, byteread: %d\n",toberead, byteread );
        byteread = read(sd, ptr, toberead);
        fprintf(stderr, "toberead: %d, byteread: %d\n",toberead, byteread );

        if (byteread <= 0) {
            fprintf(stderr, "byteread val: %d",byteread);
            if (byteread == -1)
                perror("read");
            raise (6);
            return (0);
        }

        toberead -= byteread;
        ptr += byteread;
    }

    fprintf(stderr, "Finished readn. %s\n", buf);
    return (1);
}

其他使用或影响 readn 的实用函数

/*----------------------------------------------------------------*/

Packet *recvpkt(int sd)
{
    printf("Recvpkt via utils.\n");
    Packet *pkt;

    /* allocate space for the pkt */
    pkt = (Packet *) calloc(1, sizeof(Packet));
    if (!pkt) {
        fprintf(stderr, "error : unable to calloc\n");
        return(NULL);
    }

    /* read the message type */
    if (!readn(sd, (char *) &pkt->type, sizeof(pkt->type))) {
        free(pkt);
        return(NULL);
    }

    /* read the message length */
    if (!readn(sd, (char *) &pkt->lent, sizeof(pkt->lent))) {
        free(pkt);
        return(NULL);
    }
    pkt->lent = ntohl(pkt->lent);

    /* allocate space for message text */
    if (pkt->lent > 0) {
        pkt->text = (char *) malloc(pkt->lent);
        if (!pkt) {
            fprintf(stderr, "error : unable to malloc\n");
            return(NULL);
        }

        /* read the message text */
        if (!readn(sd, pkt->text, pkt->lent)) {
            freepkt(pkt);
            return(NULL);
        }
    }

    fprintf(stderr, "Reading packet complete succesfully.\n");

    /* done reading */
    return(pkt);
}

int sendpkt(int sd, char typ, long len, char *buf)
{
    fprintf(stderr, "Send packet via utils.\n");
    char tmp[8];
    long siz;

    /* write type and lent */
    bcopy(&typ, tmp, sizeof(typ));
    siz = htonl(len);
    bcopy((char *) &siz, tmp+sizeof(typ), sizeof(len));
    write(sd, tmp, sizeof(typ) + sizeof(len));

    /* write message text */
    if (len > 0)
        write(sd, buf, len);
    return(1);
}

void freepkt(Packet *pkt)
{
    fprintf(stderr, "Freeing packet.\n");
    free(pkt->text);
    free(pkt);
}

在第一种情况下(x86 到 x64)的输出如下。

readn via utils. 3, , 1
toberead: 1, byteread: 0
toberead: 1, byteread: 1
Finished readn. 
readn via utils. 3, , 8
toberead: 8, byteread: 1
toberead: 8, byteread: 8
Finished readn. 
readn via utils. 3, , 57
toberead: 57, byteread: 58
toberead: 57, byteread: 53
toberead: 4, byteread: 53

从x64到x86的函数输出如下

readn via utils. 3, , 1
toberead: 1, byteread: -1079631112
toberead: 1, byteread: 1
Finished readn. 
readn via utils. 3, , 4
toberead: 4, byteread: 1
toberead: 4, byteread: 4
Finished readn. 
readn via utils. 3, , 57
toberead: 57, byteread: -1079631112
toberead: 57, byteread: 57
Finished readn. 
Reading packet complete succesfully.
>> 

数据应该在>>之后打印

如果我的问题清楚或是否有任何其他信息,请告诉我。我花了整整 2 天的时间试图解决这个问题,但徒劳无功。

更新:调用 readn 的函数被更新。

【问题讨论】:

  • 第一个 fprintf(stderr, ... 引入了未定义的行为,因为 byteread 是在未初始化的情况下访问的。执行int byteread = 0; 将解决此问题并且使日志输出更有意义。
  • read() 返回 ssize_t 并采用 size_t 而不是 int 代码所做的那样。这个错误会使代码无法处理大量数据。
  • 如果接收到的数据没有将0 作为最后一个字符,那么fprintf(stderr, "Finished readn. %s\n", buf); 这一行也会引入未定义的行为。将其更改为逐字节打印缓冲区或测试尾随 0 并在打印“字符串”之前“手动”添加它。

标签: c linux sockets


【解决方案1】:

在 x64 上,您读取的是 1 个字节,然后是 8 个字节(然后是更多字节)。
在 x86 上,您正在读取 1 个字节,然后是 4 个字节(然后是更多字节)。
这是关于您的程序的 x86 和 x64 版本不匹配的线索。

也许你像这样打电话给sendn

long l;
l = /* something */;
sendn(sd, &l, sizeof l);

longs 在 32 位 Linux 上是 32 位宽,在 64 位 Linux 上是 64 位宽。

【讨论】:

  • 你是对的,这里的问题很可能是一个 XY 问题:meta.stackexchange.com/questions/66377/what-is-the-xy-problem
  • "XY 问题是询问您尝试的解决方案,而不是您的实际问题。" - 这个问题没有提到任何尝试的解决方案。但是作者是在错误的地方寻找问题。
  • 将代码发布给我们寻求帮助是尝试的解决方案,因为 OP 没有提及如何使用代码,这似乎是实际问题?但也许我把 XY-problem-idiom 弄错了。
  • @immibis,谢谢。我应该早点粘贴 sendpkt 函数,这样您就可以更深入地了解我在做什么。现在我明白了这个问题。 Send 正在发送 1 + 4 + 57,Receive 正在接收 1 + 8 + 53,但正在等待接收 57,其中剩余的 4 个不可用。对于如何解决这个问题,有任何的建议吗 ?发送数据包中 typ 的想法是特定于此代码的,用于告知消息的时间 - 欢迎消息、数据消息、用户名等。
【解决方案2】:

代码有几个问题:

  • 第一个printf(""readn via utils ...)假定buf指向的内存已经初始化,至少buf[0]应该被设置为'\0',否则这个调用的行为是未定义的。

  • 第一个 fprintf(stderr, ... 引入了未定义的行为,因为 byteread 是在未初始化的情况下访问的。执行int byteread = 0; 将解决此问题并使日志输出更有意义。

  • 同样read() 返回ssize_t 并采用size_t 而不是int,就像代码一样。这个错误会使代码无法处理大量数据。

  • perror() 的调用可能不会反映在对read() 的调用中发生的错误,因为在perror() 之前对fprintf() 的调用可能已经更改了errno 的值。

  • 如果接收到的数据没有携带'\0' 作为最后一个字符,那么fprintf(stderr, "Finished readn. %s\n", buf); 这一行也会引入未定义的行为。将其更改为逐字节打印缓冲区或测试尾随 0 并在打印“字符串”之前“手动”添加。

根据我上面的 cmets 对代码进行了一些修复:

int readn(int sd, char * buf, size_t n)
{
  printf("readn via utils. %d, %s, %zu\n", sd, buf, n);
  size_t toberead = n;
  char * ptr = buf;

  while (toberead > 0)
  {
    int errno_save = 0;
    fprintf(stderr, "toberead: %zu\n", toberead);

    ssize_t byteread = read(sd, ptr, toberead);
    errno_save = errno;

    fprintf(stderr, "toberead: %zu, byteread: %zd\n", toberead, byteread);

    if (byteread <= 0)
    {
      fprintf(stderr, "byteread val: %zd\n", byteread);
      if (byteread == -1)
      {
        errno = errno_save;
        perror("read");
      }
      raise(6);
      return (0);
    }

    toberead -= byteread;
    ptr += byteread;
  }

  if ('\0' != buf[n]) /* This assumes buf is one byte **larger** then n. */
  {
    buf[n] = '\0';
  }

  fprintf(stderr, "Finished readn. %s\n", buf);
  return (1);
}

作为最后的评论:这种从套接字读取数据的构造,或者假设调用者预先知道将发送多少字节,或者在发送较少数据后关闭连接,否则(即较少字节然后发送n 并且连接关闭)对readn() 的调用将阻塞。

请参阅 immibis's answer,了解后者可能对您的设置造成影响的原因(32 位与 64 位)。

【讨论】:

  • 添加这些调试后,我仍然没有得到更多信息。我无法在此处格式化此输出。通过 utils 接收。通过 utils 读取。 3, , 1 toberead: 1 toberead: 1, byteread: 1 完成readn。通过 utils 读取。 3, , 8 toberead: 8 toberead: 8, byteread: 8 完成readn。通过 utils 读取。 3, , 57 toberead: 57 toberead: 57, byteread: 53 toberead: 4
  • x86 到 x86 上的相同功能打印以下内容。通过 utils 接收。通过 utils 读取。 3, , 1 toberead: 1 toberead: 1, byteread: 1 完成readn。通过 utils 读取。 3, , 4 toberead: 4 toberead: 4, byteread: 4 完成readn。通过 utils 读取。 3, , 57 toberead: 57 toberead: 57, byteread: 57 完成readn。欢迎使用 Santosh 的电子邮件服务器,运行在 5945 端口。读取数据包成功。
猜你喜欢
  • 2017-06-09
  • 2012-03-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-12-31
  • 2011-12-14
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多