1

I've already read about how to prevent SIGPIPE, then I write a small program to test it. Here is the code.

server.c

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

void hdl(int sig_num, siginfo_t *sig_info, void *context)
{
    printf("got you, SIGPIPE!\n");
}

int main()
{
    int sfd, cfd;
    struct sockaddr_in saddr, caddr;


    struct sigaction act;

    memset (&act, '\0', sizeof(act));
    act.sa_sigaction = hdl;
    act.sa_flags = SA_SIGINFO;

    if (sigaction(SIGPIPE, &act, NULL) < 0) {
        return 1;
    }

    sfd= socket(AF_INET, SOCK_STREAM, 0);
    saddr.sin_family=AF_INET;
    saddr.sin_addr.s_addr=inet_addr("192.168.22.91");
    saddr.sin_port=htons(12345);

    if(bind(sfd, (struct sockaddr *)&saddr, sizeof(saddr)) )
    {
        printf("bind error\n");
        return -1;
    }
    if(listen(sfd, 1))
    {
        printf("error\n");
        return -1;
    }

    char buf[1024] = {0};
    while(1) {
            printf("Server listening...\n");
            cfd=accept(sfd, (struct sockaddr *)NULL, NULL);

            fcntl(cfd, F_SETFL, O_NONBLOCK);
            int size = read(cfd, buf, 1024);

            if(size == -1)
                printf("read error\n");

            sleep(2); // sleep for a while to make sure the client closed the socket

            int ret;
            if((ret = write(cfd, buf, strlen(buf)))<0)
            {
                if(errno == EPIPE)
                    fprintf(stderr, "SIGPIPE");
            }

            ret = write(cfd, buf, strlen(buf)); // write again.
            printf("write return %d\n", ret);
    }
    close(sfd);
}

client.c

#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <assert.h>


int main()
{
    int ret, fd;
    struct sockaddr_in sa_dst;
    char buffer[] = "hello, world";
    char rcv_buf[128] = {0};

    fd = socket(AF_INET, SOCK_STREAM, 0);

    memset(&sa_dst, 0, sizeof(struct sockaddr_in));
    sa_dst.sin_family = AF_INET;
    sa_dst.sin_port = htons(12345);
    sa_dst.sin_addr.s_addr = inet_addr("192.168.22.91"); 

    ret = connect(fd, (struct sockaddr *)&sa_dst, sizeof(struct sockaddr));
    if(ret != -1)
    {
        send(fd, buffer, strlen(buffer), 0);
        close(fd);
    }
    return 0;
}

When I run the server and the client on the same linux machine, on the server side, the first write() returns the number of bytes written while I expect a SIGPIPE signal because I closed the socket on the client side, the second write() does generate a SIGPIPE signal.
But when I ran the client on another linux machine or on a Windows machine(implement the same client with Winsock), I did't catch any SIGPIPE signal, and the second write() still returns the size of the buffer. Can someone tell me what's going on?

jfly
  • 7,715
  • 3
  • 35
  • 65
  • 1
    Strictly, it is not safe to call `printf()` in a signal handler. There's a list of safe functions, and `printf()` is not in the list. I doubt that's the main problem, but do remember for future. You didn't set the `sa_mask` member of your structure; since you aren't using the extra information in your signal handler, you shouldn't use SA_SIGINFO. – Jonathan Leffler May 28 '14 at 14:33
  • @JonathanLeffler Thanks for the tip. I use `sigaction` just to test whether SIGPIPE signal is generated, so don't notice `printf` is not in the list of function we can safely call from a signal handler. – jfly May 29 '14 at 03:19
  • There's no sigpipe on windows – zoska May 29 '14 at 07:00
  • @zoska Server always runs on linux, only client may run on Windows. – jfly May 29 '14 at 07:12

2 Answers2

1

It can't happen on the first write, for two reasons:

  1. The localhost doesn't know that the peer has closed the socket for reading. A FIN has been received but that could just be because the peer has shutdown for output. Only an RST will tell it that, and it doesn't get that util the next I/O at the earliest.
  2. Buffering.

NB you're corrupting the value of errno by calling perror(), so testing it afterwards isn't valid.

user207421
  • 305,947
  • 44
  • 307
  • 483
  • I use tcpdump to capture the packets between client and server, a `RST` does send to server after first `write`. Then I'm wondering why no `SIGPIPE` signal when the server and client run on different machines? – jfly May 29 '14 at 07:17
  • You mean there was an RST but no SIGPIPE in the same test? – user207421 May 29 '14 at 07:18
  • Yes. no `SIGPIPE` with `RST` when C&S running on different machines. – jfly May 29 '14 at 07:21
  • So the RST must have come after the write()-s returned. Buffering would explain that. – user207421 May 29 '14 at 07:26
0

Just Change this in SERVER and it will work

        fcntl(cfd, F_SETFL, O_NONBLOCK);
        int size = read(cfd, buf, 1024);

        if(size == -1)
            printf("read error\n");

        sleep(2); // sleep for a while to make sure the client closed the socket

        int ret;
           ret = write(cfd, buf, strlen(buf));
         sleep(2);
        ret = write(cfd, buf, strlen(buf)); // write again.
        printf("write return %d\n", ret);
Jovi DSilva
  • 216
  • 3
  • 14