http://www.360doc.com/content/18/1224/13/61419251_804110743.shtml
- 根据ERRNO和recv结果进行判断
在UNIX/LINUX下,非阻塞模式SOCKET可以采用recv+MSG_PEEK的方式进行判断,其中MSG_PEEK保证了仅仅进行状态判断,而不影响数据接收
对于主动关闭的SOCKET, recv返回-1,而且errno被置为9(#define EBADF 9 /* Bad file number /)或104 (#define ECONNRESET 104 / Connection reset by peer */)
对于被动关闭的SOCKET,recv返回0,而且errno被置为11(#define EWOULDBLOCK EAGAIN /* Operation would block */)
对正常的SOCKET, 如果有接收数据,则返回>0, 否则返回-1,而且errno被置为11(#define EWOULDBLOCK EAGAIN /* Operation would block */)
因此对于简单的状态判断(不过多考虑异常情况):
recv返回>0, 正常
返回-1,而且errno被置为11 正常
其它情况 关闭
int np_socket_alive (int conn_fd) {
char buff[32];
int recv_buff = recv (conn_fd, buff, sizeof (buff), MSG_PEEK);
int sockErr = errno;
if (recv_buff > 0) // Get Data
return 1;
if ((recv_buff == -1) && (sockErr == EWOULDBLOCK)) // No Data recieved
return 1;
return -1;
}
- 通过TCP_KEEPALIVE进行判断
Tcp是面向连接的,在实际应用中通常都需要检测连接是否还可用.如果不可用,可分为:
a. 连接的对端正常关闭.
b. 连接的对端非正常关闭,这包括对端设备掉电,程序崩溃,网络被中断等.这种情况是不能也无法通知对端的,所以连接会一直存在,浪费国家的资源.
tcp协议栈有个keepalive的属性,可以主动探测socket是否可用,不过这个属性的默认值很大.
Linux方法:
全局设置可更改/etc/sysctl.conf,加上:
net.ipv4.tcp_keepalive_intvl = 20
net.ipv4.tcp_keepalive_probes = 3
net.ipv4.tcp_keepalive_time = 60
在程序中设置如下:
int keepAlive = 1; // 开启keepalive属性
int keepIdle = 60; // 如该连接在60秒内没有任何数据往来,则进行探测
int keepInterval = 5; // 探测时发包的时间间隔为5 秒
int keepCount = 3; // 探测尝试的次数.如果第1次探测包就收到响应了,则后2次的不再发.
setsockopt(rs, SOL_SOCKET, SO_KEEPALIVE, (void )&keepAlive, sizeof(keepAlive));
setsockopt(rs, SOL_TCP, TCP_KEEPIDLE, (void)&keepIdle, sizeof(keepIdle));
setsockopt(rs, SOL_TCP, TCP_KEEPINTVL, (void *)&keepInterval, sizeof(keepInterval));
setsockopt(rs, SOL_TCP, TCP_KEEPCNT, (void *)&keepCount, sizeof(keepCount));
Windows方法:
//定义结构及宏
struct TCP_KEEPALIVE {
u_longonoff;
u_longkeepalivetime;
u_longkeepaliveinterval;
} ;
define SIO_KEEPALIVE_VALS _WSAIOW(IOC_VENDOR,4)
//KeepAlive实现
TCP_KEEPALIVE inKeepAlive = {0}; //输入参数
unsigned long ulInLen = sizeof(TCP_KEEPALIVE);
TCP_KEEPALIVE outKeepAlive = {0}; //输出参数
unsigned long ulOutLen = sizeof(TCP_KEEPALIVE);
unsigned long ulBytesReturn = 0;
//设置socket的keep alive为5秒,并且发送次数为3次
inKeepAlive.onoff = 1;
inKeepAlive.keepaliveinterval = 5000; //两次KeepAlive探测间的时间间隔
inKeepAlive.keepalivetime = 5000; //开始首次KeepAlive探测前的TCP空闭时间
if (WSAIoctl((unsigned int)s, SIO_KEEPALIVE_VALS,
(LPVOID)&inKeepAlive, ulInLen,
(LPVOID)&outKeepAlive, ulOutLen,
&ulBytesReturn, NULL, NULL) == SOCKET_ERROR)
{
}
在程序中表现为,当tcp检测到对端socket不再可用时(不能发出探测包,或探测包没有收到ACK的响应包),select会返回socket可读,并且在recv时返回-1,同时置上errno为ETIMEDOUT
补充一个查看tcp连接状态的例子:
int tcp_state(int tcp_fd)
{
struct tcp_info info;
int optlen = sizeof(struct tcp_info);
if (getsockopt (tcp_fd, IPPROTO_TCP, TCP_INFO, &info, (socklen_t *)&optlen) < 0) {
printf ("getsockopt() TCP_INFO error/n"); exit (0);
}
printf ("%d/n",info.tcpi_state);
if (info.tcpi_state == TCP_ESTABLISHED) return 0; /* ESTABLISHED */
else return -1;
}
在一个非阻塞的socket上调用read/write函数, 返回EAGAIN或者EWOULDBLOCK(注: EAGAIN就是EWOULDBLOCK)
从字面上看, 意思是:
-
EAGAIN: 再试一次
-
EWOULDBLOCK: 如果这是一个阻塞socket, 操作将被block
-
perror输出: Resource temporarily unavailable
总结:
这个错误表示资源暂时不够, 可能read时, 读缓冲区没有数据, 或者, write时,
写缓冲区满了.
遇到这种情况, 如果是阻塞socket, read/write就要阻塞掉.
而如果是非阻塞socket, read/write立即返回-1, 同 时errno设置为EAGAIN.
所以, 对于阻塞socket, read/write返回-1代表网络出错了.
但对于非阻塞socket, read/write返回-1不一定网络真的出错了.
可能是Resource temporarily unavailable. 这时你应该再试, 直到Resource available.
综上, 对于non-blocking的socket, 正确的读写操作为:
读: 忽略掉errno = EAGAIN的错误, 下次继续读
写: 忽略掉errno = EAGAIN的错误, 下次继续写
对于select和epoll的LT模式, 这种读写方式是没有问题的. 但对于epoll的ET模式, 这种方式还有漏洞.
网友评论