2013-08-17 42 views
2

我有一臺服務器每隔0.1秒發送一次狀態。我使用此代碼訪問UI並在客戶端程序中顯示結果。使用TIdNotify.NotifyMethod時丟失了一些數據包

procedure TModules.TCPServerExecute(AContext: TIdContext); 
begin 
    Buffer:= AContext.Connection.IOHandler.ReadLn(); 
    TIdNotify.NotifyMethod(ServerExecute); 
end; 

但是有些數據包沒有被ServerExecute收到。我更改了代碼並使用了TIdSync.SynchronizeMethod。該問題解決了:

procedure TModules.TCPServerExecute(AContext: TIdContext); 
begin 
    Buffer:= AContext.Connection.IOHandler.ReadLn(); 
    TIdSync.SynchronizeMethod(ServerExecute); 
end; 

我已閱讀本網站TIdSync.SynchronizeMethod可能會產生死鎖。 所以我想知道TIdNotify.NotifyMethod有什麼問題。另外我看到一個答案,建議使用Timer來同步UI並且不使用Notify()和Synchronize()方法。

另外我不得不提的是,該程序在ServerExecute內部完成了其餘的工作。

正如您所看到的,我將代碼縮小到以下範圍。我必須指出,我與確切下面的代碼執行的程序,它已經擁有的問題,並沒有得到想要的結果:

procedure TModules.ServerExecute; 
var 
    I: Word; 
    tmp, Packet, Cmd:string; 
    CheckSum: Boolean; 
begin 
    //try 
    frmDiag.DataLog.Lines.Add(Buffer); 

    {ExplodeStr(Buffer, Cmd, Packet); 

    if Cmd='status' then 
    begin 

     //ExplodeStr(Packet, Cmd, Packet); 

     if trim(Packet)='' then 
     raise Exception.Create('Empty packet received.'); 

     //Prepare for log 
     {tmp:=''; 
     for I := 1 to Length(Packet) do 
     begin 
     if (Ord(Packet[I])>=48) and (Ord(Packet[I])<=122) then 
      tmp:=tmp+Packet[I]+'' 
     else 
      tmp:=tmp+IntToStr(Ord(Packet[I]))+''; 
     end; 

     //frmITCAMain.Memo1.Lines.Add(Packet); 
     CheckSum:=ParsePackets(Packet); 
     IntersectionsList.Int.CallNotifier;  //Call the notifier to execute assigned proc 

     if frmLoader.Visible=true then 
     with frmLoader do 
     begin 
      if 
      //(Packet[1]='X') or   //Server responce 
      (Packet[1]<>'l') and (Packet[1]<>'s') and (Packet[1]<>'e')and //ignore general packet 
      (
       (Req[1]<>'f')      //Only receive ACK 
       or 
       ((Req[1]='f')and((Req[2]='m')or(Req[2]='a')or(Req[2]='b'))) 
       or 
       (      //Receive Data+ACK 
        (Req[1]='f')and((Req[2]='g')or(Req[2]='h')or(Req[2]='p')or(Req[2]='j')) 
        and 
        (Packet[1]<>'k')  //Ignore ACK 
       ) 
      ) 
      then 
      begin 
       if CheckSum then 
       begin 
       Res:= Packet; 
       Confirm; 
       end 
       else 
       begin 
       if Packet='n' then //Checksum failure 
        Fail 
       else 
        SendPacket; //Resend. 
       //lblError.Visible:=true; 
       end; 
      end; 
     end; 

     if (Packet[1]='g') or (Packet[1]='h') or (Packet[1]='p') or 
     (Packet[1]='j') or (Packet[1]='k') then 
      frmIntDetails.memReceived.Lines.Text:=tmp; 

    end 

    else if Cmd='server' then 
    begin 
     with frmLoader do 
     begin 
      if Visible=false then exit; 

     Res:= Packet; 
     if Copy(Res, 1, 2)='ok' then 
      Confirm 
     else 
      Cancel; 
     end; 
    end 

    else 
     ClientLog.Add(Buffer); 

    except on E: Exception do 
    begin 
     if E.Message='Connection Closed Gracefully.' then 
     ClientLog.Add('X:Connection closed(Gracefully)') 
     else 
     ClientLog.Add(E.Message+' Buffer="'+Buffer+'"'); 
    end; 
    end; 
    //Memo2.Lines.Add(FloatToStr(TimeStampToMSecs(DateTimeToTimeStamp(Now)))); 
    } 
end; 

frmDiag.DataLogTMemo組件。

frmDiag.DataLog例如下面的列表是,我預計(以下字符串從數據記錄組件提取與TIdSync.SynchronizeMethod液)結果:

status:l77770000140000 
status:eFFFF20000140 
status:s0000 
status:s0000 
status:s0000 
status:s0000 
status:l00005555140000 
status:eFFFF20000140 
status:s0000 
status:s0000 
status:s0000 
status:s0000 
status:l77770000140000 
status:eFFFF20000140 

而是我得到這樣的結果:

status:eFFFF20000140 
status:eFFFF20000140  
status:s0000 
status:s0000 
status:s0000 
status:s0000 
status:s0000 
status:s0000 
status:s0000 
status:s0000 
status:s0000 
status:l00005555140000 
status:l77770000140000 

正如你所看到的訂單不符合。

我必須得到一個status:l77770000140000像數據包然後status:eFFFF20000140然後4 status:s0000等等。

@Remy我已經改變了你的代碼一點點:

TMyNotify = class(TIdNotify) 
protected 
    FBuffer: String; 
    FMethod: TThreadMethod; 
    procedure DoNotify; override; 
public 
    constructor Create(const ABuffer: String; AMethod: TThreadMethod); 
end; 

///...... 

{ TMyNotify } 

constructor TMyNotify.Create(const ABuffer: String; AMethod: TThreadMethod); 
begin 
    inherited Create; 
    FBuffer := ABuffer; 
    FMethod := AMethod; 
end; 

procedure TMyNotify.DoNotify; 
begin 
    inherited; 
    FMethod; 
    //Destroy; 
end; 

這是我現在該怎麼叫ServerExcecute:

procedure TModules.TCPServerExecute(AContext: TIdContext); 
begin 
    Buffer:= AContext.Connection.IOHandler.ReadLn(); 

    TMyNotify.Create(Buffer, ServerExecute).Notify; 

// ServerExecute; 
// TIdNotify.NotifyMethod(ServerExecute); 
// TIdSync.SynchronizeMethod(ServerExecute); 
end; 

回答

4

TIdNotify是異步的。如果您在上一行有機會處理之前收到一條新行並覆蓋您的變量Buffer,則上一行將丟失。更改您的代碼傳遞TIdNotify本身內部的線值,例如:

type 
    TMyDataProc = procedure(const ABuffer: String) of object; 

    TMyNotify = class(TIdNotify) 
    protected 
    fBuffer: String; 
    fProc: TMyDataProc; 
    procedure DoNotify; override; 
    public 
    constructor Create(const ABuffer: String; AProc: TMyDataProc); 
    end; 

constructor TMyNotify.Create(const ABuffer: String; AProc: TMyDataProc); 
begin 
    inherited Create; 
    fBuffer := ABuffer; 
    fProc := AProc; 
end; 

procedure TMyNotify.DoNotify; 
begin 
    fProc(fBuffer); 
end; 

procedure TModules.TCPServerExecute(AContext: TIdContext); 
var 
    LBuffer: String; 
begin 
    LBuffer := AContext.Connection.IOHandler.ReadLn(); 
    TMyNotify.Create(LBuffer, ServerExecute).Notify(); 
end; 

procedure TModules.ServerExecute(const ABuffer: String); 
begin 
    // use ABuffer as needed... 
end; 
+0

沒有工作。我也有丟包問題和'TIdSync.SynchronizeMethod'解決了這個問題。 – SAMPro

+0

然後你沒有正確處理數據。我向你保證,我描述的技術在正確使用時工作得很好。我用過很多次。最終,'TIdNotify'和'TIdSync'通過RTL中相同的'TThread'序列化隊列,他們只是以不同的方式到達那裏。如果您遇到了我描述的方法問題,請使用您實際使用的最新代碼更新您的問題。您使用TIdSync並不安全,因爲您並未保護您的「緩衝區」變量不受併發訪問的影響,但仍會導致數據丟失。 –

+0

感謝您的信息。這是實際的'TCPServerExecute'代碼。該程序執行'MyServerExecute'內的其餘工作。在那個函數裏面有字符串解析器,然後程序做一些與UI相關的工作來顯示結果。 – SAMPro