尽管我已经关闭套接字,但写入(2)仍然报告成功

时间:2010-08-06 11:17:05

标签: c sockets unix

我遇到了一个问题,即使在我的服务器端关闭() - 编辑套接字后,客户端仍然可以执行单个write(),只有第二个write()将返回SIGPIPE,这会导致我丢失数据因为我没有应用程序级别的握手,只依赖于write()的返回值。在服务器端关闭连接后,有没有办法让我立即获得SIGPIPE?

测试程序如下:

我期待第二次写入返回SIGPIPE,但它返回成功,只有第三次写入返回SIGPIPE!这是为什么?

#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdarg.h>
#include <netdb.h>
#include <unistd.h>
#include <signal.h>

void log_error(const char *fmt, ...) 
{
 va_list ap;
 char buf[BUFSIZ] = {0};

 va_start(ap, fmt);
 vsnprintf(buf, BUFSIZ, fmt, ap);
 fprintf(stderr, "ERROR: %s\n", buf);
 va_end(ap);
 return;
}

static int create_inet_socket()
{
    int fd;
    struct sockaddr_in my_addr;
    int yes = 1;

    if ((fd=socket(PF_INET, SOCK_STREAM, 0))==-1) {
 log_error("create_inet_socket:socket:%d:%s", errno, strerror(errno));
 return -1;
    }

    if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int))==-1) {
 log_error("create_inet_socket:setsockopt:%d:%s", errno, strerror(errno));
 return -1;
    }

    my_addr.sin_family = AF_INET;
    my_addr.sin_port = htons(9998);
    my_addr.sin_addr.s_addr = INADDR_ANY;
    memset(&(my_addr.sin_zero), '0', 8);

    if (bind(fd, (struct sockaddr*)&my_addr, sizeof(struct sockaddr_in))==-1) {
 log_error("main:bind:%d:%s", errno, strerror(errno));
 return -1;
    }

    if (listen(fd, 5)==-1) {
 log_error("main:listen:%d:%s", errno, strerror(errno));
 return -1;
    }

    return fd;
}

int myconnect()
{
    int fd;
    char ch[1] = {'a'};
    struct sockaddr_in sin;
    fd_set wfds;
    struct timeval tv;
    struct hostent *he;
    ssize_t nwritten;

    if ((fd=socket(PF_INET, SOCK_STREAM, 0))==-1) {
 log_error("rlog:socket failed:%d:%s", errno, strerror(errno));
 return -1;
    }

    bzero(&sin, sizeof(sin));

    if ((he=gethostbyname("localhost"))==NULL) {
 log_error("rlog:gethostbyname failed:%d:%s", errno, strerror(errno));
 return -1;
    }

    sin.sin_addr = *((struct in_addr*)he->h_addr);
    sin.sin_port = htons(9998);
    sin.sin_family = AF_INET;

    if (connect(fd,(struct sockaddr *) &sin,sizeof(sin)) == -1) {
 log_error("connect:%d:%s", errno, strerror(errno));
 return 0;
    }

    nwritten = write(fd, &ch, 1);
    if (nwritten==-1) {
 log_error("write:%d:%s", errno, strerror(errno));
    } else {
 fprintf(stderr, "client : 1. written %ld\n", nwritten);
    }
    sleep(3);
    nwritten = write(fd, &ch, 1);
    if (nwritten==-1) {
 log_error("write:%d:%s", errno, strerror(errno));
    } else {
 fprintf(stderr, "client : 2. written %ld\n", nwritten);
    }
    sleep(3);
    nwritten = write(fd, &ch, 1);
    if (nwritten==-1) {
 log_error("write:%d:%s", errno, strerror(errno));
    } else {
 fprintf(stderr, "client : 3. written %ld\n", nwritten);
    }
    return 0;
}

void run_server()
{
    int fd;
    int newfd;
    char c[1];
    ssize_t nread;
    int status;
    fprintf(stderr, "server : Running\n");
    fd = create_inet_socket();
    if (fd==-1) {
 perror("create_inet_socket");
    }

    newfd = accept(fd, NULL, NULL);
    fprintf(stderr, "server : accepted newfd %d\n", newfd);

    nread = read(newfd, &c, 1);
    if (nread==-1) {
 log_error("read:%d:%s", errno, strerror(errno));
    } else {
 fprintf(stderr, "read returned %ld, closing socket\n", nread);
    }
    shutdown(newfd, SHUT_RDWR);
    close(newfd);
    wait(&status);
    fprintf(stderr, "server : exit\n");
}

void run_client()
{
    fprintf(stderr, "client : running\n");
    myconnect();
    fprintf(stderr, "client : exit\n");
    _exit(1);
}

int main()
{
    signal(SIGPIPE, SIG_IGN);
    int pid = fork();
    switch (pid) {
    case 0:
 run_client();
 break;
    case -1:
 perror("fork");
 break;
    default:
 run_server();
 break;
    }
    return 0;
}

1 个答案:

答案 0 :(得分:4)

你不能只依赖write(2)的返回值 - 这是连接两端之间的一个大竞争条件(内核缓存你给系统调用的数据,数据包需要时间来通过网络,等等。)因此在传输级连接拆除的情况下引入了数据丢失的可能性。如果您需要可靠性,请设计应用程序级协议,以便接收方确认所有数据。

过去有一篇关于类似这样的文章的好文章 - The ultimate SO_LINGER page or why is my TCP not reliable,但现在似乎已经失败了。谷歌这个称号,可能会在某个地方反映出来。