为什么write()不应该返回0?

时间:2015-10-03 18:52:30

标签: c linux sockets broken-pipe

我遇到过在远程关闭的客户端上使用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);
}

4 个答案:

答案 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'