• WindowsSocket心跳实现


    0   引言 
    在采用 TCP连接的 C/S结构的系统中,当通信的一方正常关闭或退出时,另一方能收到相应的连接
    断开的通知,然后进行必要的处理;但如果任意一方发生所谓的“非优雅断开”,如:意外崩溃、死机、
    拔掉网线或路由器故障时,另一方无法得知 TCP 连接已经失效,除非继续在此连接上不断地发送数据,
    经过若干时间后导致错误返回。但在很多时候,更希望服务器端和客户端都能及时有效地检测到网络连
    接的非正常断开,然后完成一些必要的清理工作并把错误报告给用户。 
    如何及时有效地检测到通信一方的非正常断开,采用的方法是通过通信的一方或双方发送心跳包来
    告诉对方网络通信是否正常或已断开。 
    1   心跳原理 
    在基于电路交换的网络中,有专用的控制信令通道,能够及时发现通路断开、故障,而 TCP/IP网络
    中,链路的连通只在连接双方记录状态,物理通道内不存在一个实际的连接链路,通信的双方只能定时
    发送简单的信息给另一方,并根据超时来判断线路是长时间空闲还是已断开。这种通过每隔一定时间发
    送一个固定信息给对方,对方收到后回复一个固定信息,告诉对方“我还在”的方式非常类似于心跳,所
    发送的这种简单信息就称为“心跳包”。 
    心跳包的发送,通常有两种技术:一种是由用户在应用层实现的心跳包,另一种是由 TCP 协议层提
    供的 KeepAlive  。  
    基于Windows Socket的网络通信中的心跳机制原理及其实现 
     
    18 
    2   应用层自己实现的心跳包 
    由应用程序自己发送心跳包来检测连接是否正常,大致的方法是:服务器在一个 Timer 事件中定时
    向客户端发送一个短小精悍的数据包,然后启动一个低级别的线程,在该线程中不断检测客户端的回应,
    如果在一定时间内没有收到客户端的回应,即认为客户端已经掉线;同样,如果客户端在一定时间内没
    有收到服务器的心跳包,则认为连接不可用。 
    以下代码给出在 Delphi 中使用 ServerSocket、ClientSocket 进行网络通信时,如何在服务端实现应用
    层心跳包: 
    //定义一个 SocketData 记录类型,用于保存客户端信息 
    Type SocketData = Record 
        IP: string;             //客户端 IP 
        StartTime: Cardinal;    //每次向客户端发送心跳包的当前时间 
        IsConnected: Boolean;   //是否正与服务端保持连接 
      end; 
      PSocketData = ^SocketData;  //定义一个指向 SocketData 的指针类型  
     
    //保存客户端信息,并创建线程,检测客户端是否有回应 
    procedure TForm1.ServerSktClientConnect(Sender: TObject; 
      Socket: TCustomWinSocket); 
    var P: PSocketData; 
    begin 
      New(p); 
      p.IP := Socket.RemoteAddress; 
      p.IsConnected := true; 
      Socket.Data := p;               
      if not Timer1.Enabled then 
      begin 
        MyThread := TCheckTimeOut.Create(ServerSkt, Panel1);  
        Timer1.Enabled := true; 
      end; 
    end; 
     
    //在定时器的 Timer事件中,每隔 2 秒向所有客户端发送一次心跳包 
    procedure TForm1.Timer1Timer(Sender: TObject); 
    var 
      i, ActConns: integer; 
      CSocket: TCustomWinSocket; 
    begin 
      ActConns := ServerSkt.Socket.ActiveConnections; 
      caption := '连接数:' + inttostr(ActConns); 
      for i := 0 to pred(ActConns) do       
      begin  
    基于Windows Socket的网络通信中的心跳机制原理及其实现 
     
    19 
        CSocket := ServerSkt.Socket.Connections[i]; 
        CSocket.SendText('Msgtest'); 
        if PSocketData(CSocket.Data).IsConnected then 
        begin 
          PSocketData(CSocket.Data).StartTime := GetTickCount; 
          PSocketData(CSocket.Data).IsConnected := false; 
        end; 
      end; 
    end; 
     
    //如果收到客户端的特定回应,由表示该客户处于连接状态 
    procedure TForm1.ServerSktClientRead(Sender: TObject; 
      Socket: TCustomWinSocket); 
    var RecTxt: string; 
    begin 
      RecTxt := Socket.ReceiveText; 
      if RecTxt = 'OK' then          
      begin 
        PSocketData(Socket.Data).IsConnected := true; 
        Memo1.Lines.Add(RecTxt); 
      end; 
    end; 
     
    //线程入口,用于检测客户端是否在规定时间内向服务端回应 
    procedure TCheckTimeOut.Execute; 
    begin 
      while true do 
      begin 
        Synchronize(CheckConnect); 
        if terminated then exit; 
      end; 
    end; 
     
    //检查所有客户端,是否在 5 000(ms)内向服务端发送回应信息 
    procedure TCheckTimeOut.CheckConnect; 
    var i: integer; 
    begin 
      for i := 0 to FServerSocket.Socket.ActiveConnections - 1 do 
        if not PSocketData(FServerSocket.Socket.Connections[i].Data).IsConnected then 
          if (GetTickCount - PSocketData(FServerSocket.Socket.Connections[i].Data)^.StartTime) > 5 000 
    then 
          begin  
    基于Windows Socket的网络通信中的心跳机制原理及其实现 
     
    20 
            Dispose(PSocketData(FServerSocket.Socket.Connections[i].Data)); 
            FServerSocket.Socket.Connections[i].Close; 
          end; 
    end; 
     
    对于客户端而言,只要在收到服务端的心跳包后,简单地发送一个回应信息即可,代码略。 
    3  TCP的 KeepAlive  保活机制 
    因为要考虑到一个服务器通常会连接多个客户端,因此由用户在应用层自己实现心跳包,代码较多
    且稍显复杂,而利用 TCP/IP 协议层为内置的 KeepAlive 功能来实现心跳功能则简单得多。 
    不论是服务端还是客户端,一方开启 KeepAlive功能后,就会自动在规定时间内向对方发送心跳包,
    而另一方在收到心跳包后就会自动回复,以告诉对方我仍然在线。 
    因为开启 KeepAlive 功能需要消耗额外的宽带和流量,所以 TCP 协议层默认并不开启 KeepAlive 功
    能,尽管这微不足道,但在按流量计费的环境下增加了费用,另一方面,KeepAlive 设置不合理时可能会
    因为短暂的网络波动而断开健康的 TCP连接。并且,默认的 KeepAlive超时需要 7,200,000 MilliSeconds,
    即 2 小时,探测次数为 5 次。对于很多服务端应用程序来说,2 小时的空闲时间太长。因此,我们需要手
    工开启 KeepAlive 功能并设置合理的 KeepAlive 参数。 
    以下代码给出在 Delphi 中使用 ServerSocket、ClientSocket 进行网络通信时,如何在服务端通过添加
    KeepAlive 功能来自动实现心跳机制。 
    //定义心跳常量 
    Const  
      IOC_IN = $80000000; 
      IOC_VENDOR = $18000000; 
      IOC_out = $40000000; 
      SIO_KEEPALIVE_VALS = IOC_IN or IOC_VENDOR or 4; 
      DATA_BUFSIZE = 8192; 
     
    //定义 KeepAlive 数据结构 
    Type 
      TTCP_KEEPALIVE = packed record 
        onoff: integer; 
        keepalivetime: integer; 
        keepaliveinterval: integer; 
      end; 
     
    //  开启 KeepAlive 保活机制,每隔 3 秒向客户端发送一次心跳包 
    procedure TForm1.ServerSocket1ClientConnect(Sender: TObject; Socket: TCustomWinSocket); 
    var 
      opt: integer; 
      klive, outKlive: TTCP_KEEPALIVE; 
    begin 
      opt := 1;  

    21 
        if setsockopt(Socket.SocketHandle,SOL_SOCKET, SO_KEEPALIVE, @opt, SizeOf(opt)) <> 0 then 
        begin 
          Showmessage('setsockopt KeepAlive Error!'); 
        end; 
        klive.onoff := 1;  
        klive.keepalivetime := 3000; 
        klive.keepaliveinterval := 1; 
    if WSAIoctl( Socket.SocketHandle, SIO_KEEPALIVE_VALS, @klive, 
                 SizeOf(TTCP_KEEPALIVE), @outKlive, 
                 SizeOf(TTCP_KEEPALIVE), @opt,0,nil) = SOCKET_ERROR then 
        begin 
          Showmessage('WSAIoctl KeepAlive Error!'); 
        end; 
    end; 
     
    其中,Windows Socket API函数 Setsockopt 用于设置套接口的选项,而此处则用来开启 KeepAlive功
    能,WSAIoctl 函数则用于设置 KeepAlive 超时及心跳包发送次数。 
    由于是在 ServerSocket 的 OnClientConnect 事件中使用 Socket 参数来设置每个连接上来的客户端的
    KeepAlive,所以上述代码同样支持多客户连接的情况。 
    在开启了 KeepAlive  后, 一旦客户端死机、 拔网线等“非优雅”退出, 就会触发服务端的 OnClientError
    事件,然后在此事件中完成必要的“善后”处理工作: 
    procedure TForm1.ServerSocket1ClientError(Sender: TObject;Socket: TCustomWinSocket; ErrorEvent: 
    TErrorEvent;  var ErrorCode: Integer); 
    begin 
      ErrorCode:=0; 
      Showmessage('客户端 '+Socket.RemoteAddress+'非正常退出,断开连接'); 
      …………        //“善后”处理工作  
      Socket.Close; 
    end; 
    4   结束语 
    实践证明,利用 TCP 本身支持的 KeepAlive 功能实现断线检测,比用户自己在应用层实现检测更方
    便有效,而且探测时带宽消耗很小。在没有数据传输时依赖 KeepAlive 确保断线检测,在传输数据时 TCP
    会通过超时判断是否断线。 
    在网络通信应用系统开发中,根据实际需要也可以在客户端开启 KeepAlive,对服务端的非正常断开
    进行及时有效地检测。 

  • 相关阅读:
    HandlerExceptionResolver 实现全局异常捕获
    Cocoapods安装过程
    mysql 安装
    崩溃日志的字段简单说明
    简述static关键字、void与void *(void指针)、函数指针
    在python后台如何将客户端提交的form表单数据提取出来?
    iOS如何提高页面流畅度
    屏幕显示机制
    计算机网络体系小知识
    Protobuf java版本安装步骤
  • 原文地址:https://www.cnblogs.com/Huayuan/p/2625639.html
Copyright © 2020-2023  润新知