2010-08-06 26 views
0

我遇到了一個問題,即使在我的服務器端關閉() - 套接字後,客戶端仍然可以做一個單一的write(),只有第二個write()將返回SIGPIPE,這會導致我丟失數據,因爲我沒有應用程序級握手,並且完全依賴write()的返回值。在服務器端關閉連接之後,我有沒有辦法讓SIGPIPE直接進入?寫(2)仍然報告成功,即使我已關閉套接字

測試程序如下:

我期待第二寫入返回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; 
} 

回答

3

你不能指望只對write(2)的返回值 - 這是連接的兩端之間有很大的競爭條件(內核會緩存您給系統調用的數據,數據包需要一定的時間才能通過線路,等等),從而在傳輸級連接拆除的情況下引發數據丟失的可能性。如果您需要可靠性,請設計您的應用級協議,以便接收方確認所有數據。

曾經有一篇關於像這樣的好文章 - The ultimate SO_LINGER page or why is my TCP not reliable,但現在看起來似乎是倒下了。谷歌那個標題,它可能在某處被鏡像。

+0

經過30分鐘搜索頁面緩存後,終於在 http://ds9a.nl/the-ultimate-so_linger-page-or-why-is-my-tcp-not-reliable.txt找到它,這是我得到的最好的,希望它與原始版本一樣。 – 2010-08-09 03:06:13

-1

您可以使用select()來確定套接字是否仍可寫入。我最低限度修飾下方的myconnect()函數(不完全恆星流控制):

... 

    sin.sin_family = AF_INET; 

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

    FD_ZERO(&wfds); 
    FD_SET(fd,&wfds); 

    tv.tv_sec=0; 
    tv.tv_usec=0; 

    int retval; 

    retval=select(fd+1, NULL, &wfds, NULL, &tv); 
    if (!retval) { 
    log_error("socket closed."); 
    } else { 
    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); 

    retval=select(fd, NULL, &wfds, NULL, &tv); 
    if (!retval) { 
    log_error("socket closed."); 
    } else { 
    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); 


    retval=select(fd, NULL, &wfds, NULL, &tv); 
    if (!retval) { 
    log_error("socket closed."); 
    } else { 
    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; 
+0

這不起作用,行爲與我原來的測試程序相同,第二次選擇/寫入仍然成功。 – 2010-08-09 02:53:49