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;
}
但是每次我尝试从客户端和服务器传递数据时,我总是得到特殊字符(脏内存),这就是我尝试做的:
- 我在FullWrite
之前做了一个memset()
- 尝试使用 fflush(stdin),即使我知道这不是一个好的做法
- 尝试在我的函数末尾删除 buff = 0
这就是我调用两个连续的 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()
,您仍然有一个关键问题需要解决:服务器实际上(尝试)读取多少字节?它只有两种方式可以知道:
可以通过某种方式在带外传送字节数。最有可能实现的是将其设为 pre-arranged 固定大小。
客户端必须提前告诉服务器需要多少字节。
A pre-arranged 大小不太适合 variable-length 数据(如字符串),但它适用于 fixed-length 数据(如二进制数值)。那么,一种解决方案是将您的字符串作为(长度,内容)对传输。长度首先出现,例如网络字节顺序中的 uint16_t
,然后是字符串数据的指定字节数。接收方负责提供字符串终止。这类似于 HTTP 使用 Content-Length
header 来指定消息的大小 body.
我在我的大学老师建议的 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 之前做了一个memset()
- 尝试使用 fflush(stdin),即使我知道这不是一个好的做法
- 尝试在我的函数末尾删除 buff = 0
这就是我调用两个连续的 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()
,您仍然有一个关键问题需要解决:服务器实际上(尝试)读取多少字节?它只有两种方式可以知道:
可以通过某种方式在带外传送字节数。最有可能实现的是将其设为 pre-arranged 固定大小。
客户端必须提前告诉服务器需要多少字节。
A pre-arranged 大小不太适合 variable-length 数据(如字符串),但它适用于 fixed-length 数据(如二进制数值)。那么,一种解决方案是将您的字符串作为(长度,内容)对传输。长度首先出现,例如网络字节顺序中的 uint16_t
,然后是字符串数据的指定字节数。接收方负责提供字符串终止。这类似于 HTTP 使用 Content-Length
header 来指定消息的大小 body.