Socket心跳包机制

http://blog.csdn.net/xuyuefei1988/article/details/8279812

心跳包的发送,通常有两种技术
方法1:应用层自己实现的心跳包 

由应用程序自己发送心跳包来检测连接是否正常,大致的方法是:服务器在一个 Timer事件中定时 向客户端发送一个短小精悍的数据包,然后启动一个低级别的线程,在该线程中不断检测客户端的回应, 如果在一定时间内没有收到客户端的回应,即认为客户端已经掉线;同样,如果客户端在一定时间内没 有收到服务器的心跳包,则认为连接不可用。

方法2:TCP的KeepAlive保活机制
因为要考虑到一个服务器通常会连接多个客户端,因此由用户在应用层自己实现心跳包,代码较多 且稍显复杂,而利用TCP/IP协议层为内置的KeepAlive功能来实现心跳功能则简单得多。 不论是服务端还是客户端,一方开启KeepAlive功能后,就会自动在规定时间内向对方发送心跳包, 而另一方在收到心跳包后就会自动回复,以告诉对方我仍然在线。 因为开启KeepAlive功能需要消耗额外的宽带和流量,所以TCP协议层默认并不开启KeepAlive功 能,尽管这微不足道,但在按流量计费的环境下增加了费用,另一方面,KeepAlive设置不合理时可能会 因为短暂的网络波动而断开健康的TCP连接。并且,默认的KeepAlive超时需要7,200,000 MilliSeconds, 即2小时,探测次数为5次。对于很多服务端应用程序来说,2小时的空闲时间太长。因此,我们需要手工开启KeepAlive功能并设置合理的KeepAlive参数。
以上转自网络。



心跳包机制
  跳包之所以叫心跳包是因为:它像心跳一样每隔固定时间发一次,以此来告诉服务器,这个客户端还活着。事实上这是为了保持长连接,至于这个包的内容,是没有什么特别规定的,不过一般都是很小的包,或者只包含包头的一个空包。
   在TCP的机制里面,本身是存在有心跳包的机制的,也就是TCP的选项:SO_KEEPALIVE。系统默认是设置的2小时的心跳频率。但是它检查不到机器断电、网线拔出、防火墙这些断线。而且逻辑层处理断线可能也不是那么好处理。一般,如果只是用于保活还是可以的。
   心跳包一般来说都是在逻辑层发送空的echo包来实现的。下一个定时器,在一定时间间隔下发送一个空包给客户端,然后客户端反馈一个同样的空包回来,服务器如果在一定时间内收不到客户端发送过来的反馈包,那就只有认定说掉线了。
   其实,要判定掉线,只需要send或者recv一下,如果结果为零,则为掉线。但是,在长连接下,有可能很长一段时间都没有数据往来。理论上说,这个连接是一直保持连接的,但是实际情况中,如果中间节点出现什么故障是难以知道的。更要命的是,有的节点(防火墙)会自动把一定时间之内没有数据交互的连接给断掉。在这个时候,就需要我们的心跳包了,用于维持长连接,保活。
   在获知了断线之后,服务器逻辑可能需要做一些事情,比如断线后的数据清理呀,重新连接呀……当然,这个自然是要由逻辑层根据需求去做了。
   总的来说,心跳包主要也就是用于长连接的保活和断线处理。一般的应用下,判定时间在30-40秒比较不错。如果实在要求高,那就在6-9秒。

心跳检测步骤
1客户端每隔一个时间间隔发生一个探测包给服务器
2客户端发包时启动一个超时定时器
3服务器端接收到检测包,应该回应一个包
4如果客户机收到服务器的应答包,则说明服务器正常,删除超时定时器
5如果客户端的超时定时器超时,依然没有收到应答包,则说明服务器挂了
转自:http://blog.sina.com.cn/s/blog_a459dcf5010153m5.html

根据上面的介绍我们可以知道对端以一种非优雅的方式断开连接的时候,我们可以设置SO_KEEPALIVE属性使得我们在2小时以后发现对方的TCP连接是否依然存在。
具体操作:
    //设置KeepAlive     
   1、 BOOL   bKeepAlive   =   TRUE;     
    int nRet=::setsockopt(sockClient,SOL_SOCKET,SO_KEEPALIVE,(char*)&bKeepAlive,sizeof(bKeepAlive));     
    if(nRet!=0)    
    {     
        AfxMessageBox("出错"); 
        return   ;
    }     
    
   2、感觉两小时时间太长可以自行设定方法1 
//设置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为10秒,并且发送次数为3次     
    inKeepAlive.onoff   =   1;       
    inKeepAlive.keepaliveinterval   =   4000;   //两次KeepAlive探测间的时间间隔     
    inKeepAlive.keepalivetime   =   1000;   //开始首次KeepAlive探测前的TCP空闭时间     
    
    nRet=WSAIoctl(sockClient,       
        SIO_KEEPALIVE_VALS,     
        (LPVOID)&inKeepAlive,     
        ulInLen,     
        (LPVOID)&outKeepAlive,     
        ulOutLen,     
        &ulBytesReturn,     
        NULL,     
        NULL);     
    if(SOCKET_ERROR   ==   nRet)     
    {     
        AfxMessageBox("出错");
        return;    
    }   
3、感觉两小时时间太长可以自行设定方法2
因此我们可以得到
    int                 keepIdle = 6;
    int                 keepInterval = 5;
    int                 keepCount = 3;
    Setsockopt(listenfd, SOL_TCP, TCP_KEEPIDLE, (void *)&keepIdle, sizeof(keepIdle));
    Setsockopt(listenfd, SOL_TCP,TCP_KEEPINTVL, (void *)&keepInterval, sizeof(keepInterval));
    Setsockopt(listenfd,SOL_TCP, TCP_KEEPCNT, (void *)&keepCount, sizeof(keepCount));

详见:http://blog.csdn.net/gavin1203/article/details/5290609
setsockopt的操作,详见:http://www.cnblogs.com/hateislove214/archive/2010/11/05/1869886.html

心跳机制详解    http://blog.csdn.net/aspnet_lyc/article/details/37318861?utm_source=tuicool&utm_medium=referral

  1. 客户端:  
  2.   
  3. /* 
  4. **功能:客户端心跳包发送线程函数 
  5. **参数:线程传参(可传套接字) 
  6. **返回值:空 
  7. */  
  8. void *send_heart(void *addr)  
  9. {  
  10.     while(1){  
  11.         pd->data_type = HEART;  //HEART:数据包类型,pd为数据包结构体指针  
  12.         write(client_sockfd,pd,sizeof(DATA_PACK));  
  13.         sleep(3); //定时3秒  
  14.     }  
  15.     return NULL;  
  16. }  

       
  1. 服务器端:  
  2.   
  3. typefdef struct session{  
  4.     char peerip[16];  
  5.     char name[10];  
  6.     int sockfd;  
  7.     int count;  
  8.     struct session *next;  
  9. }s_t;  
  10.   
  11. /* 
  12. **功能:处理用户心跳包事件,将其会话记录中的计数器清零 
  13. **参数:套接字和数据包指针 
  14. **返回值:无 
  15. */  
  16. void heart_handler(int sockfd,DATA_PACK *pd)  
  17. {  
  18.     s_t *cur = shead->next;  // shead为用户在线会话记录全局变量头指针  
  19.     while( NULL != cur){  
  20.         if(strcmp(cur->name,pd->name) == 0){  
  21.             cur->count = 0;  //将计数器清零,表明用户名为pd->name的客户端还活着  
  22.             printf("客户端IP: %s :用户 %s 连接正常\n",cur->peerip,pd->name);  
  23.         }   
  24.         cur = cur->next;  
  25.     }  
  26. }  
  27.   
  28. /* 
  29. **功能:心跳检测线程函数 
  30. **参数:无 
  31. **返回值:无 
  32. */  
  33. void *heart_check(void *p)  
  34. {  
  35.     printf("心跳检测线程已开启!\n");  
  36.   
  37.     while(1){  
  38.         check_handler(); // 心跳检测处理函数  
  39.         sleep(3); //定时3秒  
  40.     }  
  41.     return NULL;  
  42. }  
  43.   
  44. /* 
  45. **功能:心跳检测处理函数 
  46. **参数:无 
  47. **返回值:无 
  48. */  
  49. void check_handler(){    
  50.     s_t *temp = NULL;  // 用于释放结点    
  51.     s_t **ppNode = &shead->next;   
  52.       
  53.     while(NULL != (*ppNode)){  
  54.         if((*ppNode)->count == 5){   
  55.         printf("客户端IP: %s :用户 %s 已经掉线!!\n",(*ppNode)->peerip,(*ppNode)  
  56.   
  57. ->name);  
  58.         close((*ppNode)->sockfd); //关闭对端套接字  
  59.                 temp = *ppNode; //存储本结点地址  
  60.                 *ppNode = (*ppNode)->next;   //移动指针    
  61.                 free(temp);  //释放结点  
  62.                 temp = NULL;    
  63.                 continue;  
  64.         }  
  65.         else if((*ppNode)->count > 0){  
  66.             printf("客户端IP: %s :用户 %s 连接异常!\n",(*ppNode)->peerip,(*ppNode)-  
  67.   
  68. >name);   
  69.             (*ppNode)->count++;  
  70.         printf("count = %d\n",(*ppNode)->count); //查看计数器内容  
  71.             ppNode = &((*ppNode)->next); // 成员指针  
  72.             continue;  
  73.         }  
  74.         else if((*ppNode)->count == 0){  
  75.             (*ppNode)->count++;  
  76.             printf("count = %d\n",(*ppNode)->count); //查看计数器内容  
  77.             ppNode = &((*ppNode)->next); // 成员指针  
  78.         }  
  79.         else;  
  80.     }       
  81. }    

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值