Entwickler-Ecke

Internet / Netzwerk - Indy - Record mit dynamischen Array senden


LittleBen - Di 24.12.13 14:58
Titel: Indy - Record mit dynamischen Array senden
Halli hallo und frohe Weihnachten!
Während dem Warten auf das Christkind habe ich mich mal mit dem Versenden eines Records, welches ein dynamisches Array beinhaltet, auseinanderngesetzt.

Das Record sieht so aus:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
type
  TEvent = record
   Name: string[255];
   {...}
  end;

  TDay = record
   Events: array of TEvent;
  end;

  TWeek = record
   Days: array[0..4of TDay;
  end;


Der Server reagiert folgendermaßen auf eine Anfrage des Clients:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
procedure TServerEventHandler.OnServerExecute(AThread: TIdPeerThread);
var cCmd, cIP: String;
    Week: TWeek;
    Len: LongInt;
    Stream: TStream;
begin
 try
  cCmd:= Trim(AThread.Connection.ReadLn);
  cIP:= AThread.Connection.Socket.Binding.IP;

  WriteLn('['+DateTimeToStr(now)+', '+cIP+']'+' '+cCmd);

  SetLength(Week.Days[0].Events,High(Week.Days[0].Events)+2);
  Week.Days[0].Events[High(Week.Days[0].Events)].Name:= 'Weihnachten';

  Stream:= TMemoryStream.Create;
  try
   Len:=SizeOf(TWeek);
   Stream.Write(Len, SizeOf(Len));
   Stream.Write(Week, Len);
   Stream.Seek(0, soFromBeginning);
   AThread.Connection.WriteStream(Stream,true,true,len);
  finally
   Stream.Free;
  end;

 finally
  AThread.Connection.Disconnect;
 end;
end;


Der Client holt sich die Daten so:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
var Week: TWeek;
    Stream: TStream;
    Len: LongInt;
    n1,n2: integer;
begin
 Stream:=TMemoryStream.Create;
 try
  IdTCPClient1.WriteLn('Gebe mir die aktuelle Woche');
  IdTCPClient1.ReadStream(Stream);

  Stream.Position:=0;
  Stream.Read(Len, SizeOf(Len));
  Stream.Read(Week, Len);

  for n1:= 0 to High(Week.Days) do
   for n2:= 0 to High(Week.Days[n1].Events) do
     Memo1.Lines.Add(Week.Days[n1].Events[n2].Name);
 finally
  Stream.Free;
 end;


Beim empfangen der Antwort gibt es eine Zugriffsverletzung.
Kann mir jemand erklären, wie ich vorgehen muss?

Viele Grüße und eine schöne Weihnachtszeit


Mr_Emre_D - Di 24.12.13 17:42

Edit: --


jaenicke - Di 24.12.13 23:22

Und dann kann man auch gleich einfach Klassen verwenden mit eigenen LoadFromStream und SaveToStream Methoden und ist die Records gleich los. ;-)


LittleBen - Mi 25.12.13 00:53

user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
Und dann kann man auch gleich einfach Klassen verwenden mit eigenen LoadFromStream und SaveToStream Methoden und ist die Records gleich los. ;-)
Da ich immer noch mit Delphi 7 arbeite, bleibt mir auch nicht anderes übrig, als mit Klassen zuarbeiten ;)

Habs einfach mal so versucht:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
type
  TEvent = class
  private
   Name: string[255];
  public
   procedure read(Stream: TStream);
   procedure write(Stream: TStream);
  end;

  TDay = class
  private
   Events: array of TEvent;
  public
   procedure read(Stream: TStream);
   procedure write(Stream: TStream);
  end;

  TWeek = class
  private
   Days: array[0..4of TDay;
  public
   procedure read(Stream: TStream);
   procedure write(Stream: TStream);
  end;

{ TEvent }

procedure TEvent.write(Stream: TStream);
var
  len: Integer;
begin
  len := Length(Name);
  Stream.Write(len, SizeOf(len));
  if len > 0 then
    Stream.Write(Name[1], len * SizeOf(Name[1]));
end;

procedure TEvent.read(Stream: TStream);
var
  len: Integer;
begin
  Stream.Read(len, SizeOf(len));
  if len > 0 then
  begin
    SetLength(Name, len);
    Stream.Read(Name[1], len * SizeOf(Name[1]));
  end;
end;

{ TDay }

procedure TDay.read(Stream: TStream);
var
  i, len: Integer;
begin
  Stream.Read(len, SizeOf(len));
  SetLength(Events, len);
  for i := 0 to len - 1 do
    Events[i].read(Stream);
end;

procedure TDay.write(Stream: TStream);
var
  i, len: Integer;
begin
  len := Length(Events);
  Stream.Write(len, SizeOf(len));
  for i := 0 to len - 1 do
    Events[i].write(Stream);
end;

{ TWeek }

procedure TWeek.read(Stream: TStream);
var
  i: Integer;
begin
  for i := 0 to High(Days) do
    Days[i].read(Stream);
end;

procedure TWeek.write(Stream: TStream);
var
  i: Integer;
begin
  for i := 0 to High(Days) do
    Days[i].write(Stream);
end;

Server:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
procedure TServerEventHandler.OnServerExecute(AThread: TIdPeerThread);
var m: TMemoryStream;
    Sender: TWeek;
begin
  m:= TMemoryStream.Create;
  try
   Sender:= TWeek.Create;
   SetLength(Sender.Days[0].Events,1);
   Sender.Days[0].Events[0].Name := 'Testevent!';
   Sender.write(m);

   m.Seek(0, soFromBeginning);
   AThread.Connection.WriteStream(m);

   AThread.Connection.Disconnect;
  finally
   m.Free;
  end;
end;

Client:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
var m: TMemoryStream;
    Empfaenger: TWeek;
begin
 IdTCPClient1.Host:= '127.0.0.1';
 IdTCPClient1.Port:= 40000;
 IdTCPClient1.Connect;

 IdTCPClient1.WriteLn('Gib mir die Daten!');

 m:= TMemoryStream.Create;
 try
  Empfaenger:= TWeek.Create;
  IdTCPClient1.ReadStream(m);
  Empfaenger.read(m);
  ShowMessage(Empfaenger.Days[0].Events[0].Name);
 finally
   m.Free;
 end;


Nur tut sich da nichts. Es kommt nur die Meldung: "Verbindung wurde erfolgreich geschlossen!"


Mr_Emre_D - Mi 25.12.13 02:01

Edit: --


LittleBen - Mi 25.12.13 02:19

Diese Fehlermeldung bekam ich voher schonmal. Das hat eher was mit dem Stream zutun, die falsch gesendet werden, vermut ich mal. Das Read hab ich in das OnConnected-Event reingepackt -> kein Unterschied :(


Quitzlinga - Mi 25.12.13 12:30

Hi,

Zitat:
AThread.Connection.WriteStream(m);
AThread.Connection.Disconnect;


Zum Zeitpunkt des Disconnects ist noch nicht gesagt, das du auch alle Streamdaten bekommen hast. Bevor Du beendest, solltes Du den Sendepuffer des Servers vorher leeren (Flush).
Was Dir generell noch fehlt ist ein Protokoll, das die Sende und Empfangsproblematik regelt.

MfG

Quitzlinga


LittleBen - Mi 25.12.13 18:40

Ich hab das mal eben als Testprojekt in den Anhang gepackt.
Auch mit dem Flush funktioniert es nicht. Vorher hat die Übertragung ja auch so funktioniert :?:


Quitzlinga - Mi 25.12.13 21:08

Hi,

Zitat:
Sender.Days[0].Events[0].Name := 'Testevent!';


An dieser Stelle knallts im Server und er schliesst die Verbindung automatisch. Warum ? Sender wird zwar erzeugt, aber nicht Days und auch nicht Events.

MfG

Quitzlinga


LittleBen - Mi 25.12.13 21:47

Tatsächlich ... :autsch:

Aber trotz folgendem funktioniert es nicht:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
var Stream: TMemoryStream;
    Sender: TWeek;
    i: integer;
begin
 Memo1.Lines.Add('['+DateTimeToStr(now)+', '+AThread.Connection.Socket.Binding.IP+']'+' '+Trim(AThread.Connection.ReadLn));
 Stream:= TMemoryStream.Create;
 try
  Sender:= TWeek.Create;
  for i := 0 to High(Sender.Days) do
   Sender.Days[i]:= TDay.Create;

  SetLength(Sender.Days[0].Events,1);
  Sender.Days[0].Events[0]:= TEvent.Create;
  Sender.Days[0].Events[0].Name := 'Testevent!';
  Sender.Write(Stream);

  Stream.Seek(0, soFromBeginning);
  AThread.Connection.WriteStream(Stream);
  AThread.Connection.FlushWriteBuffer();
  AThread.Connection.Disconnect;
 finally
  Stream.Free;
 end;

Wenn ich

Delphi-Quelltext
1:
2:
  AThread.Connection.FlushWriteBuffer();
  AThread.Connection.Disconnect;
raushaue, dann hängt sich der Client einfach auf, sonst passiert nichts. Mit den zwei Zeilen kommt wieder die Meldung "Verbindung wurde erfolgreich geschlossen!"


jaenicke - Do 26.12.13 13:02

Es ist sinnvoll, dass der Server dem Client zuerst mitteilt wie groß der Stream ist, der kommt. Stichwort: Protokoll... das fehlt dir noch komplett.

Wie man im Debugger sofort sieht hängt der Client beim Auslesen und bekommt den Disconnect nicht mit. Daher ist es besser gleich zu sagen wie viel Byte kommen werden und auch nur die zu lesen.

Dann benutzt du noch eine alte Indyversion. Das solltest du dringend ändern, du machst dir nur unnötig Arbeit und musst bei einem späteren Upgrade nur unnötig viel anpassen, wenn du noch Code für die alte Version schreibst. Eine aktuelle Version bekommst du z.B. hier:
http://indy.fulgan.com/ZIP/

Dann hast du den Code für deine Wochendefinition in Client und Server dupliziert. Das macht absolut keinen Sinn. Das gehört in eine gemeinsame Unit.

Im Anhang liegt eine bereinigte und funktionierende Version, getestet mit Delphi 7 Personal und der aktuellen Indy 10_5078 (nur im Bibliothekspfad eingetragen, nicht extra installiert).


LittleBen - Do 26.12.13 14:41

Der Jänicke mal wieder... viele vielen Dank!!! :flehan:


jaenicke - Do 26.12.13 15:07

Da siehst du dann auch gleich wie man generische Listen in deinem Delphi 7 so einbindet, dass sie auch später ab Delphi 2009 automatisch mit echten Generics benutzt werden. ;-)


LittleBen - Do 26.12.13 15:58

Ja das hat mich tief beeindruckt ^^ Werde versuche, das für alle folgenden Units von dem Typ bei zu behalten.
Aber davor hab ich doch noch eine Frage... Wenn die Event-Klasse ein weiteres String Attribut beinhaltet, wie muss ich das dann mit dem Stream handhaben?

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
type
  TEvent = class
  private
    FName: String;
    FLocation: String;
  public
    constructor Create(const ALoadFromStream: TStream); overload;
    constructor Create(const AName, ALocation: String); overload;
    procedure LoadFromStream(const AStream: TStream);
    procedure SaveToStream(const AStream: TStream);
    property Name: String read FName write FName;
    property Location: String read FLocation write FLocation;
  end;

Folgendes kann ja nicht stimmen:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
procedure TEvent.LoadFromStream(const AStream: TStream);
begin
 FName:= LoadStringFromStream(AStream);
 FLocation:= LoadStringFromStream(AStream);
end;

procedure TEvent.SaveToStream(const AStream: TStream);
begin
 SaveStringToStream(AStream, FName);
 SaveStringToStream(AStream, FLocation); 
end;


jaenicke - Do 26.12.13 16:05

Doch, das ist schon richtig. Du speicherst es hintereinander in den Stream und liest es entsprechend in der gleichen Reihenfolge wieder aus.


LittleBen - Do 26.12.13 16:13

Mein Delphi 7 hat sich mal wieder eine andere, gleichname Unit geholt... bin ja selber schuld :autsch: