Einzelnen Beitrag anzeigen

Astat

Registriert seit: 2. Dez 2009
Ort: München
320 Beiträge
 
Lazarus
 
#15

Re: Stream über TCP - Blockaufteilung ?

  Alt 5. Dez 2009, 08:00
Hallo Stefanie.


Stefanie hat geschrieben.

  • Mir wäre es lieber, wenn es eine Möglichkeit beim Senden gäbe evtl. die Fragmentierung(die ja in meinen Fall das Zusammenfassen kleiner Streams zu einem Block scheinbar auch ünernimmt) auszuschalten. Einfach, dass ich einen Stream mit z.B. 10 Bytes sende und nur genau das wird als ein Block übertragen, nichts Fragmentiert bzw. zusammengefasst.

Naja, das funktioniert leider nicht in allen Fällen.

Du sendest in einem Netzwerk irgendwelche Daten zu einem Server.
Protokoll ist IPPROTO_TCP.

Du übergibst normalerweise die zu sendenden Daten dem OSI-Layer 1. = Socket.Send!!
Danach brauchst du dir um den Datentransport keine Sorgen mehr zu machen.
Der Sendebuffer und MTU Size ist je nach Einstellung des OS, Treiber bzw, Netzwerkkarte unterschiedlich.
Wenn du kein Performance Freak bist, brauchst du dir darüber keinerlei Gedanken machen.
Die gesendeten Daten werden nun sicher (TCP) zum Server übertragen.
Der Server (OSI-Layer 1. = Socket.recv) hat einen Empfangsbuffer, der je nach OS, und Treiber und oder
Netzwerkkarte, natürlich auch unterschiedlich gross ist.

Also folgendes Szenario:
Client 1 sendet 10 Rote Kugeln, und Client 2 sendet zur gleichen Zeit 9 Grüne Kugeln.

Der Server Buffer hat Platz für 6 Kugeln (MTU * Socet Buffer Size)!

Im Buffer landen 2 Rote und 1 grüne Kugel, du wirst benachrichtigt, dass Daten empfangen wurden.

Du hast dir beim Connect des Clients am Server einen Empfangsbuffer für einen Grünen und einen Roten Client generiert.

Nun sortierst du die Grünen und die Roten Kugeln solange, bis alle Kugeln übertragen wurden.

Hier kommt nun das Problem das schon von Luckie, sirius, Mavarik angesprochen wurde.

Wenn du keine Informationen hast, wieviele Kugeln für einen bestimmten Client eigentlich übertragen werden müssen,
bist du auf verlorenem Posten. Du wirst es nie schaffen die genau gewünschte Anzahl der Kugeln zu übertragen.

Hier brauchst du einen Header (Info Kugel, die sagt dass für Rot 10 Kugeln kommen,
und eine Info Kugel, die sagt, dass für Grün 9 Kugeln kommen),
Also, Informationen wieviele Kugeln für einen bestimmten Climet übertragen werden müssen.

D.h. Wenn die Info Kugel übertragen wurde, müssen die Roten und die Grünen Kugeln solange getrennt werden, bis
die Anzahl der Kugeln für jeden Client erreicht ist.

Wo man nun richtig Hirnschmalz hineinstecken muss, sind die Sonderfälle, die auftreten können.

Der Empfangsbuffer enthält mehrere vollständig übertragene Datensätze (nur Rote Kugeln)

Der Empfangsbuffer enthält mehrere vollständig übertragene Datensätze,
inclusive Fragment des noch nicht vollständig gesendeten Datensatzes. (nur Rote Kugeln)

Der Empfangsbuffer enthält mehrere unvollständig übertragene Datensätze. (Rote und Grüne Kugeln)


Der Empfangsbuffer enthält mehrere vollständig übertragene Datensätze (Rote und Grüne Kugeln)

Der Empfangsbuffer enthält mehrere vollständig übertragene Datensätze,
inclusive Fragment des noch nicht vollständig gesendeten Datensatzes. (von Roten und Grünen Kugeln)

Der Empfangsbuffer enthält mehrere unvollständig übertragene Datensätze. (nur Rote Kugeln)

usw....

Hier ein Beispiel wie dies korrekt für Client und Server implementieren werden kann:

Clients wurden zuvor im Connect Evewnt in einer Hashliste gespeichet.
Jeder Client beinhaltet einen eigenen RecvBuffer = CliCon.FMemBuf = Aufteilung der Kugeln.
Socket Read Speichert die Daten in einem Ringbuffer, und der TServerObj.Thread
liest den Ringbuffer threadsave aus, und bereitet die Daten auf.

Delphi-Quellcode:
procedure TServerObj.Execute;
const
  HEADER_LENGTH = SizeOf(byte) + SizeOf(DWORD);
var
  cbRcv: integer;
  cbWritten: integer;
  cbRest: integer;
  ptrByte: ^Byte;
  nLen: integer;
  nDataSize: integer;
  cb: integer;
  nMsgID: Byte;
  p: pointer;
  CliCon: TCliCon;
  RcvBuf: array [0..WSOCK_READ_BUFFER_SIZE * 8] of byte;
  RcvBufItem: TRcvBufItem;
begin
  while not terminated do begin

    if FRecvBuffer.CountBytes > 0 then begin

      RcvBufItem := TRcvBufItem(FRecvBuffer.Peek(nLen)^);

      if FRecvBuffer.Remove(SizeOf(TRcvBufItem)) <> SizeOf(TRcvBufItem) then
        raise exception.Create('nLen <> FRecvBuffer.Remove(nLen)');

      cbRcv := RcvBufItem.cbData;
      if cbRcv <= 0 then
        raise exception.create('cbRcv <= 0');

      move(RcvBufItem.ptrData^, RcvBuf[0], cbRcv);

      CliCon := FIntHash.ValueOf(RcvBufItem.Socket);
      if CliCon = nil then begin
        if GlobalFree(RcvBufItem.hMemData) <> 0 then
          raise exception.create('GlobalFree(RcvBufItem.hMemData)) <> 0');

        if GlobalFree(RcvBufItem.hMemStruct) <> 0 then
          raise exception.create('GlobalFree(RcvBufItem.hMemStruct) <> 0');
        Continue;
      end;

      cbWritten := CliCon.FMemBuf.Write(@RcvBuf[0], cbRcv);
      cbRest := cbRcv - cbWritten;
      if cbRest <> 0 then begin
        CliCon.FMemBuf.SetBufSize((CliCon.FMemBuf.BufSize + cbRcv) * 2);

        if CliCon.FMemBuf.Write(@RcvBuf[cbWritten], cbRest) <> cbRest then
          raise exception.Create('you should never see this');
      end;

      while true do begin
        nLen := HEADER_LENGTH;
        ptrByte := CliCon.FMemBuf.Peek(nLen);
        if ptrByte <> nil then
        begin
          move(ptrByte^, nMsgID, SizeOf(Byte));
          inc(ptrByte, SizeOf(Byte));
          move(ptrByte^, nDataSize, SizeOf(DWORD));
        end else begin
          Break;
        end;

        if nMsgID > MSG_ID_KEEP_ALLIVE then begin
          CliCon.FMemBuf.Remove(CliCon.FMemBuf.BufSize);
          raise exception.Create('not (nMsgID in [MSG_ID_LOGON..MSG_ID_KEEP_ALIVE])');
        end;

        if CliCon.FMemBuf.CountBytes >= nDataSize + HEADER_LENGTH then
        begin //-- Mindestens ein Datensatz ist vorhanden
          if CliCon.FMemBuf.Remove(HEADER_LENGTH) = 0 then
            raise exception.Create('Buffer.Remove(HEADER_LENGTH) = 0');

          cb := nDataSize;
          p := CliCon.FMemBuf.Peek(cb);

          CliCon.ProcessMessage(nMsgID, p, nDataSize);

          if CliCon.FMemBuf.Remove(nDataSize) = 0 then
            raise exception.Create('Buffer.Remove(nDataSize) = 0');
        end else //-- if CliCon.FMemBuf.CountBytes >= nDataSize + HEADER_LENGTH
          Break;
      end; //-- while true do begin

      if GlobalFree(RcvBufItem.hMemData) <> 0 then
        raise exception.create('GlobalFreeII(RcvBufItem.hMemData)) <> 0');

      if GlobalFree(RcvBufItem.hMemStruct) <> 0 then
        raise exception.create('GlobalFreeII(RcvBufItem.hMemStruct) <> 0');
    end else begin
      DoGarbageCollection;
      Waitforsingleobject(FMsgDispatchEvent, INFINITE);
    end;
  end;
end;
Für das obige Beispiel ist auch vollständiger Source vorhanden.

Also, wenn du Asynchrone und oder Synchrone Socket brauchst, sag einfach bescheid, hab da fix und fertige
DLL's (Synchron, Asynchron) Client und Server incl. Source, hochlast getestet.
Asynchrone Client Server Anwendung mit ~10000 Client Verbindungen.
Synchrone Client Server Anwendung mit 2 X Quad Xeon mit ca. 3000 Requests/s bei ~ 25Kb Daten in einem 1 GB Netz.

Können dir auch als Tutorial dienen, um das Hirnschmalz Problem in den Griff zu bekommen.

Hoffe etwas geholfen zu haben.

lg. Astat
Lanthan Astat
06810110811210410503210511511603209711003210010110 9032084097103
03211611111604403209711003210010110903210010510103 2108101116122
11610103209010110510810103206711110010103210511003 2068101108112
10410503210310111509910411410510109810111003211910 5114100046
  Mit Zitat antworten Zitat