目前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");
}