如何判断一个tcp连接已经断开

目前linux mips 项目,中有DLNA server 提供NAS 的 多媒体DLNA 服务,当用户请求某个多媒体文件时,进程就pthread_create 一个线程,专门发包,但是当网络断开(Ethernet)时,TCP 传输中断,但是线程没有类似keep live 的检测,而且没有相关的timeout 设置,全凭select / send返回,所以无法及时退出。

可以用的方法:

1. select + 非阻塞方式

read 返回 0,或者 write 返回 EPIPE,这是唯一可用的两种判断网络是否断开的方法。

zt -- http://bbs.chinaunix.net/thread-651036-1-1.html --

2. TCP keep live

但是这是一个系统配置参数,如果修改将影响所有的连接。

3. socket 的超时属性

SO_RCVTIMEO / SO_SNDTIMEO


于是涉及内容

1. select 的 timeout

zt -- http://www.linuxidc.com/Linux/2011-04/34713.htm --

Linux下的select,注意咯,不是pselect。

select()函数的最后一个参数是timeout,用来设置超时用的。

用的是一个struct timeval结构,需要用到sys/time.h文件。

struct timeval {

 long tv_sec; 秒

 long tv_usec; 毫毛

 

}

设置了timeout的值之后呢,select在没有文件描述符监视可用的情况下,会等待这个timeout的时间,时间到了select返回0

如果timeout超时之前有文件描述符可用,则返回可用的数量,这时候的timeout则会依然计数,因此如果想要每次都超时一定的时间那么在slelect返回>0的值之后要重新装填timeout的值一次。以保证超时时间没有变化。

如果tv_sec和tv_usec都是0,那么就是超时时间为0,那么select就会立刻返回了。

如果timeout这里是个NULL,那么超时就未被启用,会一直阻塞在监视文件描述符的地方。

在pselect中的timeout更牛X

用到了struct timespec {

 long tv_sec; 秒

 long tv_nsec; 纳秒

}

时间更精确。

当然了系统做不了这么精确的事情,一般Linux的系统精度也就是10ms左右了。

这里说的重点在于timeout的值的选择和处理上。

另外遇到的一个问题是信号SIGALRM会使得select返回-1并置errno为EINTR。但这个信号已经被我的sigaction干掉了函数处理了,这个问题还需要研究一下。


2. 阻塞socket和非阻塞socket

zt -- http://blog.sina.com.cn/s/blog_4b029ef30100nxcb.html --

读操作
对于阻塞的socket,当socket的接收缓冲区中没有数据时,read调用会一直阻塞住,直到有数据到来才返

回。当socket缓冲区中的数据量小于期望读取的数据量时,返回实际读取的字节数。当sockt的接收缓冲

区中的数据大于期望读取的字节数时,读取期望读取的字节数,返回实际读取的长度。

对于非阻塞socket而言,socket的接收缓冲区中有没有数据,read调用都会立刻返回。接收缓冲区中有
数据时,与阻塞socket有数据的情况是一样的,如果接收缓冲区中没有数据,则返回错误号为

EWOULDBLOCK,
表示该操作本来应该阻塞的,但是由于本socket为非阻塞的socket,因此立刻返回,遇到这样的情况,可

以在下次接着去尝试读取。如果返回值是其它负值,则表明读取错误。
因此,非阻塞的rea调用一般这样写:

if ((nread = read(sock_fd, buffer, len)) <0)
{
 if (errno == EWOULDBLOCK)
  {
   return 0;//表示没有读到数据
  }else return -1; //表示读取失败
}else return nread;读到数据长度

写操作

对于写操作write,原理是类似的,非阻塞socket在发送缓冲区没有空间时会直接返回错误号EWOULDBLOCK,
表示没有空间可写数据,如果错误号是别的值,则表明发送失败。如果发送缓冲区中有足够空间或者
是不足以拷贝所有待发送数据的空间的话,则拷贝前面N个能够容纳的数据,返回实际拷贝的字节数。

而对于阻塞Socket而言,如果发送缓冲区没有空间或者空间不足的话,write操作会直接阻塞住,如果有

足够空间,则拷贝所有数据到发送缓冲区,然后返回.
非阻塞的write操作一般写法是:

int write_pos = 0;
int nLeft = nLen;

while (nLeft > 0)
{
 int nWrite = 0;
 if ((nWrite = write(sock_fd, data + write_pos,nLeft)) <= 0)
 {
  if (errno == EWOULDBLOCK)
                 {
   nWrite =0;
    }else return-1; //表示写失败
 }
 nLeft -= nWrite;
       write_pos += nWrite;
}
return nLen;

建立连接
  阻塞方式下,connect首先发送SYN请求道服务器,当客户端收到服务器返回的SYN的确认时,则

connect
返回.否则的话一直阻塞.
  非阻塞方式,connect将启用TCP协议的三次握手,但是connect函数并不等待连接建立好才返回,而是
立即返回。返回的错误码为EINPROGRESS,表示正在进行某种过程.

接收连接
对于阻塞方式的倾听socket,accept在连接队列中没有建立好的连接时将阻塞,直到有可用的连接,才返

回。
非阻塞倾听socket,在有没有连接时都立即返回,没有连接时,返回的错误码为EWOULDBLOCK,表示本来应
该阻塞。


无阻塞的设置方法

方法一:fcntl
int flag;
if (flag = fcntl(fd, F_GETFL, 0) <0) perror("getflag");
flag |= O_NONBLOCK;
if (fcntl(fd, F_SETFL, flag) < 0)
perror("set flag");

方法二:ioctl

int b_on = 1;
ioctl (fd, FIONBIO, &b_on);


3. TCP keep live

        int keepAlive = 1;    //enabled
        int keepIdle = 5;        //TCP idle time (second)
        int keepInterval = 2;    //keep live proble interval (second)
        int keepCount = 3;    //keep live proble count

        if(setsockopt(sockfd,SOL_SOCKET,SO_KEEPALIVE,(void*)&keepAlive,sizeof(keepAlive)) == -1)
        {
            fprintf(stderr, "sock_read_write-> setsockopt SO_KEEPALIVE failed!\n");
        }
        if(setsockopt(sockfd,SOL_TCP,TCP_KEEPIDLE,(void *)&keepIdle,sizeof(keepIdle)) == -1)
        {
            fprintf(stderr, "sock_read_write-> setsockopt TCP_KEEPIDLE failed!\n");
        }
        if(setsockopt(sockfd,SOL_TCP,TCP_KEEPINTVL,(void *)&keepInterval,sizeof(keepInterval)) == -1)
        {
            fprintf(stderr, "sock_read_write-> setsockopt TCP_KEEPINTVL failed!\n");
        }
        if(setsockopt(sockfd,SOL_TCP,TCP_KEEPCNT,(void *)&keepCount,sizeof(keepCount)) == -1)
        {
            fprintf(stderr, "sock_read_write-> setsockopt TCP_KEEPCNT failed!\n");
        }*/

4. socket 的超时属性

        timeout.tv_sec = 3;
        timeout.tv_usec = 0;
        if(setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(struct timeval)) < 0)
        {
            fprintf(stderr, "sock_read_write-> setsockopt SO_RCVTIMEO failed!\n");
        }

        timeout.tv_sec = 3;

        timeout.tv_usec = 0;
        if(setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(struct timeval)) < 0)
        {
            fprintf(stderr, "sock_read_write-> setsockopt SO_SNDTIMEO failed!\n");
        }

你可能感兴趣的:(工作备忘录)