tcp 服务端如何判断客户端断开连接
2014-10-28 09:42 youxin 阅读(48166) 评论(3) 编辑 收藏 举报一篇文章:
这个问题在思考测试,询问同事之后,找到了一个方法,可以做到这一点。
当使用 select()函数测试一个socket是否可读时,如果select()函数返回值为1,且使用recv()函数读取的数据长度为0 时,就说明该socket已经断开。
为了更好的判定socket是否断开,我判断当recv()返回值小于等于0时,socket连接断开。但是还需要判断 errno是否等于 EINTR 。如果errno == EINTR 则说明recv函数是由于程序接收到信号后返回的,socket连接还是正常的,不应close掉socket连接。
PS:对于堵塞socket的recv函数会在以下三种情况下返回:
(1)recv到数据时,会返回。
(2)在整个程序接收到信号时,返回-1。errno = EINTR。//在程序的起始阶段,屏蔽掉信号的除外。部分信号还是屏蔽不掉的。
(3)socket出现问题时,返回-1.具体错误码看 man recv()
(4)一定要看 man 说明,很详细,很有帮助。
这种方法经过长时间测试后,是有效的。所以写出来让大家参考一下,请大家发表意见。
如果能自动断开的话,这个时间大约是多少呢?
摘自《TCP/IP详解》卷1第23章:保活并不是T C P规范中的一部分。Host Requirements RFC提供了3个不使用保活定
时器的理由: (1) 在出现短暂差错的情况下,这可能会使一个非常好的连接释放掉;
(2)它们耗费不必要的带宽;(3)在按分组计费的情况下会在互联网上花掉更多的钱。
然而,许多实现提供了保活定时器。
更具体的资料,请参阅RFC。
23.1介绍
在一个空闲的(idle)TCP连接上,没有任何的数据流,许多TCP/IP的初学者都对此感到惊奇。也就是说,如果TCP连接两端没有任何一个进程在向对方发送数据,那么在这两个TCP模块之间没有任何的数据交换。你可能在其它的网络协议中发现有轮询(polling),但在TCP中它不存在。言外之意就是我们只要启动一个客户端进程,同服务器建立了TCP连接,不管你离开几小时,几天,几星期或是几个月,连接依旧存在。中间的路由器可能崩溃或者重启,电话线可能go down或者back up,只要连接两端的主机没有重启,连接依旧保持建立。
这就可以认为不管是客户端的还是服务器端的应用程序都没有应用程序级(application-level)的定时器来探测连接的不活动状态(inactivity),从而引起任何一个应用程序的终止。回忆在10.7结束,BGP每隔30秒就向对方发送一个应用程序探测。这是一个应用程序定时器(application timer),与TCP存活定时器不同。
然而有的时候,服务器需要知道客户端主机是否已崩溃并且关闭,或者崩溃但重启。许多实现提供了存活定时器来完成这个任务。
存活(keepalive)并不是TCP规范的一部分。在Host Requirements RFC罗列有不使用它的三个理由:(1)在短暂的故障期间,它们可能引起一个良好连接(good connection)被释放(dropped),(2)它们消费了不必要的宽带,(3)在以数据包计费的互联网上它们(额外)花费金钱。然而,在许多的实现中提供了存活定时器。
存活定时器是一个包含争议的特征。许多人认为,即使需要这个特征,这种对对方的轮询也应该由应用程序来完成,而不是由TCP中实现。一些人对这个话题表现了极大的热情,甚至达到宗教般的*。
如果两个终端系统之间的某个中间网络上有连接的暂时中断,那么存活选项(option)就能够引起两个进程间一个良好连接的终止。例如,如果正好在某个中间路由器崩溃、重启的时候发送存活探测,TCP就将会认为客户端主机已经崩溃,但事实并非如此。
一些服务器应用程序可能代表客户端占用资源,它们需要知道客户端主机是否崩溃。存活定时器可以为这些应用程序提供探测服务。Telnet服务器和Rlogin服务器的许多版本都默认提供存活选项。
个人计算机用户使用TCP/IP协议通过Telnet登录一台主机,这是能够说明需要使用存活定时器的一个常用例子。如果某个用户在使用结束时只是关掉了电源,而没有注销(log off),那么他就留下了一个半打开(half-open)的连接。在图18.16,我们看到如何在一个半打开连接上通过发送数据,得到一个复位(reset)返回,但那是在客户端,是由客户端发送的数据。如果客户端消失,留给了服务器端半打开的连接,并且服务器又在等待客户端的数据,那么等待将永远持续下去。存活特征的目的就是在服务器端检测这种半打开连接。
- int SocketConnected(int sock)
- {
- if(sock<=0)
- return 0;
- struct tcp_info info;
- int len=sizeof(info);
- getsockopt(sock, IPPROTO_TCP, TCP_INFO, &info, (socklen_t *)&len);
- if((info.tcpi_state==TCP_ESTABLISHED))
- {
- //myprintf("socket connected\n");
- return 1;
- }
- else
- {
- //myprintf("socket disconnected\n");
- return 0;
- }
- }
#include <asm/byteorder.h>
#include <linux/config.h>
#include <linux/skbuff.h>
#include <linux/ip.h>
#include <net/sock.h>
{
if(sock<=0)
return 0;
struct tcp_info info;
int len=sizeof(info);
getsockopt(sock, IPPROTO_TCP, TCP_INFO, &info, (socklen_t *)&len);
if((info.tcpi_state==TCP_ESTABLISHED))
{
//myprintf("socket connected\n");
return 1;
}
else
{
//myprintf("socket disconnected\n");
return 0;
}
}
/* Net check Make sure you have not used OUT OF BAND DATA AND YOU CAN use OOB */ int netcheck(int fd) { int buf_size = 1024; char buf[buf_size]; //clear OOB DATA recv(fd, buf, buf_size); if(send(fd, (void *)"\0", 1, MSG_OOB) < 0 ) { fprintf(stderr, "Connection[%d] send OOB failed, %s", fd, strerror(errno)); return -1; } return 0; }
/* Setting SO_TCP KEEPALIVE */ //int keep_alive = 1;//设定KeepAlive //int keep_idle = 1;//开始首次KeepAlive探测前的TCP空闭时间 //int keep_interval = 1;//两次KeepAlive探测间的时间间隔 //int keep_count = 3;//判定断开前的KeepAlive探测次数 void set_keepalive(int fd, int keep_alive, int keep_idle, int keep_interval, int keep_count) { int opt = 1; if(keep_alive) { if(setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (void*)&keep_alive, sizeof(keep_alive)) == -1) { fprintf(stderr, "setsockopt SOL_SOCKET::SO_KEEPALIVE failed, %s\n",strerror(errno)); } if(setsockopt(fd, SOL_TCP, TCP_KEEPIDLE, (void *)&keep_idle,sizeof(keep_idle)) == -1) { fprintf(stderr, "setsockopt SOL_TCP::TCP_KEEPIDLE failed, %s\n", strerror(errno)); } if(setsockopt(fd,SOL_TCP,TCP_KEEPINTVL, (void *)&keep_interval, sizeof(keep_interval)) == -1) { fprintf(stderr, "setsockopt SOL_tcp::TCP_KEEPINTVL failed, %s\n", strerror(errno)); } if(setsockopt(fd,SOL_TCP,TCP_KEEPCNT, (void *)&keep_count,sizeof(keep_count)) == -1) { fprintf(stderr, "setsockopt SOL_TCP::TCP_KEEPCNT failed, %s\n", strerror(errno)); } } }
这周在上班的路上看了本书《Effective TCP/IP Programming》,以下是一些读书笔记。顺带推荐一下这本书,写的很棒,适用于像我这样经常要写一些有一定质量的网络编程,但又没时间啃那些讲解TCPIP协议大部头书的人。
很多人都知道TCP并不会去主动检测连接的丢失,这意味着,如果双方不产生交互,那么如果网络断了或者有一方机器崩溃,另外一方将永远不知道连接已经不可用了。检测连接是否丢失的方法大致有两种:keepalive和heart-beat。
Keepalive是很多的TCP实现提供的一种机制,它允许连接在空闲的时候双方会发送一些特殊的数据段,并通过响应与否来判断连接是否还存活着(所谓keep~~alive)。我曾经写过一篇关于keepalive的blog ,但后来我也发现,其实keepalive在实际的应用中并不常见。为何如此?这得归结于keepalive设计的初衷。Keepalive适用于清除死亡时间比较长的连接。
比如这样的场景:一个用户创建tcp连接访问了一个web服务器,当用户完成他执行的操作后,很粗暴的直接拨了网线。这种情况下,这个tcp连接已经断开了,但是web服务器并不知道,它会依然守护着这个连接。如果web server设置了keepalive,那么它就能够在用户断开网线的大概几个小时以后,确认这个连接已经中断,然后丢弃此连接,回收资源。
采用keepalive,它会先要求此连接一定时间没有活动(一般是几个小时),然后发出数据段,经过多次尝试后(每次尝试之间也有时间间隔),如果仍没有响应,则判断连接中断。可想而知,整个周期需要很长的时间。
所以,如前面的场景那样,需要一种方法能够清除和回收那些在系统不知情的情况下死去了很久的连接,keepalive是非常好的选择。
但是,在大部分情况下,特别是分布式环境中,我们需要的是一个能够快速或者实时监控连接状态的机制,这里,heart-beat才是更加合适的方案。
Heart-beat(心跳),按我的理解,它的原理和keepalive非常类似,都是发送一个信号给对方,如果多次发送都没有响应的话,则判断连接中断。它们的不同点在于,keepalive是tcp实现中内建的机制,是在创建tcp连接时通过设置参数启动keepalive机制;而heart-beat则需要在tcp之上的应用层实现。一个简单的heart-beat实现一般测试连接是否中断采用的时间间隔都比较短,可以很快的决定连接是否中断。并且,由于是在应用层实现,因为可以自行决定当判断连接中断后应该采取的行为,而keepalive在判断连接失败后只会将连接丢弃。
关于heart-beat,一个非常有趣的问题是,应该在传输真正数据的连接中发送“心跳”信号,还是可以专门创建一个发送“心跳”信号的连接。比如说,A,B两台机器之间通过连接m来传输数据,现在为了能够检测A,B之间的连接状态,我们是应该在连接m中传输“心跳”信号,还是创建新的连接n来专门传输“心跳”呢?我个人认为两者皆可。如果担心的是端到端的连接状态,那么就直接在该条连接中实现“心跳”。但很多时候,关注的是网络状况和两台主机间的连接状态,这种情况下, 创建专门的“心跳”连接也未尝不可。