我遇到过在远程关闭的客户端上使用write()
服务器端不返回0的情况。
根据男人2 write
:
成功时,返回写入的字节数(零表示 没有写的东西)。出错时,返回-1,并设置errno 适当。
根据我的理解:在远程关闭的套接字上使用read
/ write
时,第一次尝试失败(因此返回0
),下一次尝试应该触发破管。但事实并非如此。 write()
就好像它在第一次尝试时成功发送数据一样,然后在下一次尝试时我得到一个损坏的管道。
我的问题是为什么?
我知道如何妥善处理破损的管道,这不是问题。我只是想了解为什么write
在这种情况下不会返回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);
}
答案 0 :(得分:5)
使write
在套接字上返回零的唯一方法是让它写入零字节。如果套接字上有错误,您将始终获得-1
。
如果您想获得“关闭连接”指示符,则需要使用{em>将返回read
的{{1}}进行远程关闭连接。
答案 1 :(得分:3)
这就是编写套接字接口的方式。当你有一个连接的插座或管道时,你应该先关闭发射端,然后接收端将获得EOF并可以关闭。首先关闭接收端是&#34;意外&#34;所以它返回一个错误而不是返回0。
这对于管道很重要,因为它允许复杂的命令比其他方式更快地完成。例如,
bunzip2 < big_file.bz2 | head -n 10
假设big_file.bz2
很大。只会读取第一部分,因为bunzip2
尝试向head
发送更多数据后会被杀死。这使得整个命令完成得更快,并且CPU使用率更低。
套接字继承了相同的行为,增加了复杂性,你必须分别关闭套接字的发送和接收部分。
答案 2 :(得分:1)
要注意的一点是,在TCP中,当连接的一端关闭时 套接字,它实际上停止在该套接字上传输;它发送一个数据包 告知其远程对等体它不会再通过它传输 连接。但是,这并不意味着它也停止接收。 (至 继续接收是关闭方的当地决定;如果它停止接收,它可以 丢失远程对等体发送的数据包。)
所以,当你write()
到一个远程关闭的套接字时,但是
没有在当地关闭,你不知道另一端是否仍在等待阅读
更多数据包,因此TCP堆栈将缓冲您的数据并尝试发送它。如
在send()
手册页
send()
中没有暗示未能传递的信息。在当地检测到 错误由返回值-1表示。
(当你write()
到套接字时,你实际上send()
了它。)
但是当你write()
第二次,而远程同伴肯定是
关闭套接字(不仅shutdown()
写入),本地TCP堆栈可能已经关闭
已收到来自对等体的重置数据包,通知它有关错误的信息
最后传输的数据包。只有这样才能write()
返回错误,告诉我
其管道已损坏的用户(EPIPE
错误代码)。
如果远程对等体只有shutdown()
写入,但仍然打开套接字,
它的TCP堆栈将成功接收数据包并将确认
收到数据回发送者。
答案 3 :(得分:0)
如果您阅读整个手册页,那么您将阅读错误返回值:
"EPIPE fd is connected to a pipe or *socket whose reading end is closed*."
因此,对write()的调用不会返回0而是返回-1而errno将设置为'EPIPE'