Search code examples
clinuxsocketsbroken-pipe

why write() doesn't return 0 when it should?


I've encountered a case where using write() server-side on a remotely closed client doesn't return 0.

According to 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.

From my understanding: when using read/write on a remotely closed socket, the first attempt is supposed to fail (thus return 0), and the next try should trigger a broken pipe. But it doesn't. write() acts as if it succeeded in sending the data on the first attempt, and then i get a broken pipe on the next try.

My question is why?

I know how to handle a broken pipe properly, that's not the issue. I'm just trying to understand why write doesn't return 0 in this case.

Below is the server code I wrote. Client-side, I tried a basic C client (with close() and shutdown() for closing the socket) and netcat. All three gave me the same result.


#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);
}

Solution

  • The only way to make write return zero on a socket is to ask it to write zero bytes. If there's an error on the socket you will always get -1.

    If you want to get a "connection closed" indicator, you need to use read which will return 0 for a remotely closed connection.