Designprobleme: Senden kleiner Datensegmente über TCP mit Winsock

Wenn Sie kleine Datenpakete über TCP senden müssen, ist der Entwurf Ihrer Winsock-Anwendung besonders wichtig. Ein Entwurf, der die Interaktion der verzögerten Bestätigung, des Nagle-Algorithmus und der Winsock-Pufferung nicht berücksichtigt, kann die Leistung drastisch beeinträchtigen. In diesem Artikel werden diese Probleme anhand einiger Fallstudien erläutert. Es leitet auch eine Reihe von Empfehlungen für das effiziente Senden kleiner Datenpakete aus einer Winsock-Anwendung ab.

Ursprüngliche Produktversion: Winsock
Ursprüngliche KB-Nummer: 214397

Hintergrund

Wenn ein Microsoft TCP-Stapel ein Datenpaket empfängt, wird ein Verzögerungszeitgeber von 200 ms deaktiviert. Wenn ein ACK gesendet wird, wird der Verzögerungszeitgeber zurückgesetzt und startet eine weitere Verzögerung von 200 ms, wenn das nächste Datenpaket empfangen wird. Um die Effizienz in Internet- und Intranetanwendungen zu erhöhen, verwendet der TCP-Stapel die folgenden Kriterien, um zu entscheiden, wann ein ACK für empfangene Datenpakete gesendet werden soll:

  • Wenn das zweite Datenpaket empfangen wird, bevor der Verzögerungszeitgeber abläuft, wird der ACK gesendet.
  • Wenn Daten in der gleichen Richtung wie der ACK gesendet werden sollen, bevor das zweite Datenpaket empfangen wird und der Verzögerungszeitgeber abläuft, wird der ACK mit dem Datensegment huckepackt und sofort gesendet.
  • Wenn der Verzögerungszeitgeber abläuft, wird der ACK gesendet.

Um zu vermeiden, dass kleine Datenpakete das Netzwerk überlasten, aktiviert der TCP-Stapel standardmäßig den Nagle-Algorithmus, der einen kleinen Datenpuffer aus mehreren Sendeaufrufen zusammensetzt und das Senden verzögert, bis ein ACK für das vorherige gesendete Datenpaket vom Remotehost empfangen wird. Im Folgenden sind zwei Ausnahmen vom Nagle-Algorithmus aufgeführt:

  • Wenn der Stapel einen Datenpuffer vereint hat, der größer als die maximale Übertragungseinheit (Maximum Transmission Unit, MTU) ist, wird sofort ein Paket in voller Größe gesendet, ohne auf den ACK vom Remotehost zu warten. In einem Ethernet-Netzwerk beträgt die MTU für TCP/IP 1460 Bytes.

  • Die TCP_NODELAY Socketoption wird angewendet, um den Nagle-Algorithmus zu deaktivieren, sodass die kleinen Datenpakete ohne Verzögerung an den Remotehost übermittelt werden.

Um die Leistung auf der Anwendungsebene zu optimieren, kopiert Winsock Datenpuffer aus Anwendungssendungsaufrufe an einen Winsock-Kernelpuffer. Anschließend verwendet der Stapel eine eigene Heuristik (z. B. den Nagle-Algorithmus), um zu bestimmen, wann das Paket tatsächlich in den Draht eingefügt werden soll. Sie können die Menge des Winsock-Kernelpuffers ändern, der dem Socket zugeordnet ist, indem Sie die SO_SNDBUF Option (standardmäßig 8.000) verwenden. Bei Bedarf kann Winsock mehr puffern als die SO_SNDBUF Puffergröße. In den meisten Fällen weist der Sendeabschluss in der Anwendung nur darauf hin, dass der Datenpuffer in einem Anwendungs-Sendeaufruf in den Winsock-Kernelpuffer kopiert wurde und nicht anzeigt, dass die Daten das Netzwerkmedium erreicht haben. Die einzige Ausnahme ist, wenn Sie die Winsock-Pufferung deaktivieren, indem Sie auf 0 festlegen SO_SNDBUF .

Winsock verwendet die folgenden Regeln, um einen Sendeabschluss an die Anwendung anzugeben (je nachdem, wie der Sendevorgang aufgerufen wird, kann die Vervollständigungsbenachrichtigung die Funktion sein, die von einem blockierenden Anruf zurückgegeben wird, ein Ereignis signalisiert oder eine Benachrichtigungsfunktion aufruft usw.):

  • Wenn sich der Socket noch innerhalb SO_SNDBUF Kontingents befindet, kopiert Winsock die Daten aus dem Anwendungssende und gibt den Sendevorgang an die Anwendung an.

  • Wenn der Socket das Kontingent überschreitet SO_SNDBUF und nur ein zuvor gepufferter Sendevorgang noch im Stapelkernpuffer vorhanden ist, kopiert Winsock die Daten aus dem Anwendungssende und gibt den Sendeabschluss an die Anwendung an.

  • Wenn der Socket das Kontingent überschreitet SO_SNDBUF und mehrere zuvor gepufferte Sendevorgänge im Stapelkernpuffer vorhanden sind, kopiert Winsock die Daten aus der Anwendungssendung. Winsock gibt den Abschluss des Sendevorgangs an die Anwendung erst an, wenn der Stapel genügend Sendevorgänge abgeschlossen hat, um das Socket wieder innerhalb SO_SNDBUF des Kontingents oder nur einer ausstehenden Sendebedingung zu platzieren.

Fallstudie 1

Ein Winsock-TCP-Client muss 10.000 Datensätze an einen Winsock-TCP-Server senden, um sie in einer Datenbank zu speichern. Die Größe der Datensätze variiert zwischen 20 Bytes und 100 Bytes. Um die Anwendungslogik zu vereinfachen, sieht der Entwurf wie folgt aus:

  • Der Client blockiert nur das Senden. Der Server blockiert recv nur.
  • Der Clientsocket legt den SO_SNDBUF auf 0 fest, sodass jeder Datensatz in einem einzelnen Datensegment ausgeht.
  • Der Server ruft in einer Schleife auf recv . Der in bereitgestellte recv Puffer beträgt 200 Bytes, sodass jeder Datensatz in einem recv Aufruf empfangen werden kann.

Leistung

Während des Tests stellt der Entwickler fest, dass der Client nur fünf Datensätze pro Sekunde an den Server senden konnte. Das Senden der insgesamt 10.000 Datensätze ( maximal 976 KB Daten ) (10000 * 100 / 1024) dauert mehr als eine halbe Stunde, um an den Server zu senden.

Analyse

Da der Client die TCP_NODELAY Option nicht festgelegt, erzwingt der Nagle-Algorithmus, dass der TCP-Stapel auf einen ACK wartet, bevor er ein weiteres Paket über die Verbindung senden kann. Der Client hat jedoch die Winsock-Pufferung deaktiviert, indem er die SO_SNDBUF Option auf 0 festgelegt hat. Daher müssen die 10000 Sendeanrufe gesendet und ACK'ed einzeln gesendet werden. Jede ACK wird um 200 ms verzögert, da folgendes auf dem TCP-Stapel des Servers auftritt:

  • Wenn der Server ein Paket erhält, wird der Verzögerungszeitgeber von 200 ms deaktiviert.
  • Der Server muss nichts zurücksenden, sodass der ACK nicht huckepackt werden kann.
  • Der Client sendet kein weiteres Paket, es sei denn, das vorherige Paket wird bestätigt.
  • Der Verzögerungszeitgeber auf dem Server läuft ab, und der ACK wird zurückgesendet.

So verbessern Sie

Bei diesem Entwurf gibt es zwei Probleme. Erstens gibt es das Problem mit dem Verzögerungszeitgeber. Der Client muss in der Lage sein, innerhalb von 200 ms zwei Pakete an den Server zu senden. Da der Client standardmäßig den Nagle-Algorithmus verwendet, sollte er nur die Standardmäßige Winsock-Pufferung verwenden und nicht auf 0 festlegen SO_SNDBUF . Sobald der TCP-Stapel einen Puffer vereint hat, der größer als die MAXIMALE Übertragungseinheit (Maximum Transmission Unit, MTU) ist, wird sofort ein Paket in voller Größe gesendet, ohne auf den ACK vom Remotehost zu warten.

Zweitens ruft dieser Entwurf einen Sendevorgang für jeden Datensatz mit so geringer Größe auf. Das Senden dieser kleinen Größe ist nicht effizient. In diesem Fall möchte der Entwickler jeden Datensatz auf 100 Bytes auffüllen und gleichzeitig 80 Datensätze von einem Client-Sendeanruf senden. Um dem Server mitzuteilen, wie viele Datensätze insgesamt gesendet werden, kann der Client die Kommunikation mit einem Header mit fester Größe starten, der die Anzahl der zu folgenden Datensätze enthält.

Fallstudie 2

Eine Winsock-TCP-Clientanwendung öffnet zwei Verbindungen mit einer Winsock TCP-Serveranwendung, die den Aktienkursdienst bereitstellt. Die erste Verbindung wird als Befehlskanal verwendet, um das Aktiensymbol an den Server zu senden. Die zweite Verbindung wird als Datenkanal zum Empfangen des Aktienkurses verwendet. Nachdem die beiden Verbindungen hergestellt wurden, sendet der Client über den Befehlskanal ein Aktiensymbol an den Server und wartet, bis der Aktienkurs über den Datenkanal zurückkommt. Die nächste Aktiensymbolanforderung wird erst an den Server gesendet, nachdem die erste Aktiennotierung eingegangen ist. Der Client und der Server legen die SO_SNDBUF Option und TCP_NODELAY nicht fest.

  • Leistung

    Während des Tests stellt der Entwickler fest, dass der Client nur fünf Angebote pro Sekunde erhalten kann.

  • Analyse

    Dieses Design ermöglicht jeweils nur eine ausstehende Aktienkursanforderung. Das erste Stock-Symbol wird über den Befehlskanal (Verbindung) an den Server gesendet, und eine Antwort wird sofort vom Server über den Datenkanal (Verbindung) an den Client zurückgesendet. Anschließend sendet der Client sofort die zweite Aktiensymbolanforderung, und der Sendevorgang wird sofort zurückgegeben, da der Anforderungspuffer im Sendeaufruf in den Winsock-Kernelpuffer kopiert wird. Der TCP-Stapel des Clients kann die Anforderung jedoch nicht sofort aus seinem Kernelpuffer senden, da der erste Sendevorgang über den Befehlskanal noch nicht bestätigt wurde. Nachdem der Verzögerungstimer von 200 ms im Serverbefehlskanal abgelaufen ist, wird der ACK für die erste Symbolanforderung an den Client zurückgegeben. Anschließend wird die zweite Angebotsanforderung erfolgreich an den Server gesendet, nachdem sie um 200 ms verzögert wurde. Das Angebot für das zweite Aktiensymbol wird sofort über den Datenkanal zurückgestellt, da der Verzögerungszeitgeber im Clientdatenkanal zu diesem Zeitpunkt abgelaufen ist. Ein ACK für die vorherige Angebotsantwort wird vom Server empfangen. (Denken Sie daran, dass der Client keine zweite Aktienkursanforderung für 200 ms senden konnte, sodass der Verzögerungszeitgeber auf dem Client abläuft und eine ACK an den Server sendet.) Daher erhält der Client die zweite Angebotsantwort und kann eine weitere Angebotsanforderung ausgeben, die dem gleichen Zyklus unterliegt.

  • So verbessern Sie

    Das Design der beiden Verbindungen (Kanal) ist hier nicht erforderlich. Wenn Sie nur eine Verbindung für die Aktienkursanforderung und -antwort verwenden, kann der ACK für die Angebotsanforderung in der Angebotsantwort huckepackt werden und sofort zurückkommen. Um die Leistung weiter zu verbessern, könnte der Client mehrere Aktienkursanforderungen in einen Sendeaufruf an den Server multiplexen , und der Server könnte auch mehrere Angebotsantworten in einen Sendeaufruf an den Client multiplexen . Wenn das Design der beiden unidirektionalen Kanäle aus irgendeinem Grund erforderlich ist, sollten beide Seiten die TCP_NODELAY Option festlegen, damit die kleinen Pakete sofort gesendet werden können, ohne auf eine ACK für das vorherige Paket warten zu müssen.

Empfehlungen

Obwohl diese beiden Fallstudien erstellt wurden, helfen sie, einige Worst-Case-Szenarien zu veranschaulichen. Wenn Sie eine Anwendung entwerfen, die umfangreiche Kleine-Datensegment-Sendevorgänge und recvsumfasst, sollten Sie die folgenden Richtlinien berücksichtigen:

  • Wenn die Datensegmente nicht zeitkritisch sind, sollte die Anwendung sie zu einem größeren Datenblock zusammenführen, um ihn an einen Sendeanruf zu übergeben. Da der Sendepuffer wahrscheinlich in den Winsock-Kernelpuffer kopiert wird, sollte der Puffer nicht zu groß sein. Etwas weniger als 8K ist effektiv. Solange der Winsock-Kernel einen Block erhält, der größer als die MTU ist, sendet er mehrere Pakete in voller Größe und ein letztes Paket mit dem, was noch übrig ist. Die sendende Seite mit Ausnahme des letzten Pakets wird vom Verzögerungstimer von 200 ms nicht getroffen. Wenn es sich um ein ungerades Paket handelt, unterliegt das letzte Paket weiterhin dem Algorithmus für die verzögerte Bestätigung. Wenn der sendenden Endstapel einen weiteren Block erhält, der größer als die MTU ist, kann er den Nagle-Algorithmus weiterhin umgehen.

  • Vermeiden Sie nach Möglichkeit Socketverbindungen mit einem unidirektionalen Datenfluss. Die Kommunikation über unidirektionale Sockets wird leichter durch die Nagle- und verzögerten Bestätigungsalgorithmen beeinflusst. Wenn die Kommunikation auf eine Anforderung und einen Antwortfluss folgt, sollten Sie einen einzelnen Socket verwenden, um sowohl Sendevorgänge als auch durchzuführen, damit der ACK in der Antwort vorgerückt werden kann.If the communication follows a request and a response, you should use a single socket to do both and recvs so that the ACK can be piggyback on the response.

  • Wenn alle kleinen Datensegmente sofort gesendet werden müssen, legen Sie die Option am Sendeende fest TCP_NODELAY .

  • Wenn Sie nicht garantieren möchten, dass ein Paket über die Übertragung gesendet wird, wenn ein Sendeabschluss von Winsock angegeben wird, sollten Sie die SO_SNDBUF nicht auf 0 (null) festlegen. In der Tat wurde der standardmäßige 8K-Puffer heuristisch festgelegt, um für die meisten Situationen gut zu funktionieren, und Sie sollten ihn nur ändern, wenn Sie getestet haben, dass Ihre neue Winsock-Puffereinstellung Eine bessere Leistung als die Standardeinstellung bietet. Außerdem ist die Festlegung SO_SNDBUF auf 0 (null) hauptsächlich für Anwendungen von Vorteil, die eine Massendatenübertragung durchführen. Selbst dann sollten Sie es für maximale Effizienz in Verbindung mit doppelter Pufferung (mehr als einem ausstehenden Sendevorgang zu einem bestimmten Zeitpunkt) und überlappenden E/A verwenden.

  • Wenn die Datenübermittlung nicht garantiert werden muss, verwenden Sie UDP.

References

Weitere Informationen zur verzögerten Bestätigung und zum Nagle-Algorithmus finden Sie in den folgenden Artikeln:

Braden, R.[1989], RFC 1122, Requirements for Internet Hosts--Communication Layers, Internet Engineering Task Force.