为什么 write() 在应该的时候不 return 0?
why write() doesn't return 0 when it should?
我遇到过这样的情况,在远程关闭的客户端上使用 write()
服务器端不会 return 0.
根据 man 2 write
:
On success, the number of bytes written is returned (zero indicates
nothing was written). On error, -1 is returned, and errno is set
appropriately.
根据我的理解:在远程关闭的套接字上使用 read
/write
时,第一次尝试应该会失败(因此 return 0
),并且下一次尝试应该会触发破损的管道。但事实并非如此。 write()
就好像它在第一次尝试时成功发送了数据,然后在下一次尝试时我发现管道坏了。
我的问题是为什么?
我知道如何正确处理破损的管道,这不是问题所在。我只是想了解为什么 write
在这种情况下不 return 0
。
下面是我写的服务器代码。在客户端,我尝试了一个基本的 C 客户端(使用 close() 和 shutdown() 来关闭套接字)和 netcat。这三个都给了我相同的结果。
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#define MY_STR "hello world!"
int start_server(int port)
{
int fd;
struct sockaddr_in sin;
fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd == -1)
{
perror(NULL);
return (-1);
}
memset(&sin, 0, sizeof(struct sockaddr_in));
sin.sin_addr.s_addr = htonl(INADDR_ANY);
sin.sin_family = AF_INET;
sin.sin_port = htons(port);
if (bind(fd, (struct sockaddr *)&sin, sizeof(struct sockaddr)) == -1
|| listen(fd, 0) == -1)
{
perror(NULL);
close(fd);
return (-1);
}
return (fd);
}
int accept_client(int fd)
{
int client_fd;
struct sockaddr_in client_sin;
socklen_t client_addrlen;
client_addrlen = sizeof(struct sockaddr_in);
client_fd = accept(fd, (struct sockaddr *)&client_sin, &client_addrlen);
if (client_fd == -1)
return (-1);
return (client_fd);
}
int main(int argc, char **argv)
{
int fd, fd_client;
int port;
int ret;
port = 1234;
if (argc == 2)
port = atoi(argv[1]);
fd = start_server(port);
if (fd == -1)
return (EXIT_FAILURE);
printf("Server listening on port %d\n", port);
fd_client = accept_client(fd);
if (fd_client == -1)
{
close(fd);
printf("Failed to accept a client\n");
return (EXIT_FAILURE);
}
printf("Client connected!\n");
while (1)
{
getchar();
ret = write(fd_client, MY_STR, strlen(MY_STR));
printf("%d\n", ret);
if (ret < 1)
break ;
}
printf("the end.\n");
return (0);
}
使套接字上的 write
return 为零的唯一方法是要求它写入零字节。如果套接字出现错误,您将始终得到 -1
.
如果你想得到一个"connection closed"指标,你需要使用read
,将 return 0
用于一个远程关闭的连接。
套接字接口就是这样写的。当你有一个连接的套接字或管道时,你应该先关闭发送端,然后接收端就会得到 EOF 并可以关闭。首先关闭接收端是 "unexpected" 所以它 returns 一个错误而不是返回 0.
这对管道很重要,因为它允许复杂的命令比其他方式更快地完成。例如,
bunzip2 < big_file.bz2 | head -n 10
假设 big_file.bz2
很大。只会读取第一部分,因为一旦 bunzip2
尝试向 head
发送更多数据,它就会被杀死。这使得整个命令完成得更快,并且使用更少 CPU。
套接字继承了相同的行为,但增加了复杂性,您必须分别关闭套接字的发送和接收部分。
如果您阅读了整个手册页,那么您会读到错误 return 值:
"EPIPE fd is connected to a pipe or *socket whose reading end is closed*."
因此,对 write() 的调用不会 return 0 而是 -1 并且 errno 将设置为 'EPIPE'
需要注意的一点是,在TCP中,当连接的一侧关闭其
套接字,它实际上停止在该套接字上传输;它发送一个数据包到
通知它的远程对等点它不会再通过那个传输
联系。但是,这并不意味着它也停止接收。 (到
继续接收是关闭方的本地决定;如果它停止接收,它可以
丢失远程对等方传输的数据包。)
所以,当你 write()
到一个远程关闭的套接字时,但是
没有本地关闭,你无法知道另一端是否还在等待读取
更多数据包,因此 TCP 堆栈将缓冲您的数据并尝试发送它。作为
send()
手册页中所述,
No indication of failure to deliver is implicit in a send()
. Locally detected
errors are indicated by a return value of -1.
(当你 write()
到一个套接字时,你实际上是 send()
ing 到它。)
当你 write()
第二次时,虽然,并且远程对等方肯定有
关闭套接字(不仅shutdown()
写入),本地TCP堆栈可能已经
已经收到来自对等方的重置数据包,通知它有关错误的信息
最后发送的数据包。只有这样才能write()
return报错,告诉
它的用户认为此管道已损坏(EPIPE
错误代码)。
如果远程端只有 shutdown()
写入,但套接字仍然打开,
它的 TCP 堆栈将成功接收数据包并将确认
收到数据返回给发送者。
我遇到过这样的情况,在远程关闭的客户端上使用 write()
服务器端不会 return 0.
根据 man 2 write
:
On success, the number of bytes written is returned (zero indicates nothing was written). On error, -1 is returned, and errno is set appropriately.
根据我的理解:在远程关闭的套接字上使用 read
/write
时,第一次尝试应该会失败(因此 return 0
),并且下一次尝试应该会触发破损的管道。但事实并非如此。 write()
就好像它在第一次尝试时成功发送了数据,然后在下一次尝试时我发现管道坏了。
我的问题是为什么?
我知道如何正确处理破损的管道,这不是问题所在。我只是想了解为什么 write
在这种情况下不 return 0
。
下面是我写的服务器代码。在客户端,我尝试了一个基本的 C 客户端(使用 close() 和 shutdown() 来关闭套接字)和 netcat。这三个都给了我相同的结果。
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#define MY_STR "hello world!"
int start_server(int port)
{
int fd;
struct sockaddr_in sin;
fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd == -1)
{
perror(NULL);
return (-1);
}
memset(&sin, 0, sizeof(struct sockaddr_in));
sin.sin_addr.s_addr = htonl(INADDR_ANY);
sin.sin_family = AF_INET;
sin.sin_port = htons(port);
if (bind(fd, (struct sockaddr *)&sin, sizeof(struct sockaddr)) == -1
|| listen(fd, 0) == -1)
{
perror(NULL);
close(fd);
return (-1);
}
return (fd);
}
int accept_client(int fd)
{
int client_fd;
struct sockaddr_in client_sin;
socklen_t client_addrlen;
client_addrlen = sizeof(struct sockaddr_in);
client_fd = accept(fd, (struct sockaddr *)&client_sin, &client_addrlen);
if (client_fd == -1)
return (-1);
return (client_fd);
}
int main(int argc, char **argv)
{
int fd, fd_client;
int port;
int ret;
port = 1234;
if (argc == 2)
port = atoi(argv[1]);
fd = start_server(port);
if (fd == -1)
return (EXIT_FAILURE);
printf("Server listening on port %d\n", port);
fd_client = accept_client(fd);
if (fd_client == -1)
{
close(fd);
printf("Failed to accept a client\n");
return (EXIT_FAILURE);
}
printf("Client connected!\n");
while (1)
{
getchar();
ret = write(fd_client, MY_STR, strlen(MY_STR));
printf("%d\n", ret);
if (ret < 1)
break ;
}
printf("the end.\n");
return (0);
}
使套接字上的 write
return 为零的唯一方法是要求它写入零字节。如果套接字出现错误,您将始终得到 -1
.
如果你想得到一个"connection closed"指标,你需要使用read
,将 return 0
用于一个远程关闭的连接。
套接字接口就是这样写的。当你有一个连接的套接字或管道时,你应该先关闭发送端,然后接收端就会得到 EOF 并可以关闭。首先关闭接收端是 "unexpected" 所以它 returns 一个错误而不是返回 0.
这对管道很重要,因为它允许复杂的命令比其他方式更快地完成。例如,
bunzip2 < big_file.bz2 | head -n 10
假设 big_file.bz2
很大。只会读取第一部分,因为一旦 bunzip2
尝试向 head
发送更多数据,它就会被杀死。这使得整个命令完成得更快,并且使用更少 CPU。
套接字继承了相同的行为,但增加了复杂性,您必须分别关闭套接字的发送和接收部分。
如果您阅读了整个手册页,那么您会读到错误 return 值:
"EPIPE fd is connected to a pipe or *socket whose reading end is closed*."
因此,对 write() 的调用不会 return 0 而是 -1 并且 errno 将设置为 'EPIPE'
需要注意的一点是,在TCP中,当连接的一侧关闭其 套接字,它实际上停止在该套接字上传输;它发送一个数据包到 通知它的远程对等点它不会再通过那个传输 联系。但是,这并不意味着它也停止接收。 (到 继续接收是关闭方的本地决定;如果它停止接收,它可以 丢失远程对等方传输的数据包。)
所以,当你 write()
到一个远程关闭的套接字时,但是
没有本地关闭,你无法知道另一端是否还在等待读取
更多数据包,因此 TCP 堆栈将缓冲您的数据并尝试发送它。作为
send()
手册页中所述,
No indication of failure to deliver is implicit in a
send()
. Locally detected errors are indicated by a return value of -1.
(当你 write()
到一个套接字时,你实际上是 send()
ing 到它。)
当你 write()
第二次时,虽然,并且远程对等方肯定有
关闭套接字(不仅shutdown()
写入),本地TCP堆栈可能已经
已经收到来自对等方的重置数据包,通知它有关错误的信息
最后发送的数据包。只有这样才能write()
return报错,告诉
它的用户认为此管道已损坏(EPIPE
错误代码)。
如果远程端只有 shutdown()
写入,但套接字仍然打开,
它的 TCP 堆栈将成功接收数据包并将确认
收到数据返回给发送者。