tcp服务端如何判断客户端断开连接
⼀篇⽂章:
最近在做⼀个服务器端程序,C/S结构。功能⽅⾯⽐较简单就是client端与server端建⽴连接,然后发送消息给server。我在server端会使⽤专门的线程处理⼀条socket连接。这就涉及到⼀个问题,如果socket连接断开(异常,正常)后,我如何才能感知到?server端这边是绝对被动的,sever端不能主动断开连接。也没有连接链路维持包之类的。client端发送数据的时间也是不定的。在socket连接断开后, server要能够感知到并释放资源。
这个问题在思考测试,询问同事之后,到了⼀个⽅法,可以做到这⼀点。
当使⽤ 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会⾃动断开连接吗?
已经建⽴了TCP连接,并可能互通信息。但是如果长时间不进⾏信息的传递。这个TCP连接会⾃动断开吗?
如果能⾃动断开的话,这个时间⼤约是多少呢?
回答:TCP的保活定时器能够保证TCP连接⼀直保持,但是TCP的保活定时器不是每个TCP/IP协议栈就
实现了,因为RFC并不要求TCP保活定时器⼀定要实现。
摘⾃《TCP/IP详解》卷1第23章:保活并不是T C P规范中的⼀部分。Host Requirements RFC提供了3个不使⽤保活定
时器的理由: (1) 在出现短暂差错的情况下,这可能会使⼀个⾮常好的连接释放掉;
(2)它们耗费不必要的带宽;(3)在按分组计费的情况下会在互联⽹上花掉更多的钱。
然⽽,许多实现提供了保活定时器。
更具体的资料,请参阅RFC。
tcp/ip详解更全⾯的描述:
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)返回,但那是在客户端,是由客户端发送的数据。如果客户端消失,留给了服务器端半打开的连接,并且服务器⼜在等待客户端的数据,那么等待将永远持续下去。存活特征的⽬的就是在服务器端检测这种半打开连接。
更多:
我的⽅法不⼀样,我⽤getsockopt来判断,还是蛮准确的
1. int SocketConnected(int sock)
2. {
3. if(sock<=0)
4. return 0;
5. struct tcp_info info;
6. int len=sizeof(info);
7. getsockopt(sock, IPPROTO_TCP, TCP_INFO, &info, (socklen_t *)&len);
8. if((pi_state==TCP_ESTABLISHED))
9. {recv函数
10. //myprintf("socket connected\n");
11. return 1;
12. }
13. else
14. {
15. //myprintf("socket disconnected\n");
16. return 0;
17. }
18. }
tcp_info和TCP_ESTABLISHED在
包含
#include <linux/types.h>
#include <asm/byteorder.h>
#include <linux/config.h>
#include <>
#include <>
#include <>
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((pi_state==TCP_ESTABLISHED))
{
//myprintf("socket connected\n");
return 1;
}
else
{
//myprintf("socket disconnected\n");
return 0;
}
}
⽬前主要有三种⽅法来实现⽤户掉线检测:SO_KEEPALIVE ,SIO_KEEPALIVE_VALS 和Heart-Beat线程。下⾯我就上⾯的三种⽅法来做⼀下介绍。
(1)SO_KEEPALIVE 机制
这是socket库提供的功能,设置接⼝是setsockopt API:
BOOL bSet=TRUE;
setsockopt(hSocket,SOL_SOCKET,SO_KEEPALIVE,(const char*)&bSet,sizeof(BOOL));
根据MSDN的⽂档,如果为socket设置了KEEPALIVE选项,TCP/IP栈在检测到对⽅掉线后,
任何在该socket上进⾏的调⽤(发送/接受调⽤)就会⽴刻返回,错误号是WSAENETRESET ;
同时,此后的任何在该socket句柄的调⽤会⽴刻失败,并返回WSAENOTCONN错误。
该机制的缺点也很明显:
默认设置是空闲2⼩时才发送⼀个“保持存活探测分节”,不能保证实时检测!
当然也可以修改时间间隔参数,但是会影响到所有打开此选项的套接⼝!
关联了完成端⼝的socket可能会忽略掉该套接字选项。
(2)SIO_KEEPALIVE_VALS 机制
设置接⼝是WSAIoctl API:
DWORD dwError = 0L ;
tcp_keepalive sKA_Settings = {0}, sReturned = {0} ;
ff = 1 ;
sKA_Settings.keepalivetime = 5500 ; // Keep Alive in 5.5 sec.
sKA_Settings.keepaliveinterval = 3000 ; // Resend if No-Reply
if (WSAIoctl(skNewConnection, SIO_KEEPALIVE_VALS, &sKA_Settings,
sizeof(sKA_Settings), &sReturned, sizeof(sReturned), &dwBytes,
NULL, NULL) != 0)
{
dwError = WSAGetLastError() ;
}
实现时需要添加tcp_keepalive and SIO_KEEPALIVE_VALS的定义⽂件MSTCPiP.h
该选项不同于SO_KEEPALIVE 机制的就是它是针对单个连接的,对系统其他的套接
⼝并不影响。
针对完成端⼝的socket,设置了SIO_KEEPALIVE_VALS后,激活包由TCP STACK来负责。
当⽹络连接断开后,TCP STACK并不主动告诉上层的应⽤程序,但是当下⼀次RECV或者SEND操作进⾏后,马上就会返回错误告诉上层这个连接已经断开了.如果检测到断开的时候,在这个连接上有正在PENDING的IO操作,则马上会失败返回.
该机制的缺点:
不通⽤啦。MS的API只能⽤于Windows拉。不过,优雅⼀些^_^.
(3)Heart-Beat线程
没说的。⾃⼰写⼀个后台线程,实现Heart-Beat包,客户端受到该包后,⽴刻返回相应的反馈包。该⽅法的好处是通⽤,但缺点就是会改变现有的通讯协议!
/* 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;
}
return0;
}
/* 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));
}
}
}
⼀篇⽂章:
keep alive VS heart beart:
这周在上班的路上看了本书《Effective TCP/IP Programming》,以下是⼀些读书笔记。顺带推荐⼀下这本书,写的很棒,适⽤于像我这样经常要写⼀些有⼀定质量的⽹络编程,但⼜没时间啃那些讲解TCPIP协议⼤部头书的⼈。
很多⼈都知道TCP并不会去主动检测连接的丢失,这意味着,如果双⽅不产⽣交互,那么如果⽹络断了或者有⼀⽅机器崩溃,另外⼀⽅将永远不知道连接已经不可⽤了。检测连接是否丢失的⽅法⼤致有两种:keepalive和heart-beat。
Keepalive是很多的TCP实现提供的⼀种机制,它允许连接在空闲的时候双⽅会发送⼀些特殊的数据段,并通过响应与否来判断连接是否还存活着(所谓keep~~alive)。我曾经写过⼀篇,但后来我也发现,其实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来专门传输“⼼跳”呢?我个⼈认为两者皆可。如果担⼼的是端到端的连接状态,那么就直接在该条连接中实现“⼼跳”。但很多时候,关注的是⽹络状况和两台主机间的连接状态,这种情况下,创建专门的“⼼跳”连接也未尝不可。