Entwickler-Ecke

Windows API - Socket: Send( <65 Megabytes> ); in 1 Sekunde fertig-ph


Noop - Fr 21.01.05 11:56
Titel: Socket: Send( <65 Megabytes> ); in 1 Sekunde fertig-ph
Hallo,
ich habe Serverseitig ein Programm geschrieben, was die Daten auf einen Socket "nur" mit zB 5 000 000 Bytes pro Sekunde einliest (mit Recv) und verarbeitet - soll sozusagen eine Bremse werden, damit übertragung von jemand anders von mir weg nicht mehr meine ganze Bandbreite blockiert (alles in Non-VCL natürlich)
An der Leseschnittstelle des Programmes wird auch live mitgeloggt, wieviel Bytes gelesen wurden und das scheint auch in der Testphase prima zu funktionieren.


Nun habe ich ein "Stress-Test"-Programm geschrieben, der das Programm mal "zubomben" soll um zu gucken wie es auf solche Situationen reagiert:

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:
function BigSend(s:TSocket; buf:PChar; bufsize:integer):integer;
var
  ln, pt: integer;
  d: double;
const
  timeout: integer=5000//5 Sekunden
begin
  d:=now;
  pt:=0;
  while (pt<bufsize) and (now-d*86400<timeout) do begin
    ln:=Send(s, buf[pt], bufsize-pt, 0); //ln = Bytes, die erfolgreich (!) gesendet wurden
    if ln>0 then begin //Wenn gesendet werden konnte
      Inc(pt, ln);
      d:=now;
    end;
  end;
end;
...
procedure Test;
var
..
  s:string;
...
begin
  ...
  SetLength(s, 65432147);
  for i:=1 to length(s) do s[i]:=Chr(Random(256));
  BigSend(MySock.SocketHandle, @s[1], Length(s));
  ...
end;


Allerdings ist Delphi mit der Funktion BigSend() schon in einer Sekunde fertig statt erst in zwölf Sekunden, obwohl der Server nur fleißig wie erwartet nur die 5 000 000 Bytes in der Sekunde mit Recv abfragt und es kommen auch in laufe der Zeit auch alle Bytes an.

Wie denn das? Obwohl es ein nichtblockierender Socket ist, habe ich doch mit meiner selbstgecodeten Funktion BigSend() extra die Schleife so programmiert, das er erst wieder rauskommen soll, wenn alle Daten drüben sind oder 5 Sekunden nichts gesendet werden konnte (wo es nicht so weit kam).
Ich bin davon ausgegangen, das Send() zurückgibt, wieviele Bytes er erfolgreich gesendet hat.

Ich kann es mir dadurch erklären, das die Socket-API die mit Send() angegebene Daten irgendwo zwischenspeichert, aber mein Task-Manager zeigt weder auf dem Client- noch auf dem Serverrechner die 65 MB Extra speicherauslastung an.

Kann mir jemand erklären, ob, wie, wo und was die Socket-API zwischenspeichert (außer dem internen 64KB-Socketbuffer, deren Existenz mir schon bekannt ist)?

Ich bin davon ausgegangen, das Send() zurückgibt wieviele Bytes erfolgreich gesendet wurden und Recv() dann je nach Timing vorgibt, wieviele Bytes ich mit Recv() lese dann vorgibt, wieviele Bytes übertragen werden können, zB also Recv(s, buf, 1000, 0) jede nur jede zehntel Sekunde aufgerufen würde eine Übertragungsrate von 10 000 Bytes / s ergeben.


ScorpionKing - Fr 21.01.05 11:59

poste doch mal den code von deinem server!!


uall@ogc - Fr 21.01.05 16:14

"schon in einer Sekunde fertig statt erst in zwölf Sekunden"

wie kommst denn bitte auf 12 sekunden?

desweiteren weißt du schon das

(now-d*86400<timeout)

immer < timeout ist?

delphi rechnet das so wie in der mathematik üblich ->punkt vor strichrechnung
((now-(d*86400))<timeout)


Noop - Fr 21.01.05 17:20

12 Sekunden sorry war ein Rechenfehler, habs leider voreilig im Kopf gemacht - 65432147 Bytes : 5000000 Bytes/sek. = 13,086 sek.

Und die Timeout-Routine war auch fehlerhaft, allerdings selbst nach paar Korrekturen des Timeout-Mechamismus blieb das Phänomen (Timeout wäre ja nie in Anspruch genommen bei BigSend(), selbst wenn ich kein Fehler gemacht hätte).
War allerdings weil ich da gerade nicht an den Rechner drankam wo die Projekte waren.

::

Da der Server schon mehrere Tausend Zeilen Code und sogar eine Plug-In Steuerung hat, habe ich das mal einfacherhalbe jetzt Extra in einem neuen (Test-)Projekt einer Unit vereinfacht und speicherschonender nachgestellt mit:

- Einem ClientSocket und einem ServerSocket - zwar jetzt teilweise VCL geworden, allerdings das Wesentliche, die Recv() und Send()-Funktion ist immernoch die selbe - NonVCL
- 500 000 Bytes pro Sekunde (Timer auf 100 ms; jeweils 50 000 Bytes lesen)
- 5 000 000 Bytes werden mit BigSend() Patched v1.1 gesendet

Ergebnis hier:
- Empfangen korrekterweise jetzt wie erwartet 10 Sekunden
- Senden dauert komischerweise 30-50 ms;

- das selbe tritt bei mir im kleinen und auch im großen Versendungen auf, so das es egal ist, ob ich jetzt 5 MB oder 1000 MB sende.

EDIT: Ach der Quelltext:


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:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
unit unitTest;

(*
  Benötigt um diesen Code hier einzufügen und auszuführen:
  1 ClientSocket "ClientSocket1"
  1 ServerSocket "ServerSocket1"
  2 Labels "Label1" "Label2"
  1 Timer "Timer1"
*)


interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  ExtCtrls, ScktComp, WinSock, StdCtrls;

type
  TForm1 = class(TForm)
    ServerSocket1: TServerSocket;
    Timer1: TTimer;
    ClientSocket1: TClientSocket;
    Label1: TLabel;
    Label2: TLabel;
    procedure ServerSocket1ClientError(Sender: TObject;
      Socket: TCustomWinSocket; ErrorEvent: TErrorEvent;
      var ErrorCode: Integer);
    procedure Timer1Timer(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure ClientSocket1Connect(Sender: TObject;
      Socket: TCustomWinSocket);
  private
    { Private-Deklarationen }
  public
    { Public-Deklarationen }
  end;

function BigSend(s:TSocket; buf:PChar; bufsize:integer):integer;

var
  Form1: TForm1;
  buf: string='';
  bread: integer=0;

implementation

{$R *.DFM}

function BigSend(s:TSocket; buf:PChar; bufsize:integer):integer;
var
  ln, pt: integer;
  d: double;
const
  timeout: integer=5//5 Sekunden
begin
  d:=now;
  pt:=0;
  while (pt<bufsize) and ((now-d)*86400<timeout) do begin
    ln:=Send(s, buf[pt], bufsize-pt, 0); //ln = Bytes, die erfolgreich (!) gesendet wurden
    if ln>0 then begin //Wenn gesendet werden konnte
      Inc(pt, ln);
      d:=now;
    end else begin
      //showmessage(syserrormessage(WSAGetLastError));break;
    end;
    Application.ProcessMessages;
  end;
  result:=0;
end;

procedure TForm1.ServerSocket1ClientError(Sender: TObject;
  Socket: TCustomWinSocket; ErrorEvent: TErrorEvent;
  var ErrorCode: Integer);
begin
  ErrorCode:=0;
end;

procedure TForm1.Timer1Timer(Sender: TObject);
var i,ln,aln:integer;
begin
  for i:=0 to ServerSocket1.Socket.ActiveConnections-1 do begin
    aln:=0;
    while aln<50000 do begin
      ln:=Recv(ServerSocket1.Socket.Connections[i].SocketHandle,
               buf[1+aln],50000-aln,0);
      if ln>=0 then aln:=aln+ln;
      Application.ProcessMessages;
    end;
    bread:=bread+aln;
  end;
  Label2.Caption:='Empfangen: '+IntToStr(bread)+' Bytes';
  if bread>=5000000 then timer1.enabled:=false;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  ServerSocket1.Port:=4444;
  ServerSocket1.Active:=True;
  Timer1.Interval:=100;
  SetLength(buf,5000000);
  ClientSocket1.Address:='127.0.0.1';
  CLientSocket1.Port:=4444;
  ClientSocket1.Active:=True;
end;

procedure TForm1.ClientSocket1Connect(Sender: TObject;
  Socket: TCustomWinSocket);
var s:string;
 i:integer;
 c:cardinal;
begin
  SetLength(s, 5000000);
  for i:=1 to length(s) do s[i]:=Chr(Random(256));
  c:=GetTickCount;
  BigSend(CLientSocket1.Socket.SocketHandle,
          @s[1],length(s));
  c:=GetTickCount-c;
  Label1.Caption:='5000000 Bytes gesendet in '+inttostr(c)+' ms';
  s:='';
end;

end.


uall@ogc - Fr 21.01.05 17:37

bei deinem senden code kann ich ja noch nichtmal erkennen wo da nen timer drin sein soll der das versenden verlansgamt....


Noop - Fr 21.01.05 17:52

Das Empfangen wird ja verlangsamt und nicht das Versenden! (Andersherum würde das gehen aber ich habe auf der Anwendung, die später mir die Daten schicken soll (also ich empfangen), keinerlei Einfluss)

Muss doch gelten, das das "langsamste Glied in der Kette" die Geschwindigkeit vorgibt, oder nicht? :?:
Also wenn ich nur mit 500 000 Bytes pro Sekunde die Daten aus dem Socket abfrage, wie kann man in unter Sekunde 5 MB Daten verschicken?
Wird der Speicher des Servers oder des Clients belastet?

Oder wie realisier ich sonst, das ich vorgebe, wie schnell der Andere sein darf?

Bei der Arbeit ist das doch auch so: wenn ich mit einem Lastwagen pro Stunde 1000 Kästen transportiert kriege aber ich bekomme am Abfahrtsort 5000 Kästen Pro Stunde zum Wegtransportieren, bleibt die Arbeitsgeschwindigkeit immernoch auf 1000 Kästen pro Stunde, die ich zur anderen Firma transportiere.


uall@ogc - Fr 21.01.05 17:58

die daten gehen verloren??? bzw. der client buffert die solange irgendwo ka ;>


Noop - Fr 21.01.05 18:17

uall@ogc hat folgendes geschrieben:
die daten gehen verloren???

Das ist ja die Sache: Bei mir geht so kein Byte verloren, das ist wenigstens eine positive Nachricht 8)
Allerdings würde ich gerne wissen, wie die Socket-API das genau Handhabt und wie ich dem Gegenüber, wovon ich Daten bekomme, sagen kann, er solle nicht so viel Gas geben.

Fall:
Ich habe ein TCP-Wrapper (Art Proxy) gebaut.
Rechner 1 (LAN Intern): Win2K-Router, mit TCP-Wrapper (also mein Programm) @ Port XY zeigt nach Rechner 2: Port XY
Rechner 2 (LAN Intern): Da läuft Apache oder auch RealVNC usw.
Rechner F (F wie Fremd; jemand anders aus dem Internet): Ruft von Internet-IP ab

Was passiert (jetzt meine Vorstellung wenn mein Server fix und fertig ist):
Verbindung/Abfrage:
Rechner F Verbindet mit Rechner 2, Port XY über Internet
Rechner 2 Verbindet mit Rechner 1 im LAN, Port XY
Serverantwort:
Rechner 1 Service gibt Daten als Stream mit 100 Megabits (LAN) zurück
Rechner 2 bekommt Daten leider mit 100 Megabits trotz beschränkung im Recv(), der nur 90 kbit/s (DSL-Bandbreite - 38kbit/s für Reserve = 90kbit/s)
Serverantwort->Problem:
Wenn ich den Code so einbaue wie er ist und ein Wrapper draus mache, hat Rechner 1 die Datenmenge (Worse case: zB eine 650 MB große .iso-Datei) schon fertig gesendet angeblich, allerdings ist jetzt bei einem Rechner 650 MB Speicherauslastung und auch wenn Rechner 2 mit Recv() nur 90 kbit/s empfängt und 90 kbit/s sendet damit nicht meine Bandbreite ganz belegt wird, und die 650 MB werden nur langsam abgebaut.

Und mit 50 Verbindungen gleichzeitig könnte man wunderbar (wer von meinem Problem Kenntniss hat) eine DoS-Attacke fahren :(