心跳机制tcp keepalive的讨论 应用及 断网 断电 检测的C代码实现 Windows环境下

分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.****.net/jiangjunshow

也欢迎大家转载本篇文章。分享知识,造福人民,实现我们中华民族伟大复兴!

               

      说明: 1. 本文的讨论和实验都以Windows为例, 其实在linux上也大同小异。

                  2. 在第一次写此博文时, 我对某些地方有一些误解, 现予以更正, 对文章结构做了较大调整,也欢迎大家提出质疑。

                  3. 在做实验玩代码的时候, 意料之中地发现腾讯QQ也在玩心跳, 不清楚具体怎么实现的, 但有点意思哈心跳机制tcp keepalive的讨论 应用及 断网 断电 检测的C代码实现 Windows环境下


      很多网友都问过一个类似这样的问题: tcp连接ok后,网络如果断了, 怎么检测断网心跳机制tcp keepalive的讨论 应用及 断网 断电 检测的C代码实现 Windows环境下对于这个问题, 我曾经给出了一个比较武断的定论: 我说, 断网断电后, tcp是死连接, 客户端和服务端无法感知,必须借助心跳机制。后来, 经过了更多的详细实验和深入思考, 我发现,事实并非完全如此。


       tcp通道建立后, 如果断网断电, 两侧是否会有感知呢? 其实, 这个问题取决于我们的网络结构, 下面, 我以如下网络结构为例进行详细说明。 网络结构为:

心跳机制tcp keepalive的讨论 应用及 断网 断电 检测的C代码实现 Windows环境下

        先说说这幅图, 总体来说, 应该还算比较性感心跳机制tcp keepalive的讨论 应用及 断网 断电 检测的C代码实现 Windows环境下。 其中, pc1做客户端, ip地址是192.168.1.101, pc2做服务端, ip地址是192.168.1.102, 都是dhcp接入的.   请注意: 在做实验的过程中, 每次实验后, 都要关闭服务端和客户端, 且要回复拆掉的线, 断掉的电, 免得影响下次做实验。

      

      确保网络连接良好, 我们来看pc2服务端程序:

#include <stdio.h>#include <winsock2.h> // winsock接口#pragma comment(lib, "ws2_32.lib") // winsock实现int main(){ WORD wVersionRequested;  // 双字节,winsock库的版本 WSADATA wsaData;         // winsock库版本的相关信息  wVersionRequested = MAKEWORD(1, 1); // 0x0101 即:257  // 加载winsock库并确定winsock版本,系统会把数据填入wsaData中 WSAStartup( wVersionRequested, &wsaData );  // AF_INET 表示采用TCP/IP协议族 // SOCK_STREAM 表示采用TCP协议 // 0是通常的默认情况 unsigned int sockSrv = socket(AF_INET, SOCK_STREAM, 0); SOCKADDR_IN addrSrv; addrSrv.sin_family = AF_INET; // TCP/IP协议族 addrSrv.sin_addr.S_un.S_addr = inet_addr("0.0.0.0"); // socket对应的IP地址 addrSrv.sin_port = htons(8888); // socket对应的端口 // 将socket绑定到某个IP和端口(IP标识主机,端口标识通信进程) bind(sockSrv,(SOCKADDR*)&addrSrv, sizeof(SOCKADDR)); // 将socket设置为监听模式,5表示等待连接队列的最大长度 listen(sockSrv, 5); // sockSrv为监听状态下的socket // &addrClient是缓冲区地址,保存了客户端的IP和端口等信息 // len是包含地址信息的长度 // 如果客户端没有启动,那么程序一直停留在该函数处 SOCKADDR_IN addrClient; int len = sizeof(SOCKADDR); unsigned int sockConn = accept(sockSrv,(SOCKADDR*)&addrClient, &len); while(1); // 卡住 closesocket(sockConn);  closesocket(sockSrv); WSACleanup();  return 0;}

 

      我们再看pc1客户端程序:

#include <winsock2.h>#include <stdio.h>#pragma comment(lib, "ws2_32.lib")#define SIO_KEEPALIVE_VALS  _WSAIOW(IOC_VENDOR, 4)// tcp keepalive结构体typedef struct tcp_keepalive {     u_long onoff;     u_long keepalivetime;     u_long keepaliveinterval; }TCP_KEEPALIVE;// 通信的socketSOCKET sockClient = 0;// 监测线程DWORD WINAPI monitorThread(LPVOID pM)  while(1) {  char szRecvBuf[10] = {0};  int nRet = recv(sockClient, szRecvBuf, 1, MSG_PEEK);  // 注意, 最后一个参数必须是MSG_PEEK, 否则会影响主线程接收信息  if(nRet <= 0) // 实际上, 等于0表示服务端主动关闭通信socket  {   printf("监测到啦: nRet is %d\n", nRet);   closesocket(sockClient);   break;  }  Sleep(200); } return 0;}  int main(){ WORD wVersionRequested; WSADATA wsaData; wVersionRequested = MAKEWORD(1, 1); WSAStartup( wVersionRequested, &wsaData ); sockClient = socket(AF_INET, SOCK_STREAM, 0); SOCKADDR_IN addrSrv; addrSrv.sin_addr.S_un.S_addr = inet_addr("192.168.1.102"); addrSrv.sin_family = AF_INET; addrSrv.sin_port = htons(8888); connect(sockClient, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR)); // 开启监测线程 HANDLE handle = CreateThread(NULL, 0, monitorThread, NULL, 0, NULL);  while(1); // 卡住 CloseHandle(handle); closesocket(sockClient); WSACleanup(); return 0;}

      下面, 我们来做几组实验:

      实验一:

      先启动服务端, 再启动客户端, 建立tcp连接。  用netstat -nao | findstr 8888查看两侧的socket状态, 发现是已经建立连接了。


      情形1:

      断掉下行网线2, 用netstat -nao | findstr 8888查看两侧的socket状态, 发现客户端socket状态变了, 且“监测到啦: nRet is -1”打印, 但服务端的socket状态没有变化。 这说明:客户端有感知, 但服务端没有感知。 此时, 服务端是死连接。


      情形2:

      断掉下行网线3, 用netstat -nao | findstr 8888查看两侧的socket状态, 发现客户端socket状态未变, 且没有“监测到啦: nRet is -1”打印, 但服务端的socket状态有变化。 这说明:客户端没有感知, 但服务端有感知。此时, 客户端是死连接。


      情形3:

      断掉路由器上行网线1, 用netstat -nao | findstr 8888查看两侧的socket状态, 发现客户端socket状态未变, 且没有“监测到啦: nRet is -1”打印, 且服务端的socket状态也没有变化。 而且这个时候, tcp连接并不是死连接, 还是活的, 还可以正常通信。 有意思的是, 此时, 我pc1上的QQ和pc2上的QQ过了一段时间都各自断了, 说明腾讯QQ客户端也有心跳机制心跳机制tcp keepalive的讨论 应用及 断网 断电 检测的C代码实现 Windows环境下。注意, pc1上的QQ和pc2上的QQ不直接通信哈。


      情形4:

      断掉路由器电源4, 用netstat -nao | findstr 8888查看两侧的socket状态, 发现客户端socket状态变化, 且有“监测到啦: nRet is -1”打印, 服务端的socket状态也变化。 说明这个时候, 客户端有感知, 服务端也有感知, 两侧都不存在死连接。


      情形5: 

     直接对pc1的电源线5进行断电(当然, 也要把笔记本pc1的电源拔出来才算数),客户端肯定就没了啊。 此时, 服务端socket状态并没有变化, 说明服务端是没有感知的, 服务端是死连接。


      情形6: 

     直接对pc2的电源线6进行断电(当然, 也要把笔记本pc2的电源拔出来才算数),服务端肯定就没了啊。 此时,客户端socket状态并没有变化, 且没有“监测到啦: nRet is -1”打印, 说明客户端是没有感知的, 客户端是死连接。


      我们看一下, 除了情形3外, tcp的正常连接都受到了影响, 而且死连接无法感知, 这显然不符合我们的期望。 那么, 怎么检测tcp死连接呢?  这就是本文要深入讨论的话题------心跳机制心跳机制tcp keepalive的讨论 应用及 断网 断电 检测的C代码实现 Windows环境下


     首先自然会问: 什么是心跳机制? 为什么需要心跳机制? 怎么来实现它? 在本文中, 我会和大家一起来学习一下。

     想一下, 当tcp连接被破坏后, 如果是死连接了, 服务端和客户端怎样才能知道信息能不能到达对方呢? 很自然的想法是, 不断地给对方发探测信号, 看有没有回应, 这就是心跳机制的直白原理。 所谓的心跳即是数据包, 发心跳就是一方向另一方发送的数据包, 不断地发送, 如果收不到回应, 那么就有理由认为是tcp连接出了问题。 那为什么要叫心跳呢? 你摸一下你的心, 你看它是不是均匀在跳? 理解了吧, 均匀发出去的数据包就类似于均匀的心跳信号。 所以, 我要说: 心跳就是(探测性的)数据包。


       到此为主, 我们算是搞懂了什么是心跳机制, 为什么需要心跳机制这两个问题。


       下面, 我们会更深入地讨论心跳机制, 并在最后会写个带心跳机制的客户端程序来实战感受一下。


       从原理上来讲, 服务端的心跳机制和客户端的心跳机制完全一致, 而且彼此独立。 服务端的心跳只能用来检测服务端的死连接, 客户端的心跳只能检测客户端的死连接。

由于服务端和客户端的心跳原理是基本一致的, 所以为了简便起见, 我们仅仅在客户端启用心跳机制, 然后让客户端去检测一下死连接。


        虽然我们说心跳就是数据包, 且我们也可以抓包看到, 但其实这个包的报文段是不含有任何数据的, 因此, 即使你用recv函数, 也不会接收到什么值, 也就是说,如果没有应用层数据通信的话, 即使有循环心跳发送接收, recv也会阻塞在那里, 静静地等待。


        既然说到心跳, 我们就不得不说说心跳发送的频率, 根据RFC的定义, TCP/IP协议栈需要等待的默认时间间隔是2小时。 但是, 对于大多数应用程序来说说, 2个小时后才能检测到死连接又有什么意义呢? 我就不明白了, RFC的作者难道傻么心跳机制tcp keepalive的讨论 应用及 断网 断电 检测的C代码实现 Windows环境下 为什么要定义这么长的一个时间? 翻阅资料后才得知: 原来, RFC作者是为了弱化用户使用心跳机制。关于心跳机制, 一直存在这么两派争论, 支持派:可以简化应用程序的设计, 让客户端或者服务端检测到断网。 反对派:心跳机制浪费了带宽, 而且可能会拆掉某个相对良好的tcp连接/通道。


        好吧, 现在要解决问题, 要检测死连接, 我们还是要继续介绍心跳机制, 好在, 是有接口可以改变心跳参数的。 让我稍微有点不太乐意的是心跳机制tcp keepalive的讨论 应用及 断网 断电 检测的C代码实现 Windows环境下: 为什么心跳机制检测死连接后, 不指定一个回调的函数接口呢? 不过, 也没关系, 既然你不提供, 那我就开个线程来检测。


        当客户端将心跳发给服务端后, 眼巴巴地期望得到服务端的反馈, 如果没有收到反馈, 协议栈自然有理由认为客户端是死连接了(于是, 客户端会发RST包重置链接, 也就是说, 这链接时无效的了),则之后客户端的任何I/O操作或者待处理的I/O操作都将失败。 所以, 自然可以用recv去检测啊, 用recv函数去偷窥接收的内核缓冲区中的数据, 如果反馈-1, 那就表明通信断了(请注意, 实际上, 在此处,recv函数的目的不是为了去获取数据, 也不是为了去探测什么数据, 而是简单地执行一个io操作, 一旦启动心跳机制,协议栈检测到网络异常后,io操作就会自然失败。之所以选择recv, 并把最后一个参数置为MSG_PEEK,  是因为我们要找到一个不影响主线程通信的io操作函数 )。 顺便说一句, 之前说过, 如果服务端主动关闭通信的socket, 客户端的recv函数会返回0, 所以, 综合起来说, 为了检测出连接的异常, 我们用<=0进行判断。


        也啰嗦不少了, 下面给出带有心跳机制的客户端代码吧(说明, 在本文中, 我们认为检测监测是同义词):

#include <winsock2.h>#include <stdio.h>#pragma comment(lib, "ws2_32.lib")#define SIO_KEEPALIVE_VALS  _WSAIOW(IOC_VENDOR, 4)// tcp keepalive结构体typedef struct tcp_keepalive {     u_long onoff;     u_long keepalivetime;     u_long keepaliveinterval; }TCP_KEEPALIVE;// 通信的socketSOCKET sockClient = 0;// 监测线程DWORD WINAPI monitorThread(LPVOID pM)  while(1) {  char szRecvBuf[10] = {0};  int nRet = recv(sockClient, szRecvBuf, 1, MSG_PEEK);  // 注意, 最后一个参数必须是MSG_PEEK, 否则会影响主线程接收信息  if(nRet <= 0) // 实际上, 等于0表示服务端主动关闭通信socket  {   printf("监测到啦: nRet is %d\n", nRet);   closesocket(sockClient);   break;  }  Sleep(200); } return 0;}  int main(){ WORD wVersionRequested; WSADATA wsaData; wVersionRequested = MAKEWORD(1, 1); WSAStartup( wVersionRequested, &wsaData ); sockClient = socket(AF_INET, SOCK_STREAM, 0); // 启用tcp keepalive机制 #if 1     // 设置SO_KEEPALIVE  int iKeepAlive = 1;   int iOptLen = sizeof(iKeepAlive);   setsockopt(sockClient, SOL_SOCKET, SO_KEEPALIVE, (char *)&iKeepAlive, iOptLen);  TCP_KEEPALIVE inKeepAlive = {0, 0, 0};   unsigned long ulInLen = sizeof(TCP_KEEPALIVE);   TCP_KEEPALIVE outKeepAlive = {0, 0, 0};   unsigned long ulOutLen = sizeof(TCP_KEEPALIVE);   unsigned long ulBytesReturn = 0;                   // 设置心跳参数  inKeepAlive.onoff = 1;                  // 是否启用  inKeepAlive.keepalivetime = 1000;       // 在tcp通道空闲1000毫秒后, 开始发送心跳包检测  inKeepAlive.keepaliveinterval = 500;    // 心跳包的间隔时间是500毫秒  /*   补充上面的"设置心跳参数":   当没有接收到服务器反馈后,对于不同的Windows版本,客户端的心跳尝试次数是不同的,   比如, 对于Win XP/2003而言, 最大尝试次数是5次, 其它的Windows版本也各不相同。   当然啦, 如果是在Linux上, 那么这个最大尝试此时其实是可以在程序中设置的。  */                   // 调用接口, 启用心跳机制  WSAIoctl(sockClient, SIO_KEEPALIVE_VALS,    &inKeepAlive, ulInLen,   &outKeepAlive, ulOutLen,    &ulBytesReturn, NULL, NULL); #endif SOCKADDR_IN addrSrv; addrSrv.sin_addr.S_un.S_addr = inet_addr("192.168.1.102"); addrSrv.sin_family = AF_INET; addrSrv.sin_port = htons(8888); connect(sockClient, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR)); // 开启监测线程 HANDLE handle = CreateThread(NULL, 0, monitorThread, NULL, 0, NULL);  while(1); // 卡住 CloseHandle(handle); closesocket(sockClient); WSACleanup(); return 0;}

      我们重做实验一, 也就是如下的实验二:

      先启动服务端, 再启动有心跳机制的客户端, 建立tcp连接。  用netstat -nao | findstr 8888查看两侧的socket状态, 发现是已经建立连接了。


      情形1:

      断掉下行网线2, 用netstat -nao | findstr 8888查看两侧的socket状态, 发现客户端socket状态变了, 且“监测到啦: nRet is -1”打印, 但服务端的socket状态没有变化。 这说明:客户端有感知, 但服务端没有感知。 此时, 服务端是死连接。 (因为服务端没有心跳, 所以还是检测不了服务端的死连接)


      情形2:

      断掉下行网线3, 用netstat -nao | findstr 8888查看两侧的socket状态, 发现客户端socket状态变了, 且有“监测到啦: nRet is -1”打印, 且服务端的socket状态有变化。 这说明:客户端的心跳感知到了死连接, 而且服务端地自己本身的异常也是有感知的(不是借助心跳机制)。


      情形3:

      断掉上行网线1, 用netstat -nao | findstr 8888查看两侧的socket状态, 发现客户端socket状态未变, 且没有“监测到啦: nRet is -1”打印, 且服务端的socket状态也没有变化。 而且这个时候, tcp连接并不是死连接, 还是活的, 还可以正常通信。 (此时, 通信ok, 没有死连接,  所以心跳机制不会检测到什么死连接)。 有意思的是, 此时, 我pc1上的QQ和pc2上的QQ过了一段时间都各自断了, 说明腾讯QQ客户端也有心跳机制心跳机制tcp keepalive的讨论 应用及 断网 断电 检测的C代码实现 Windows环境下。注意, pc1上的QQ和pc2上的QQ不直接通信哈。


      情形4:

      断掉路由器电源4, 用netstat -nao | findstr 8888查看两侧的socket状态, 发现客户端socket状态变化, 且有“监测到啦: nRet is -1”打印, 服务端的socket状态也变化。 说明这个时候, 客户端有感知, 服务端也有感知, 两侧都不存在死连接。 (不要心跳机制都能检测到啊, 何况有了心跳机制)


      情形5: 

     直接对pc1的电源线5进行断电(当然, 也要把笔记本pc1的电源拔出来才算数),客户端肯定就没了啊。 此时, 服务端socket状态并没有变化, 说明服务端是没有感知的, 服务端是死连接。(因为服务端没有心跳, 所以还是检测不了服务端死连接)


      情形6: 

     直接对pc2的电源线6进行断电(当然, 也要把笔记本pc2的电源拔出来才算数),服务端肯定就没了啊。 此时,客户端socket状态有变化, 且有“监测到啦: nRet is -1”打印, 说明客户端的心跳对死连接是有感知的。

      看来, 心跳机制确实生效了, 以上介绍的主要是tcp协议栈自身提供的心跳机制, 当然, 我们也可以自己在应用层写写自己的心跳机制, 代码会相对复杂一些, 但灵活度也会更大。 从作用上来讲, 殊途同归。总之, 借助心跳机制, 可以检测到tcp连接的异常心跳机制tcp keepalive的讨论 应用及 断网 断电 检测的C代码实现 Windows环境下


      最后, 我们来简要说说另外一种网络结构, 假设把pc1和pc2直接用网线相连, 建立起世界最小局域网,  并形成tcp连接。如果在客户端和服务端都没有心跳机制,那么实验结果如下

     1. 如果断掉其中的网线, 客户端和服务端都没有感知。

     2. 客户端突然断电, 则服务端没有感知。

     3.服务端突然断电, 则客户端没有感知。


      有兴趣的朋友可以验证一下上述结果。


      好了, 心跳机制的介绍到此为止。 未来, 路漫漫, 但必将继续勇敢前行!心跳机制tcp keepalive的讨论 应用及 断网 断电 检测的C代码实现 Windows环境下




           

给我老师的人工智能教程打call!http://blog.****.net/jiangjunshow

心跳机制tcp keepalive的讨论 应用及 断网 断电 检测的C代码实现 Windows环境下