`

[转]基于Windows Socket 的网络通信中的心跳机制原理

 
阅读更多
http://blog.sina.com.cn/s/blog_608740170100wni3.html
引言
    在采用TCP 连接的C/S 结构的系统中,当通信的一方正常关闭或退出时,另一方能收到相应的连接
断开的通知,然后进行必要的处理;但如果任意一方发生所谓的“非优雅断开”,如:意外崩溃、死机、
拔掉网线或路由器故障时,另一方无法得知TCP 连接已经失效,除非继续在此连接上不断地发送数据,
经过若干时间后导致错误返回。但在很多时候,更希望服务器端和客户端都能及时有效地检测到网络连
接的非正常断开,然后完成一些必要的清理工作并把错误报告给用户。
    如何及时有效地检测到通信一方的非正常断开,采用的方法是通过通信的一方或双方发送心跳包来
告诉对方网络通信是否正常或已断开。
1. 心跳原理
    在基于电路交换的网络中,有专用的控制信令通道,能够及时发现通路断开、故障,而TCP/IP 网络
中,链路的连通只在连接双方记录状态,物理通道内不存在一个实际的连接链路,通信的双方只能定时
发送简单的信息给另一方,并根据超时来判断线路是长时间空闲还是已断开。这种通过每隔一定时间发
送一个固定信息给对方,对方收到后回复一个固定信息,告诉对方“我还在”的方式非常类似于心跳,所
发送的这种简单信息就称为“心跳包”。
心跳包的发送,通常有两种技术:一种是由用户在应用层实现的心跳包,另一种是由TCP 协议层提
供的KeepAlive 。
2. 应用层自己实现的心跳包
    由应用程序自己发送心跳包来检测连接是否正常,大致的方法是:服务器在一个Timer 事件中定时
向客户端发送一个短小精悍的数据包,然后启动一个低级别的线程,在该线程中不断检测客户端的回应,
如果在一定时间内没有收到客户端的回应,即认为客户端已经掉线;同样,如果客户端在一定时间内没
有收到服务器的心跳包,则认为连接不可用。

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

目前主要有三种方法来实现用户掉线检测: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} ;
     sKA_Settings.onoff = 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包,客户端受到该包后,立刻返回相应的反馈  包。
    该方法的好处是通用,但缺点就是会改变现有的通讯协议!
有开发网络应用经历的人都知道,网络中的接收和发送数据都是使用WINDOWS中的SOCKET进行实现。但是如果此套接字已经断开,那发送数据和接收数据的时候就一定会有问题。可是如何判断这个套接字是否还可以使用呢?
  有人一定想到使用Send函数中的返回结果来进行判断。如果返回的长度和自己发送出去的长度一致,那就说明这个套接字是可用的,否则此套接字一定出现了问题。但是我们并不是无时无刻的发送数据呀。如何解决呢?
  其实TCP中已经为我们实现了一个叫做心跳的机制。如果你设置了心跳,那TCP就会在一定的时间(比如你设置的是3秒钟)内发送你设置的次数的心跳(比如说2次),并且此信息不会影响你自己定义的协议。
  在VC中实现心跳的例子很多,可是在DLEPHI中一直没有相应的代码。下面我是我使用DELPHI编写的关于心跳的代码(以IOCP为例),希望对大家有帮助。
定义心跳常量
const
  IOC_IN               =$80000000;
  IOC_VENDOR           =$18000000;
  IOC_out              =$40000000;
  SIO_KEEPALIVE_VALS   =IOC_IN or IOC_VENDOR or 4;
var
  inKeepAlive,OutKeepAlive:TTCP_KEEPALIVE;
实现代码是在Acceptsc:= WSAAccept(Listensc, nil, nil, nil, 0);代码的后面加入:
      opt:=1;
      if setsockopt(Acceptsc,SOL_SOCKET,SO_KEEPALIVE,@opt,sizeof(opt))=SOCKET_ERROR then
      begin
        closesocket(Acceptsc);
      end;
      inKeepAlive.onoff:=1;
      //设置3秒钟时间间隔
  inKeepAlive.keepalivetime:=3000;
      //设置每3秒中发送1次的心跳
      inKeepAlive.keepaliveinterval:=1;
      insize:=sizeof(TTCP_KEEPALIVE);
      outsize:=sizeof(TTCP_KEEPALIVE);
      if WSAIoctl(Accept,SIO_KEEPALIVE_VALS,@inKeepAlive,insize,@outKeepAlive,outsize,@outByte,nil,nil)=SOCKET_ERROR then
      begin
        closesocket(Acceptsc);
      end;
如果加入以上的代码以后,系统会每3秒中加入一次的心跳。并且如果客户端断线以后(网线断),函数GetQueuedCompletionStatus会返回FALSE。
if (GetQueuedCompletionStatus(CompletionPort, BytesTransferred,DWORD(PerHandleData), POverlapped(PerIoData), INFINITE) = False) then
        begin
           //在这里处理客户端断线信息。
   continue;
        end;
以上就是我使用心跳的方法,此方法我已经在我的网络游戏中使用。情况稳定!
网络程序容易出现死连接,连接以经无效了,但是连接状态还是ESTABLISHED状态,如何识别连接以经失效就需要使用


------------------------------------------------------------------------------------------------------
下面是方法2的一段可用代码(经过僧僧鞋子 测试 可用!!!)
---------------------------------------------------------------------------------------
保活机制(心跳机制)下面是一个代码效果还是挺好的,感谢cooldiyer的资料

typedef struct STcpKeepAlive2   
{   
    DWORD onoff;   
    DWORD keepalivetime;   
    DWORD keepaliveinterval;   
       
}TCP_KEEP_ALIVE2;   
  
VOID SetKeepLive(SOCKET m_Socket)   
{   
    const char chOpt = 1; // True   
    // Set KeepAlive 开启保活机制, 防止服务端产生死连接   
    if (setsockopt(m_Socket, SOL_SOCKET, SO_KEEPALIVE, (char *)&chOpt, sizeof(chOpt)) == 0)   
    {   
        // 设置超时详细信息   
        TCP_KEEP_ALIVE2 klive;   
        klive.onoff = 1; // 启用保活   
        klive.keepalivetime = 1000 * 15; // 3分钟超时 Keep Alive   
        klive.keepaliveinterval = 1000 * 5; // 重试间隔为5秒 Resend if No-Reply   
        WSAIoctl   
            (   
            m_Socket,    
            SIO_KEEPALIVE_VALS,   
            &klive,   
            sizeof(TCP_KEEP_ALIVE2),   
            NULL,   
            0,   
            (unsigned long *)&chOpt,   
            0,   
            NULL   
            );   
    }   
}
 

其中SIO_KEEPALIVE_VALS的值定义为: const int SIO_KEEPALIVE_VALS  =IOC_IN | IOC_VENDOR | 4;

注:SIO_KEEPALIVE_VALS  只有在winsock2下才可使用
#include <winsock2.h>
#include <stdio.h>
#include <iostream.h>
 
SOCKET ser;
SOCKET client;
SOCKADDR_IN clientaddr;
int len=sizeof(SOCKADDR);
char buff[1024];
DWORD WINAPI Fun1(LPVOID lpParameter);
void main()
{
       WORD wVersionRequested;
       WSADATA wsaData;
       int err;
      
       wVersionRequested = MAKEWORD( 2, 2 );
      
       err = WSAStartup( wVersionRequested, &wsaData );
       if ( err != 0 ) {
              return;
       }    
      
       if ( LOBYTE( wsaData.wVersion ) != 2 ||
        HIBYTE( wsaData.wVersion ) != 2 ) {
              WSACleanup( );
              return;
       }
      
       ser=socket(AF_INET,SOCK_STREAM,0);
       int TimeOut=5000;//设置接收超时6秒
       if(::setsockopt(ser,SOL_SOCKET,SO_RCVTIMEO,(char *)&TimeOut,sizeof(TimeOut))==SOCKET_ERROR)
       {
              return ;
       }
      
       SOCKADDR_IN seraddr;
       seraddr.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
       seraddr.sin_family=AF_INET;
       seraddr.sin_port=htons(12345);
       bind(ser,(SOCKADDR*)&seraddr,sizeof(SOCKADDR));
      
       listen(ser,5);
    while (1)
       {
stop:client=accept(ser,(SOCKADDR*)&clientaddr,&len);
      
           DWORD lngPassTime=0;
           while(lngPassTime<=2500)   
              {    
                  DWORD lngStartTime=GetTickCount(); 
                     if(recv(client,buff,1024,0)==SOCKET_ERROR)
            {
                            cout<<WSAGetLastError()<<endl;                   
                            break;
                     }
                     else
                            cout<<buff<<endl;
                     lngPassTime=GetTickCount()-lngStartTime;  
                    
                     send(client,"收到!",strlen("收到")+1,0);
        }
              closesocket(client);
              closesocket(client);
              goto stop;                    
    }
       return;
}

用vc怎么发个心跳包来检测对方是否掉线
2009-04-03 15:02
//设置KeepAlive 
BOOL bKeepAlive = TRUE; 
nRet = ::setsockopt(m_sockDesc, SOL_SOCKET, SO_KEEPALIVE, (char*)&bKeepAlive, sizeof(bKeepAlive)); 
if (nRet !=0) 
{ 
sprintf(m_pszError, "Winsock error : %s (Error Code %d)\n ", "Socket SetOpt failed ", WSAGetLastError()); 
return FALSE; 
} 

//设置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 = 10000; //两次KeepAlive探测间的时间间隔 
inKeepAlive.keepalivetime = 3; //开始首次KeepAlive探测前的TCP空闭时间 

nRet = WSAIoctl(m_sockDesc, 
SIO_KEEPALIVE_VALS, 
(LPVOID)&inKeepAlive, 
ulInLen, 
(LPVOID)&outKeepAlive, 
ulOutLen, 
&ulBytesReturn, 
NULL, 
NULL); 
if(SOCKET_ERROR == nRet) 
{ 
sprintf(m_pszError, "Winsock error : %s (Error Code %d)\n ", "Nonblocking socket call error ", WSAGetLastError()); 
return FALSE; 
}
分享到:
评论

相关推荐

    基于Socket的网络通信设计

    - 考虑缓冲区大小、心跳机制、连接复用等策略,以提高网络通信效率。 总结来说,基于Socket的网络通信设计涉及网络协议理解、编程语言运用、API调用以及图形界面的集成。通过这些知识点的综合运用,我们可以构建出...

    心跳机制原理及其实现

    本文将详细介绍心跳机制的基本原理及其在Windows Socket网络通信中的实现方法。 #### 二、心跳机制原理 心跳机制是一种通过定期发送特定的信息来维持网络连接有效性的技术。它模仿了生物体的心跳现象,通过周期性...

    基于WinSock的即时通信软件功能原理模拟

    为了实现实时性,可以采用长连接或者心跳机制。 4. **在线状态**:客户端定期向服务器发送心跳包,表示其在线状态。服务器根据心跳包判断用户是否在线,并更新用户的状态信息。 **实现步骤** 1. **创建服务器端**...

    NET.rar_基于Socket_网络课程设计_聊天 课程设计_计算机网络课程设计

    在这个课程设计中,我们将探讨如何使用Socket来实现一个简单的网络聊天室,该聊天室基于VC++开发,让学生深入理解网络通信的基本原理。 首先,我们来了解一下Socket。Socket是应用层与传输层之间的一个抽象接口,它...

    基于MFC的双机通信程序

    在IT领域,MFC(Microsoft Foundation Classes)是一个C++库,由微软开发,用于构建Windows应用程序。...通过这个项目,开发者可以深入理解和实践网络通信的基本原理,并且提升在MFC环境下的应用开发能力。

    Socket通信.7z

    总之,这个"Socket通信.7z"压缩包的内容可能是关于如何在WinForm应用中使用Socket进行网络通信的一个实践案例,它可以帮助开发者理解和掌握Socket通信的基本原理和实现方式,为构建网络应用提供基础。

    基于java的网络即时通信系统

    【标题】:“基于Java的网络即时通信系统” ...通过学习和实践这个基于Java的网络即时通信系统,不仅可以提升Java编程技能,还能深入理解网络通信的原理,为开发其他类型的应用程序打下坚实基础。

    基于TCP/IP SOCKET的多点聊天系统

    在IT领域,网络通信是至关重要的部分,而TCP/IP套接字(Socket)则是实现这一目标的核心工具。本文将深入探讨基于TCP/IP SOCKET的多点聊天系统,它在Windows环境中如何运作,以及与之相关的技术点。 首先,TCP/IP...

    基于MFC的TCP心跳检测包

    在IT领域,网络通信是不可或缺的一部分,而TCP(Transmission Control Protocol)作为一种面向连接的、可靠的传输协议,广泛应用于各种应用程序中。心跳检测是TCP通信中一个重要的机制,用于确保网络连接的稳定性和...

    socket library

    2. **套接字(Socket)概念**:套接字是网络通信中的端点,它代表了一个网络连接。在Python中,socket模块提供了创建和操作套接字的接口。 3. **Socket类型**:分为流式套接字(SOCK_STREAM,对应TCP)和数据报套接...

    UnityNetWork:一套完整的unity的socket网络通信模块

    UnityNetWork是一个针对Unity引擎开发的全面的Socket网络通信模块,专为实时在线游戏和应用设计。这个模块提供了高效、稳定且易于集成的网络通信解决方案,使得开发者能够快速构建网络功能,如玩家交互、游戏状态...

    C# ppc gprs socket 源码

    PPC(Pocket PC)是微软早期推出的一种移动设备操作系统,它基于Windows CE内核。GPRS(General Packet Radio Service)是一种2.5G的移动通信技术,允许用户通过手机或移动设备进行数据传输。Socket编程则是网络编程...

    socket 编程之入门与提高

    总之,掌握Socket编程需要理解网络通信的基本原理,熟悉C++及MFC中的相关API,并通过实践不断优化代码,解决实际问题。通过学习和实践“socket编程之入门与提高”,你将能够从容应对各种网络编程挑战,成为真正的...

    socket网络编程

    Socket网络编程是计算机科学中的一个重要领域,特别是在分布式系统和互联网应用中。它是应用程序通过网络通信的一种方式,允许两台或多台计算机上的程序进行数据交换。本文将深入探讨socket网络编程的基础知识,包括...

    fastsocket&simplesocket

    在IT行业中,网络通信是不可或缺的一部分,而Socket编程则是实现这一功能的核心技术。"fastsocket&simplesocket" 提供了两个开源库,旨在简化和优化Socket编程过程,提高网络通信效率。下面将分别对这两个库进行详细...

    vc6.0平台 socket 实现 多点和点对点聊天

    在IT行业中,网络通信是至关重要的一个领域,特别是在开发分布式系统和实时交互应用时。本文将深入探讨如何在Microsoft Visual C++ 6.0(简称VC6.0)平台上使用Socket API来实现点对点(P2P)和多点(多用户)聊天...

    高效的socket tcp IOCP完成端口通信机制, 实现多人同时在线. 本实例登录服务器同时在线的最大人数为20000.zip

    4. **心跳机制**:维持连接状态,定期发送心跳包检测客户端是否在线,防止死连接占用资源。 5. **负载均衡**:如果服务器集群部署,可以考虑使用负载均衡策略,如轮询、权重分配等,将用户请求分发到不同的服务器,...

    C#Socket异步服务器 IOCP 源码

    Socket是网络通信的基础,它提供了进程间通信的接口,让应用程序能够通过TCP/IP协议发送和接收数据。在C#中,`System.Net.Sockets.Socket`类是进行Socket编程的主要对象。 2. **异步编程**: 异步编程在处理大量...

    BCB6实现阻塞模式的SOCKET实例(待完善)

    【标题】:“BCB6实现阻塞模式的SOCKET实例(待完善)”是指使用Borland C++ Builder 6(BCB6)这个集成开发环境来创建一个基于阻塞模式的TCP套接字(SOCKET)应用程序。阻塞模式是网络编程中的一种常见模式,意味着...

    基于C#实现飞信,FeiQ的源码

    在C#中实现飞信功能,首先要了解网络通信的基本原理。套接字是网络通信中的基本组件,它提供了应用程序与传输层协议(如TCP/IP)的接口。C#的System.Net.Sockets命名空间提供了Socket类,用于实现客户端和服务器端的...

Global site tag (gtag.js) - Google Analytics