注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

海宏软件的个人主页

用机器解放人的双手

 
 
 

日志

 
 

我解决TCP-socket -掉线问题的总结  

2007-11-29 16:34:16|  分类: 默认分类 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

直接用socket做的服务器和客户端,程序做的差不多了,实际使用时,发现经常“掉线”,而程序不知道,找了许多解决办法,最后全都用上了,总结一下。

1:在连接时,设置他的avalie保活定时器.

Type
    TCP_KeepAlive = record
        OnOff: Cardinal;
        KeepAliveTime: Cardinal;
        KeepAliveInterval: Cardinal;
    end;

procedure TFrm_Client.Sck_MainConnect(Sender: TObject;
  Socket: TCustomWinSocket);
var val:TCP_KeepAlive;
    Ret:DWord;
begin
    inherited;
    try
        val.OnOff:=1;
        val.KeepAliveTime:=1000 * 60 * 60 * 24;   //24小时
        val.KeepAliveInterval:=100;
        Ret:=WSAIoctl(socket.SocketHandle, IOC_IN or IOC_VENDOR or 4, @val, SizeOf(Val), nil, 0, @Ret, nil, nil)
    finally
        showInfo('已经连接到服务器.TCP_KeepAlive定时器'+intToStr(val.KeepAliveTime)+'结果:'+intToStr(Ret));
    end;
end;

含义一目了然,不再多说。

2:在出错中检查10053之类错误,遇到就直接close,主动触发关闭事件,在关闭事件中触发自动重新连接的timer的自动重连:

procedure TFrm_Client.Sck_MainError(Sender: TObject;
  Socket: TCustomWinSocket; ErrorEvent: TErrorEvent;
  var ErrorCode: Integer);
Var nCode:Cardinal;
begin
    inherited;
    showInfo('||[Error]出错:'+intToStr(ErrorCode)+';自动重连:'+boolToStr(lAskForReConnect, true));
    nCode:=ErrorCode;               //GetLastError;
    ErrorCode:=0;
    //如果出错,关闭,触发断开事件
    if (nCode=EConnAborted)         //10053   EConnAborted  WSAEConnAborted
      or (nCode=ENetDown)           //10050   ENetDown
      or (nCode=ETimedOut)          //10060
      or (nCode=EConnRefused)       //10061
      or (nCode=EHostUnReach)       //10065
    then begin
        if lAskForReConnect and (not lInReConnecting) then Timer_ReConnect.enabled:=true;  //ReConnectServer;
        if not lInReConnecting then begin
            sck_Main.active:=false;
            sck_Main.close; //ReConnectServer   and (not sck_Main.active)
        end;
        showInfo('  连接状态:'+boolToStr(sck_Main.active,true)+';重连设置:'+boolToStr(lAskForReConnect,true)+';重连状态:'+boolToStr(timer_ReConnect.enabled,true));
    end;
end;
//断开连接,断开时自动尝试再次连接,连接不上才断开
procedure TFrm_Client.Sck_MainDisconnect(Sender: TObject; Socket: TCustomWinSocket);
begin
    inherited;
    if (not lAskForReConnect) or (lInReConnecting) then exit;
    Timer_ReConnect.enabled:=true;  //ReConnectServer;
end;
//重新连接
Function TFrm_Client.ReConnectServer:Boolean;
const nMax=6;
var Qry:TAdoQuery;    L,L2:Boolean;     i:Integer;        s:String;
begin
    Result:=False;    i:=1;             if not lAskForReConnect then exit;
    L:=frm_MDI.ASysConfig.lAutoLogon;
    qry:=nil;
    with sck_Main do
    try
        if not lInCreatting then    //or (not lLoginOK)
        try
            if not lAskForReConnect then exit;
            sck_Main.Socket.Disconnect(sck_Main.Socket.Handle);
            sck_Main.Close;
            if L then begin
                showInfo('断线,开始尝试自动重新连接服务器,最大重试次数['+intToStr(nMax)+']...');
                i:=1;           //尝试3次重新连接
                while (not sck_Main.active) and (i<=nMax) do
                try
                    if not lAskForReConnect then break;
                    showInfo('  *尝试自动重新连接['+intToStr(i)+'/'+intToStr(nMax)+']...');
                    sck_Main.Open;
                    delay(2000);
                    L2:= sck_Main.active;         //connectToServer(sck_Main, txt_svrIP.text, strToIntDef(txt_SvrPort.text,65180));
                    if not L2 then delay(1000);   //sck_Main.active
                    inc(i);
                except
                    on x:Exception do ;
                end;
            end;
            //
        except
            on x:exception do ;
        end;
    finally
        lLoginOK:=sck_Main.Active;
        if not lLoginOK then begin
            showInfo('已经断开服务器的连接,正在尝试重新连接');
            lbl_Move.caption:='已经断开连接,正在尝试重新连接';
        end;
        Result:=lLoginOK;
        s:='失败';
        if lLoginOK then
        Try
            s:='[第'+intToStr(i)+'次重试成功]';
            qry:=tAdoQuery.create(self);
            qry.connection:=dmain.conn_Main;
            clsAskForNewMessages(sck_Main.Socket, qry);
        finally
            if assigned(qry) then qry.free;
        End;
        if L then showInfo('断线重新连接结果:'+s);
    end;
End;

procedure TFrm_Client.Timer_ReconnectTimer(Sender: TObject);
var msg:_msgIdle;
begin
    inherited;
    Try
        //已连接了或用户不要求自动重连,则退出
        showInfo('[##Timer_ReconnectTimer]断线自动重连。连接状态:'+boolToStr(sck_Main.active,true)+';重连设置:'+boolToStr(lAskForReConnect,true)+';重连状态:'+boolToStr(timer_ReConnect.enabled,true));
        if (sck_Main.active) or (not lAskForReConnect) then exit;
        //
        lInReConnecting:=True;            //处于连接中
        timer_ReConnect.enabled:=False;
        timer_ReConnect.enabled:=(not ReConnectServer) and lAskForReConnect;   //连上就停止
        //
    finally
        lInReConnecting:=False;
    end;
end;

3:服务器上。信息发送都用一个sendMessage方法,在他里边给socket加上错误处理,也时遇到10053错误直接就直接认为已经断开了。

//发送聊天信息
function sendMessage(socket:TCustomWinSocket; nType:Integer; pMessage:Pointer):boolean;
const sFile='d:\aa.txt';
var tp:_msgHead;      buf, pMsg, p:pByte;         f,m,hBuf:THandle;
    pOldErrorEvent:TSocketErrorEvent;
    s:String;         n,i,nLen,nMsgLen:integer;
begin
    result:=false;    buf:=nil;       m:=0;
    if lGlobalTerminate then exit;
    //
    try
      pOldErrorEvent:=nil;
      if assigned(pSocketErrProcedure) then begin
          pOldErrorEvent:=socket.OnErrorEvent;
          socket.OnErrorEvent:=pSocketErrProcedure;
      end;
      try
        if not socket.Connected then raise exception.Create('连接未打开或不存在');
        //
        tp.msgType:=nType;
        nMsgLen:=getMessageTypeLen(nType);
        if nMsgLen<0 then exit;
        tp.nBagSize:=sizeof(tp)+nMsgLen;
        tp.Version.dwMajorVersion:=frm_MDI.Version.dwMajorVersion;  //1
        tp.Version.dwMinorVersion:=frm_MDI.Version.dwMinorVersion;  //0
        //
        nLen:=nMsgLen+sizeOf(tp)+0;
        //分配内存区、复制头  globalAlloc(gmem_MoveAble, n+sizeOf(tp));
        hBuf:=globalAlloc(GMEM_MOVEABLE + GMem_Share, nLen);
        buf:=globalLock(hBuf);                //getMem(buf, nLen);
        fillChar( buf^, nLen, #0);
        copyMemory(buf, @tp, sizeOf(tp));     //strCopy(buf, @tp);
        //复制内容到全部内存区
        if (nMsgLen<>0) and (pMessage<>nil) then begin
         n:=integer(buf)+sizeOf(tp);
         p:=ptr(n);
         copyMemory(p, pMessage, nMsgLen);   //strLCopy(p, pMessage, nMsgLen);          //copyMemory(p, pMessage, nMsgLen);
        end;
        //发送,重试n次,最多150ms
        n:=-1;        i:=1;
        while (n=-1) and (i<=5) do begin
            if lGlobalTerminate or (not socket.Connected) then break;
            n:=socket.SendBuf(buf^, nLen);
            if n=-1 then delay(10*i);     //socket没有空间,则等待后重发
            inc(i);
        end;
        //
        result:=lGlobalTerminate or (n>-1);
      except
        on x:exception do begin
            s:='[sendMessage]发送信息出错!Type='+intToStr(nType)+#13+#10+'  '+x.Message;
            //if assigned(buf) then freeMem(buf);
            insShowInfo(s);
            raise eSocketError.Create(s);
        end;
      end;
    finally
      if assigned(pOldErrorEvent) then socket.OnErrorEvent:=pOldErrorEvent;
      if assigned(buf) then begin
          globalUnlock(hBuf);
          globalFree(hBuf);
      end;
    end;
end;
//服务器端初始化时:

pSocketErrProcedure:=defaultSocketErrProcedure;

//socket出错的处理
procedure TFrm_Service.defaultSocketErrProcedure(Sender: TObject; Socket: TCustomWinSocket;
        ErrorEvent: TErrorEvent; var ErrorCode: Integer);
var nCode:Integer;
begin
    inherited;            nCode:=errorCode;
    //若出错,关闭
    if nCode=10053 then begin
        socket.Close;
        raise exception.create('客户端已经断开连接了!');
    end;
end;

4:心跳包,可能这个最有效,心跳包的意思,就是像心跳一样,每隔多长事件跳一次,跳自然会触发错误了。

//保持连接,心跳包
procedure TFrm_Client.Timer_KeepConnectTimer(Sender: TObject);
var msg:_msgIdle;     H:TSocket;      n:Integer;      L:Boolean;
begin
    inherited;
    if (not lLoginOK) or (lInReConnecting) then exit;
    L:=False;
    try
        if lGlobalTerminate then exit;
        Timer_KeepConnect.enabled:=false;
        //
        //fillChar(msg.sComment,length(msg.sComment), #0);
        msg.Version.dwMajorVersion:=frm_MDI.Version.dwMajorVersion;
        msg.Version.dwMinorVersion:=frm_MDI.Version.dwMinorVersion;
        msg.nTickCount:=0;  //GetTickCount;
        msg.nHandle:=0;     //Socket.Handle;
        msg.sComment:='你好!有空常联系!';   //sComment;
        if not sendMessage(sck_Main.Socket, msgTypeIdle, @msg) then ;
        //
        //sendIdleMsg(sck_Main.socket);
        {n:=timer_KeepConnect.interval;
        H:=sck_Main.Socket.SocketHandle;
        waitForData(n-300);   }
        L:=True;
    finally
        Timer_KeepConnect.enabled:=true;
        showInfo('发送联系服务器消息IDLE'+intToStr(Timer_KeepConnect.interval)+',结果:'+boolToStr(L,true)+'...');
    end;
end;

  评论这张
 
阅读(2153)| 评论(0)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017