处理服务器数据

Processing Server data

我有一个 Indy TCPServer 将一个设备与多个客户端连接起来。 当设备数据到达时,服务器将其发送给每个客户端。 当客户端数据到达时,它被发送到设备。 (并且设备会再次将其发送到服务器(回显))。 我每个周期只处理 2 个字节。 以上工作完美。

现在我想要 process/save 该数据。 只有当数据通过设备端口到达时,我才想 'Translate' 数据。

翻译后的数据也保存在另一个客户端数据集中。

    unit BusServer;
interface
uses
  Winapi.Windows, Winapi.Messages,

  System.SysUtils, System.Classes, System.Variants,

  Vcl.Graphics, Vcl.Controls, Vcl.SvcMgr, Vcl.Dialogs,

  IdContext, IdAntiFreezeBase, IdAntiFreeze, IdBaseComponent, IdComponent,
  IdCustomTCPServer, IdTCPServer,

  Data.DB, Datasnap.DBClient;

type
  TBus_Server = class(TService)
    tcpBusDataServer: TIdTCPServer;
    IdAntiFreeze1: TIdAntiFreeze;
    cdsBusMonitor: TClientDataSet;
    cdsBusMonitorNr: TIntegerField;
    cdsBusMonitorDate: TStringField;
    cdsBusMonitorTime: TStringField;
    cdsBusMonitorAad: TIntegerField;
    cdsBusMonitorAgr: TIntegerField;
    cdsBusMonitorAName: TStringField;
    cdsBusMonitorAFct: TStringField;
    cdsBusMonitorOrigin: TStringField;
    cdsIncoming: TClientDataSet;
    cdsMemberState: TClientDataSet;
    cdsMemberStateMemberID: TStringField;
    cdsMemberStateState: TStringField;
    cdsMemberStateDateTime: TDateTimeField;
    cdsMemberStateTotaal: TFloatField;
    procedure tcpBusDataServerExecute(AContext: TIdContext);
    procedure ServiceCreate(Sender: TObject);
  private
    { Private declarations }

    functionmon: String;
    DevicePort: Integer;
    ClientPort: Integer;
    ClientLSB, ClientMSB: Byte;
    DeviceLSB, DeviceMSB: Byte;
    FunctionList: TStringList;

    procedure TranslateData;
    function CodeValue: String;

  public
    function GetServiceController: TServiceController; override;
    { Public declarations }
  end;

var
  Bus_Server: TBus_Server;

implementation

  uses
    CodesiteLogging;

{$R *.dfm}

procedure ServiceController(CtrlCode: DWord); stdcall;
begin
  Bus_Server.Controller(CtrlCode);
end;

function TBus_Server.CodeValue: String;
begin
  if (cdsIncoming.FieldbyName('MemberType').AsInteger = 11) or
     (cdsIncoming.FieldbyName('MemberType').AsInteger = 22)or
     (cdsIncoming.FieldbyName('MemberType').AsInteger = 33)  then
  begin
    Result := FunctionMon
  end
  else
  begin
//    TODO:
  end
end;

function TBus_Server.GetServiceController: TServiceController;
begin
  Result := ServiceController;
end;

procedure TBus_Server.ServiceCreate(Sender: TObject);
var
  DataSetName: String;
begin
  DevicePort := 10001;
  ClientPort := 10012;

  tcpBusDataServer.Bindings.Clear;
  tcpBusDataServer.Bindings.Add.Port := DevicePort;
  tcpBusDataServer.Bindings.Add.Port := ClientPort;
  tcpBusDataServer.Active := True;

  FunctionList := TStringList.Create;
  FunctionList.Add('Null');
  FunctionList.Add('Reset');
  FunctionList.Add('Toggle');
  FunctionList.Add('Set');
  FunctionList.Add('Misc');
  FunctionList.Add('Status');
  FunctionList.Add('Timer/Direct');
  FunctionList.Add('Value');
  FunctionList.Add('Dimmer');
  FunctionList.Add('Readout');
  FunctionList.Add('Teller');
  FunctionList.Add('System');
  FunctionList.Add('Settings');
  FunctionList.Add('Select');
  FunctionList.Add('Data');
  FunctionList.Add('Program');

  ForceDirectories('c:\Test\');
  DataSetName := 'c:\Test\BusMonitor' + FormatDateTime('YYYY-MM-DD', Now) + '.xml';
  if cdsBusMonitor.Active then
    cdsBusMonitor.Close;
  cdsBusMonitor.Filename := DataSetName;
  if not System.SysUtils.FileExists(DataSetName) then
  begin
    cdsBusMonitor.CreateDataSet;
    cdsBusMonitor.SaveToFile
  end;
  DataSetName := 'c:\Test\MemberState' + FormatDateTime('YYYY-MM-DD', Now) + '.xml';
  if cdsMemberState.Active then
    cdsMemberState.Close;
  cdsMemberState.Filename := DataSetName;
  if not System.SysUtils.FileExists(DataSetName) then
  begin
    cdsMemberState.CreateDataSet;
    cdsMemberState.SaveToFile
  end;

end;

procedure TBus_Server.tcpBusDataServerExecute(AContext: TIdContext);
var
  Ctx: TIdContext;
  List: TList;
begin
  if AContext.Binding.Port = ClientPort then  // Client
  begin
    DeviceLSB := AContext.Connection.IOHandler.ReadByte;
    DeviceMSB := AContext.Connection.IOHandler.ReadByte;
    List := tcpBusDataServer.Contexts.LockList;
    try
      for var i := 0 to List.count - 1 do
      begin
        Ctx := TIdContext(List[I]);
        if (Ctx <> AContext) and (Ctx.Binding.Port = DevicePort) then
        begin
          Ctx.Connection.IOHandler.Write(DeviceLSB);
          Ctx.Connection.IOHandler.Write(DeviceMSB);
          // Since only 1 has to be written to
          Break
        end
      end
    finally
      tcpBusDataServer.Contexts.UnlockList
    end
  end
  else
  begin
    if AContext.Binding.Port = DevicePort then  // Device
    begin
      ClientLSB := AContext.Connection.IOHandler.ReadByte;
      ClientMSB := AContext.Connection.IOHandler.ReadByte;
      List := tcpBusDataServer.Contexts.LockList;
      try
        for var i := 0 to List.count - 1 do
        begin
          Ctx := TIdContext(List[I]);
          if (Ctx <> AContext) and (Ctx.Binding.Port = ClientPort) then
          begin
            Ctx.Connection.IOHandler.Write(ClientLSB);
            Ctx.Connection.IOHandler.Write(ClientMSB)
          end
        end
      finally
        TIdNotify.NotifyMethod(TranslateData);
        tcpBusDataServer.Contexts.UnlockList
      end
    end
  end
end;

procedure TBus_Server.TranslateData;
const {$J+}
  LastSave: TDateTime = 0;
type
  TProgramState = (psNone,psProgram,psIgnore1,psIgnore2);
const
  ProgramState: TProgramState = psNone;
const
  ValueMode: Boolean = False;
var
  i: Integer;
  fct: Integer;
  GroupMon: Integer;
  AddressMon: Integer;
  CorrecteSettings: Boolean;
  TmpStr: String;
begin
  fct := 0;
  // Functie uit MSB halen
  if ClientMSB >= 128 then
  begin
    ClientMSB := ClientMSB - 128;
    fct := 8
  end;
  if ClientMSB >= 64 then
  begin
    ClientMSB := ClientMSB - 64;
    fct := fct + 4
  end;
  if ClientMSB >= 32 then
  begin
    ClientMSB := ClientMSB - 32;
    fct := fct + 2
  end;
  if ClientMSB >= 16 then
  begin
    ClientMSB := ClientMSB - 16;
    fct := fct + 1
  end;
  // Variabelen voor monitor bepalen

  functionMon := FunctionList[fct];
  if cdsBusMonitor.Active then
  begin
    cdsBusMonitor.Filtered := False;
    cdsBusMonitor.Append;
    cdsBusMonitor.FieldByName('Nr').AsInteger := cdsBusMonitor.RecordCount + 1;
    cdsBusMonitor.FieldByName('AFct').Asstring := functionMon;
    cdsBusMonitor.FieldByName('Aad').AsInteger := ClientLSB;
    cdsBusMonitor.FieldByName('Agr').AsInteger := ClientMSB;
    cdsBusMonitor.FieldByName('Time').Asstring := TimeToStr(Now);
    cdsBusMonitor.FieldByName('Origin').AsString := 'Van de Bus: '{ + UserPeerIP};
    cdsBusMonitor.Post;
  end;

  if ProgramState = psNone then
  begin
    CodeSite.Send('New situation...');
    try
      if cdsIncoming.Locate('Group;Address', VarArrayOf([IntToStr(DeviceMsb),IntToStr(DeviceLsb)]), []) then
      begin
        CodeSite.Send('After locate...');
        if cdsMemberState.Locate('MemberID', cdsIncoming.FieldByName('MemberID').AsString, []) then
          cdsMemberState.Edit
        else
          cdsMemberState.Append;

        if cdsMemberStateState.AsString = Codevalue then
        begin
          CodeSite.Send('New state ' + Codevalue + ' already known');
          cdsMemberState.Cancel
        end
        else
        begin
          CodeSite.Send('New state ' + Codevalue);
          cdsMemberStateState.AsString := Codevalue;
          if Codevalue.ToLower = 'reset' then
            cdsMemberStateTotaal.AsFloat := cdsMemberStateTotaal.AsFloat + (Now - cdsMemberStateDateTime.AsDateTime);
          cdsMemberStateDateTime.AsDateTime := Now;
          cdsMemberState.Post
        end
      end
      else
        CodeSite.SendError('ServerMethodsBServer.cdsIncoming Locate Fail');
    except
      on E: Exception do
        CodeSite.SendException(E);
    end
  end;

  if ((cdsBusMonitor.RecordCount mod 100) = 0) or ((Now - LastSave) > (1/24/60)) then
  begin
    LastSave := Now;
    cdsBusMonitor.MergeChangeLog;
    cdsBusMonitor.SaveToFile;
    cdsMemberState.MergeChangeLog;
    cdsMemberState.SaveToFile
  end
end;

在clientdataset中,偶尔会看到"wrong data",意思是: 首先我有一个正确的记录,然后是一个 lsb 相同但 msb 错误的记录。 我将程序 (Translatedata) 中的 msb 拆分为 'high nibble' 和 'low nibble'.

所以现在我想找出它的来源。

正如您在我的代码中看到的那样,我通过 TIdNotify.NotifyMethod(TranslateData);

调用该过程

有人知道哪里出了问题吗?

你的代码不是很thread-safe。如果多个客户端同时向服务器发送数据,那么您并没有保护您的数据值免受跨线程边界的并发访问。

此外,您不应在 OnCreate 事件中进行所有服务初始化,而应使用 OnStart 事件。 OnCreate 事件会在您的 TService 对象因任何原因创建时触发,这不仅包括 运行 服务,还包括(卸载)安装服务。

试试像这样的东西:

unit BusServer;

interface

uses
  Winapi.Windows, Winapi.Messages,
  System.SysUtils, System.Classes, System.Variants,
  Vcl.Graphics, Vcl.Controls, Vcl.SvcMgr, Vcl.Dialogs,
  IdContext, IdBaseComponent, IdComponent,
  IdCustomTCPServer, IdTCPServer, IdThreadSafe,
  Data.DB, Datasnap.DBClient, System.SyncObjs;

type
  TBus_Server = class(TService)
    tcpBusDataServer: TIdTCPServer;
    cdsBusMonitor: TClientDataSet;
    cdsBusMonitorNr: TIntegerField;
    cdsBusMonitorDate: TStringField;
    cdsBusMonitorTime: TStringField;
    cdsBusMonitorAad: TIntegerField;
    cdsBusMonitorAgr: TIntegerField;
    cdsBusMonitorAName: TStringField;
    cdsBusMonitorAFct: TStringField;
    cdsBusMonitorOrigin: TStringField;
    cdsIncoming: TClientDataSet;
    cdsMemberState: TClientDataSet;
    cdsMemberStateMemberID: TStringField;
    cdsMemberStateState: TStringField;
    cdsMemberStateDateTime: TDateTimeField;
    cdsMemberStateTotaal: TFloatField;
    procedure tcpBusDataServerExecute(AContext: TIdContext);
    procedure ServiceStart(Sender: TObject; var Started: Boolean);
    procedure ServiceStop(Sender: TObject; var Stopped: Boolean);
    procedure ServiceShutdown(Sender: TObject);
  private
    { Private declarations }

    FunctionMon: TIdThreadSafeString;
    DevicePort: Integer;
    ClientPort: Integer;
    DeviceLSB, DeviceMSB: Byte;
    DeviceDataLock: TCriticalSection;
    FunctionList: TStringList;

    procedure TranslateData(ClientLSB, ClientMSB: Byte);
    function CodeValue: String;

  public
    function GetServiceController: TServiceController; override;
    { Public declarations }
  end;

var
  Bus_Server: TBus_Server;

implementation

uses
  CodesiteLogging;

{$R *.dfm}

procedure ServiceController(CtrlCode: DWord); stdcall;
begin
  Bus_Server.Controller(CtrlCode);
end;

function TBus_Server.CodeValue: String;
begin
  case cdsIncoming.FieldByName('MemberType').AsInteger of
    11, 22, 33: begin
      Result := FunctionMon.Value;
    end;
  else
    // TODO
    Result := '';
  end;
end;

function TBus_Server.GetServiceController: TServiceController;
begin
  Result := ServiceController;
end;

procedure TBus_Server.ServiceStart(Sender: TObject; var Started: Boolean);
var
  DataSetName: String;
begin
  DevicePort := 10001;
  ClientPort := 10012;

  DeviceDataLock := TCriticalSection.Create;
  FunctionMon := TIdThreadSafeString.Create;

  FunctionList := TStringList.Create;
  FunctionList.Add('Null');
  FunctionList.Add('Reset');
  FunctionList.Add('Toggle');
  FunctionList.Add('Set');
  FunctionList.Add('Misc');
  FunctionList.Add('Status');
  FunctionList.Add('Timer/Direct');
  FunctionList.Add('Value');
  FunctionList.Add('Dimmer');
  FunctionList.Add('Readout');
  FunctionList.Add('Teller');
  FunctionList.Add('System');
  FunctionList.Add('Settings');
  FunctionList.Add('Select');
  FunctionList.Add('Data');
  FunctionList.Add('Program');

  ForceDirectories('c:\Test\');

  DataSetName := 'c:\Test\BusMonitor' + FormatDateTime('YYYY-MM-DD', Now) + '.xml';
  if cdsBusMonitor.Active then
    cdsBusMonitor.Close;
  cdsBusMonitor.Filename := DataSetName;
  if not System.SysUtils.FileExists(DataSetName) then
  begin
    cdsBusMonitor.CreateDataSet;
    cdsBusMonitor.SaveToFile;
  end;

  DataSetName := 'c:\Test\MemberState' + FormatDateTime('YYYY-MM-DD', Now) + '.xml';
  if cdsMemberState.Active then
    cdsMemberState.Close;
  cdsMemberState.Filename := DataSetName;
  if not System.SysUtils.FileExists(DataSetName) then
  begin
    cdsMemberState.CreateDataSet;
    cdsMemberState.SaveToFile;
  end;

  tcpBusDataServer.Bindings.Clear;
  tcpBusDataServer.Bindings.Add.Port := DevicePort;
  tcpBusDataServer.Bindings.Add.Port := ClientPort;
  tcpBusDataServer.Active := True;

  Started := True;
end;

procedure TBus_Server.ServiceStop(Sender: TObject; var Stopped: Boolean);
begin
  ServiceShutdown(Sender);
  Stopped := True;
end;

procedure TBus_Server.ServiceShutdown(Sender: TObject);
begin
  tcpBusDataServer.Active := False;

  cdsBusMonitor.Close;
  cdsMemberState.Close;

  DeviceDataLock.Free;
  FunctionMon.Free;
  FunctionList.Free;
end;

procedure TBus_Server.tcpBusDataServerExecute(AContext: TIdContext);
var
  LSB, MSB: Byte;
  List: TList;
  Ctx: TIdContext;
begin
  LSB := AContext.Connection.IOHandler.ReadByte;
  MSB := AContext.Connection.IOHandler.ReadByte;

  if AContext.Binding.Port = ClientPort then  // Client
  begin
    DeviceDataLock.Enter;
    try
      DeviceLSB := LSB;
      DeviceMSB := MSB;
    finally
      DeviceDataLock.Leave;
    end;
    List := tcpBusDataServer.Contexts.LockList;
    try
      for var i := 0 to List.count - 1 do
      begin
        Ctx := TIdContext(List[I]);
        if (Ctx <> AContext) and (Ctx.Binding.Port = DevicePort) then
        begin
          Ctx.Connection.IOHandler.Write(LSB);
          Ctx.Connection.IOHandler.Write(MSB);
          // Since only 1 has to be written to
          Break;
        end;
      end;
    finally
      tcpBusDataServer.Contexts.UnlockList;
    end;
  end
  else if AContext.Binding.Port = DevicePort then  // Device
  begin
    List := tcpBusDataServer.Contexts.LockList;
    try
      for var i := 0 to List.count - 1 do
      begin
        Ctx := TIdContext(List[I]);
        if (Ctx <> AContext) and (Ctx.Binding.Port = ClientPort) then
        begin
          Ctx.Connection.IOHandler.Write(LSB);
          Ctx.Connection.IOHandler.Write(MSB)
        end;
      end
    finally
      tcpBusDataServer.Contexts.UnlockList;
      TThread.Queue(nil,
        procedure
        begin
          TranslateData(LSB, MSB);
        end
      );
    end;
  end;
end;

procedure TBus_Server.TranslateData(ClientLSB, ClientMSB: Byte);
const {$J+}
  LastSave: TDateTime = 0;
type
  TProgramState = (psNone,psProgram,psIgnore1,psIgnore2);
const
  ProgramState: TProgramState = psNone;
const
  ValueMode: Boolean = False;
var
  i: Integer;
  fct: Integer;
  GroupMon: Integer;
  AddressMon: Integer;
  CorrecteSettings: Boolean;
  TmpFunc, TmpCodeValue: string;
  TmpDeviceLSB, TmpDeviceMSB: Byte;
begin
  fct := 0;
  // Functie uit MSB halen
  if ClientMSB >= 128 then
  begin
    Dec(ClientMSB, 128);
    fct := 8;
  end;
  if ClientMSB >= 64 then
  begin
    Dec(ClientMSB, 64);
    Inc(fct, 4);
  end;
  if ClientMSB >= 32 then
  begin
    Dec(ClientMSB, 32);
    Inc(fct, 2);
  end;
  if ClientMSB >= 16 then
  begin
    Dec(ClientMSB, 16);
    Inc(fct, 1);
  end;
  // Variabelen voor monitor bepalen

  TmpFunc := FunctionList[fct];
  FunctionMon.Value := TmpFunc;

  if cdsBusMonitor.Active then
  begin
    cdsBusMonitor.Filtered := False;
    cdsBusMonitor.Append;
    try
      cdsBusMonitor.FieldByName('Nr').AsInteger := cdsBusMonitor.RecordCount + 1;
      cdsBusMonitor.FieldByName('AFct').Asstring := TmpFunc;
      cdsBusMonitor.FieldByName('Aad').AsInteger := ClientLSB;
      cdsBusMonitor.FieldByName('Agr').AsInteger := ClientMSB;
      cdsBusMonitor.FieldByName('Time').AsString := TimeToStr(Now);
      cdsBusMonitor.FieldByName('Origin').AsString := 'Van de Bus: '{ + UserPeerIP};
      cdsBusMonitor.Post;
    except
      cdsBusMonitor.Cancel;
      raise;
    end;
  end;

  if ProgramState = psNone then
  begin
    CodeSite.Send('New situation...');
    try
      DeviceDataLock.Enter;
      try
        TmpDeviceLSB := DeviceLSB;
        TmpDeviceMSB := DeviceMSB;
      finally
        DeviceDataLock.Leave;
      end;
      if cdsIncoming.Locate('Group;Address', VarArrayOf([IntToStr(TmpDeviceMSB),IntToStr(TmpDeviceLSB)]), []) then
      begin
        CodeSite.Send('After locate...');
        if cdsMemberState.Locate('MemberID', cdsIncoming.FieldByName('MemberID').AsString, []) then
          cdsMemberState.Edit
        else
          cdsMemberState.Append;
        try
          TmpCodeValue := CodeValue;
          if cdsMemberStateState.AsString = TmpCodeValue then
          begin
            CodeSite.Send('New state ' + TmpCodeValue + ' already known');
            cdsMemberState.Cancel;
          end
          else
          begin
            CodeSite.Send('New state ' + TmpCodeValue);
            cdsMemberStateState.AsString := TmpCodeValue;
            if TmpCodeValue = 'Reset' then
              cdsMemberStateTotaal.AsFloat := cdsMemberStateTotaal.AsFloat + (Now - cdsMemberStateDateTime.AsDateTime);
            cdsMemberStateDateTime.AsDateTime := Now;
            cdsMemberState.Post;
          end;
        except
          cdsMemberState.Cancel;
          raise;
        end;
      end
      else
        CodeSite.SendError('ServerMethodsBServer.cdsIncoming Locate Fail');
    except
      on E: Exception do
        CodeSite.SendException(E);
    end
  end;

  if ((cdsBusMonitor.RecordCount mod 100) = 0) or ((Now - LastSave) > (1/24/60)) then
  begin
    LastSave := Now;
    cdsBusMonitor.MergeChangeLog;
    cdsBusMonitor.SaveToFile;
    cdsMemberState.MergeChangeLog;
    cdsMemberState.SaveToFile;
  end;
end;