首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 操作系统 > UNIXLINUX >

拥塞读 真有硬伤

2013-11-08 
阻塞读 真有硬伤?本帖最后由 shouso888 于 2013-10-30 10:47:40 编辑捣捣stevens的《Unix 网络编程》后,留下

阻塞读 真有硬伤?
本帖最后由 shouso888 于 2013-10-30 10:47:40 编辑 捣捣stevens的《Unix 网络编程》后,留下的疑问:

客户端 阻塞于 read; 服务器主机崩溃,来不及做close 发出 FIN。

问:阻塞read 会一直阻塞?而且完全没有任何可能让它返回?因为对端主机崩溃时,只有写操作才能引起
ETIMEOUT,如果是read就会一直阻塞,TCP也不会知道对端已经崩溃。那阻塞read就是个Bug啊,不知道对端崩
溃还一直傻傻的等,用它就是埋下隐患?

网络编程 unix tcp Linux
[解决办法]
可以设置keepalive;也可以自己加心跳包;
[解决办法]
就像你把网线拔了, socket并不知道这个事情的发生, 如果你什么动作都没做过的话.
[解决办法]
网络不通,第一次可以写,第二次写立马弹出一个错误,而且如果拔网线,有时候会被发送信号,程序不处理,有可能被杀死。TCP对于长时间无影响的连接,我认为是会进入CLOSE_WAIT状态,很久很久以后就断开。
[解决办法]
read是会阻塞,但是不会一直阻塞的!协议里会有一个超时的!
[解决办法]
用阻塞的应用一般都会考虑超时的,例如用SO_RCVTIMEO or alarm等
灵活性强了不是bug
[解决办法]

引用:
Quote: 引用:

阻塞读可以自己设置timeout,
struct timeval timeout = {20, 0 };
int result = setsockopt(connfd, SOL_SOCKET, SO_RCVTIMEO,
(char *) &timeout, sizeof(timeout));
设置20秒超时,楼主可以去试试

这个我知道,谢谢提醒。
我现在最想知道:阻塞read 不设置超时, 是不是会 一直阻塞。


man手册是这样说的
If  no messages are available at the socket, the receive calls wait for a message to arrive, unless the socket is non-blocking (see  fcntl(2)),
楼主不要纠结read是否一直阻塞的问题,阻塞的系统调用大都会被EINTR信号打断,当你阻塞的recv被意外返回时,你可以perror打出来看看
[解决办法]
引用:
Quote: 引用:

如果不设置超时,也没有设置SO_KEEPALIVE,会一直阻塞。


我也是这么想的,但其他的版主怎么说“read是会阻塞,但是不会一直阻塞的!协议里会有一个超时的!”
哎,不就结了。我就笃定它是永远阻塞吧。

另外其实你说的SO_KEEPALIVE原理也是间歇性的与对端有交互数据,所以保持了链接(因为太久没动作的链
接会被防火墙什么的关闭)。相当于向对端写东西,如果一直收不到对方的TCP确认就置为自己机器的SO_ERROR,
进而使我的阻塞读返回 -1。

如果有防火墙或者是通过NAT连接出去的,我就不能确定了。
但是如果两台机器是直接连接的,并且也没有设置SO_KEEPALIVE的话,read永远不会返回的。
以下的测试代码可以说明这一点

/**
 * @file    demo.c
 * @brief   
 */


#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netdb.h>

#include <unistd.h>
#include <signal.h>

#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

int  init(void)
{
    FILE *fp;
    char path[PATH_MAX];

    daemon(0, 0);

    signal(SIGCHLD, SIG_IGN);

    close(STDOUT_FILENO);
    close(STDERR_FILENO);
    sprintf(path, "%s/log/demo.log", getenv("HOME"));
    fp = fopen(path, "a+b");
    dup2(STDOUT_FILENO, STDERR_FILENO);

    return 0;
}

int main(int argc, char *argv[])
{
    int servfd, clntfd;
    struct sockaddr_in servaddr, clntaddr;
    socklen_t          servlen,  clntlen;
    int port;
    int keepalive;
    int reuseaddr;
    socklen_t optlen;
    int ret;
    pid_t pid;
    ssize_t len;
    char buf[1024];

    time_t tick;

    keepalive = 0;
    port = 8888;
    if (argc > 1) {
        keepalive = atoi(argv[1]);
        if (keepalive)
            port++;
    }

    init();

    servfd = socket(PF_INET, SOCK_STREAM, 0);
    optlen = sizeof(reuseaddr);
    ret = setsockopt(servfd, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, optlen);
    if (ret < 0) {


        perror("setsockopt SO_REUSEADDR");
        exit(1);
    }

    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(port);

    servlen = sizeof(servaddr);
    ret = bind(servfd, (struct sockaddr *)&servaddr, servlen);
    if (ret < 0) {
        printf("%04d: ERRO %-25.25s:%4d : %s", port, "bind", errno, ctime(&tick));
        exit(1);
    }
    listen(servfd, 5);

    for (;;) {
        clntlen = sizeof(clntaddr);
        clntfd = accept(servfd, (struct sockaddr *)&clntaddr, &clntlen);
        if (clntfd >= 0) {
            pid = fork();
            if (pid == 0) {
                tick = time(NULL);
                printf("%04d: CONN %-30.30s : %s", port, inet_ntoa(clntaddr.sin_addr), ctime(&tick));
                fflush(stdout);
                optlen = sizeof(keepalive);
                ret = setsockopt(clntfd, SOL_SOCKET, SO_KEEPALIVE, &keepalive, optlen);
                if (ret < 0) {
                    tick = time(NULL);
                    printf("%04d: ERRO %-25.25s %4d : %s", port, "setsockopt SO_KEEPALIVE", errno, ctime(&tick));
                    exit(1);
                }
                memset(buf, 0, sizeof(buf));
                len = read(clntfd, buf, sizeof(buf));
                tick = time(NULL);
                printf("%04d: RECV %-30d : %s", port, (int)len, ctime(&tick));
                exit(0);
            } else if (pid > 0) {
                close(clntfd);
                continue;
            } else {
                close(clntfd);
                tick = time(NULL);
                printf("%04d: ERRO %-25.25s:%4d : %s", port, "fork", errno, ctime(&tick));
                continue;
            }
        } else if (errno == EINTR) {
            continue;
        } else {
            tick = time(NULL);
            printf("%04d: ERRO %-25.25s:%4d : %s", port, "accept", errno, ctime(&tick));
            exit(1);
        }
    }

    return 0;
}



下面是日志和网络的状态的输出
netstat -naA inet 
------解决方案--------------------


 grep -E "8888
[解决办法]
8889"
tcp        0      0 0.0.0.0:8888                0.0.0.0:*                   LISTEN      
tcp        0      0 0.0.0.0:8889                0.0.0.0:*                   LISTEN      
tcp        0      0 10.1.1.23:8888              10.1.2.116:1558             ESTABLISHED 
$ cat ~/log/demo.log
8888: CONN 127.0.0.1                      : Mon Nov  4 11:35:09 2013
8888: RECV 6                              : Mon Nov  4 11:35:12 2013
8889: CONN 127.0.0.1                      : Mon Nov  4 11:35:16 2013
8889: RECV 11                             : Mon Nov  4 11:35:22 2013
8888: CONN 10.1.2.116                     : Mon Nov  4 11:46:57 2013
8889: CONN 10.1.2.116                     : Mon Nov  4 11:47:06 2013
8889: RECV -1                             : Mon Nov  4 13:58:21 2013
$ ps -fU $LOGNAME 
[解决办法]
 grep demo
smgview   2684 10925  0 09:24 pts/13   00:00:00 grep demo
smgview   9878     1  0 Nov04 ?        00:00:00 ./demo 0
smgview   9885     1  0 Nov04 ?        00:00:00 ./demo 1
smgview  10038  9878  0 Nov04 ?        00:00:00 ./demo 0
$ date
Wed Nov  6 09:24:20 CST 2013


注意这里:
8889: CONN 10.1.2.116                     : Mon Nov  4 11:47:06 2013
8889: RECV -1                             : Mon Nov  4 13:58:21 2013
设置了SO_KEEPALIVE的8889的连接在2小时11分15秒之后read就返回-1了,可惜的是我没有打印errno,至于说为什么是2小时11分15秒,在Richard Stevens的UNP里有详细的描述。

而8888的连接到今天都还在
tcp        0      0 0.0.0.0:8888                0.0.0.0:*                   LISTEN      
tcp        0      0 0.0.0.0:8889                0.0.0.0:*                   LISTEN      
tcp        0      0 10.1.1.23:8888              10.1.2.116:1558             ESTABLISHED 

热点排行