代码之家  ›  专栏  ›  技术社区  ›  user3699677

C套接字编程写/读脏内存

  •  2
  • user3699677  · 技术社区  · 10 年前

    我为我的C程序编写了两个自定义函数,我的大学老师建议:

    ssize_t FullRead(int fd, void *buff, size_t count) {
        size_t nleft;
        ssize_t nread;
        nleft = count;
        while(nleft > 0) {
            nread = read(fd, buff, nleft);
            if(errno == EINTR) {
                continue;
            }
            else {
                return nread;
            }
            if(nread == 0) {
                break;
            }
            nleft -= nread;
            buff += nread;
        }
        buff = 0;
        return nleft;
    }
    

    ssize_t FullWrite(int fd, const void *buff, size_t count) {
        size_t nleft;
        ssize_t nwritten;
        nleft = count;
        while(nleft > 0) {
            if((nwritten=write(fd, buff, nleft)) < 0) {
                if(errno == EINTR) {
                    continue;
                }
                else {
                    return nwritten;
                }
            }
            nleft -= nwritten;
            buff += nwritten;
        }
        buff = 0;
        return nleft;
    }
    

    但每次我试图从客户端和服务器传递数据时,我总是会得到特殊字符(脏内存),这就是我试图做到的:

    • 我做了一个 内存集() 在FullWrite之前
    • 尝试使用 fflush(标准输入) 即使我知道这不是一个好的做法
    • 已尝试删除 buff=0 在我的职能结束时

    这就是我所说的两个连续的FullRead和FullWrite。

    这是在 服务器 :

    FullRead(connfd, buff, strlen(buff));
    printf("Buff: %s\n", buff);
    FullRead(connfd, buff, strlen(buff));
    printf("Buff: %s", buff);
    

    这是在 客户 :

    memset((void *)buff, 0, sizeof(char)*1000);
    scanf("%s", buff);
    fflush(stdin);
    FullWrite(sockfd, buff, strlen(buff));
    
    memset((void *)buff, 0, sizeof(char)*1000);
    scanf("%s", buff);
    fflush(stdin);
    FullWrite(sockfd, buff, strlen(buff));
    

    如果我在linux终端上从服务器上写下类似“001”的内容,在客户端上我会看到“001t”或类似的内容。。像这样的事情。无法摆脱这种状况。


    我尝试更改代码,从FullWrite()和FullRead()中移除buff=0,移除fflush()和memset,调用如下函数:

    客户

    scanf("%s", buff);
    FullWrite(sockfd, buff, sizeof(buff));
    

    服务器

    length = FullRead(connfd, buff, strlen(buff));
    printf("Buffer: %.*s\n", length, buff);
    

    服务器仍然读取并打印垃圾。

    2 回复  |  直到 10 年前
        1
  •  3
  •   John Bollinger    10 年前

    Paul R和EJP发现了当前的问题:实际上,假设缓冲区最初包含一个与要读取的长度相同的C字符串。只有在这种情况下 strlen(buff) 指定正确的字节数,结果将可靠地以空结尾。

    但这只是冰山一角。我接下来观察到你 FullWrite() FullRead() 实现是有缺陷的。以后者为代表,考虑阅读循环中的以下摘录:

            if(errno == EINTR) {
                continue;
            }
            else {
                return nread;
            }
    

    观察(1)它仅在读取被信号中断的情况下(可能在读取任何字节之前, (2) 在任何情况下都不会到达以下代码,直到循环体的末尾。除了被信号打断之外,还有很多原因 read() 调用可能传送比预期和期望的字节更少的字节。这种循环的通常实现重复读取,直到传输了指定数量的字节,或者 read() 呼叫返回 -1 。您的循环包含实现该功能的位,但它从不使用它们。

    此外,如果实际读取的字节数少于请求的字节数,则函数返回错误的值。

    还要注意,对 void * 不是由C定义的。一些编译器将其视为指针的类型 char * 相反,但这不是便携式的。如果要将该值视为 字符* 那么应该将其强制转换或分配给该类型。

    此外,函数赋值是无用的(虽然不是有害的) 0 返回到缓冲区指针。

    以下是改进的 FullRead() :

    ssize_t FullRead(int fd, void *buff, size_t count) {
        size_t nleft = count;
        char *next = buff;
    
        while (nleft > 0) {
            ssize_t nread = read(fd, next, nleft);
    
            if (nread < 0) {
                if (errno == EINTR) {
                    /* interrupted by a signal */
                    /* 
                     * It is not specified by POSIX whether this condition can
                     * arise if any bytes are successfully transerred.  In the GNU
                     * implementation, it does not arise in that case.
                     */
                    continue;
                } else {
                    /* other error */
                    return -1;
                }
            } else if (nread == 0) {
                /* end of file */
                break;
            } else {
                /* 'nread' bytes successfully transferred */
                nleft -= nread;
                next += nread;
            }
        }
    
        return (count - nleft);
    }
    

    你的 FullWrite 函数也有类似的问题。我让你来解决这个问题。

    但我们还没有完成。即使(事实上)正确实现了 FullWrite() FullRead() ,您仍然需要解决一个关键问题:服务器实际上应该(尝试)读取多少字节?只有两种方法可以知道:

    1. 字节数可以以某种方式在带外传递。最可能的实现方式是将其设置为预先安排的固定大小。

    2. 客户端必须提前告诉服务器需要多少字节。

    预先排列的大小不太适合字符串等可变长度数据,但它适用于二进制数值等固定长度数据。因此,一种解决方案是将字符串作为(长度、内容)对传输。长度第一,例如 uint16_t 按网络字节顺序,然后是字符串数据的指定字节数。接收器负责提供字符串终止。这与HTTP使用 Content-Length 标头以指定消息正文的大小。

        2
  •  1
  •   Paul R    10 年前

    两个问题:

    • 你通过了 strlen(buff) FullRead() -你可能想要 sizeof(buff) (如果看不到如何声明/初始化此缓冲区,很难说)。

    • 您没有终止从返回的字符串 FullRead() -你需要一个 '\0' 紧接在接收到的数据之后,否则 printf() 将很可能发出超出字符串结尾的垃圾字符。

    而且,正如你已经知道的,你不应该打电话 fflush(stdin) .