C 套接字编程 write/read 脏内存

C Socket Programming write/read dirty memory

我在我的大学老师建议的 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;
}

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

这就是我调用两个连续的 FullRead 和 FullWrites 所做的。

这是在服务器上:

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”的内容,在客户端上我会看到“001�t”或类似的东西......类似的东西。出不了这种情况。


我尝试更改我的代码,从 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);

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

两个问题:

  • 你正在将 strlen(buff) 传递给 FullRead() - 你可能想要 sizeof(buff)(如果不看你如何 declare/initialise 这个缓冲区很难说)。

  • 你没有终止从 FullRead() 返回的字符串 - 你需要在接收到的数据之后立即有一个 '[=14=]' 否则随后的 printf() 很可能会发出垃圾超出字符串末尾的字符。

此外,如您所知,您不应该调用 fflush(stdin)

Paul R 和 EJP 确定了眼前的问题:实际上,您假设您的缓冲区最初包含一个与您要读取的字符串长度相同的 C 字符串。只有在那种情况下,strlen(buff) 都指定了正确的字节数,并且结果可靠地以 null-terminated.

结尾

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

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

观察 (1) 只有在读取被信号中断的情况下(可能在或可能不在读取任何字节之前),它才循环循环,( 2) 后面的代码,直到循环结束 body,在任何情况下都不会到达。 read() 调用传输的字节数可能比预期和期望的少,除了被信号中断之外,还有更多原因。这种循环的通常实现是重复读取,直到传输了指定数量的字节,或者 read() 调用 returns -1。您的循环包括实现它的位,但它从不使用它们。

此外,如果实际读取的字节数少于请求的字节数,您的函数 returns 的值是错误的。

还要注意,在 void * 上执行算术的语义不是由 C 定义的。一些编译器将其视为指针是 char * 类型,但这是不可移植的.如果您想将该值视为 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. 可以通过某种方式在带外传送字节数。最有可能实现的是将其设为 pre-arranged 固定大小。

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

A pre-arranged 大小不太适合 variable-length 数据(如字符串),但它适用于 fixed-length 数据(如二进制数值)。那么,一种解决方案是将您的字符串作为(长度,内容)对传输。长度首先出现,例如网络字节顺序中的 uint16_t,然后是字符串数据的指定字节数。接收方负责提供字符串终止。这类似于 HTTP 使用 Content-Length header 来指定消息的大小 body.