Ontwerpproblemen: kleine gegevenssegmenten verzenden via TCP met Winsock

Wanneer u kleine gegevenspakketten via TCP wilt verzenden, is het ontwerp van uw Winsock-toepassing van cruciaal belang. Een ontwerp dat geen rekening houdt met de interactie van vertraagde bevestiging, het Nagle-algoritme en Winsock-buffering, kan de prestaties drastisch beïnvloeden. In dit artikel worden deze problemen besproken aan de hand van een aantal casestudy's. Het leidt ook een reeks aanbevelingen af voor het efficiënt verzenden van kleine gegevenspakketten vanuit een Winsock-toepassing.

Oorspronkelijke productversie: Winsock
Origineel KB-nummer: 214397

Achtergrond

Wanneer een Microsoft TCP-stack een gegevenspakket ontvangt, gaat een vertragingstimer van 200 ms af. Wanneer een ACK wordt verzonden, wordt de vertragingstimer opnieuw ingesteld en wordt er nog een vertraging van 200 ms gestart wanneer het volgende gegevenspakket wordt ontvangen. Om de efficiëntie in zowel internet- als intranettoepassingen te verhogen, gebruikt TCP-stack de volgende criteria om te bepalen wanneer een ACK moet worden verzonden op ontvangen gegevenspakketten:

  • Als het tweede gegevenspakket wordt ontvangen voordat de vertragingstimer verloopt, wordt de ACK verzonden.
  • Als er gegevens in dezelfde richting moeten worden verzonden als de ACK voordat het tweede gegevenspakket wordt ontvangen en de vertragingstimer verloopt, wordt de ACK meegelift met het gegevenssegment en onmiddellijk verzonden.
  • Wanneer de vertragingstimer verloopt, wordt de ACK verzonden.

Om te voorkomen dat kleine gegevenspakketten het netwerk overbelasten, schakelt TCP-stack standaard het Nagle-algoritme in, dat een kleine gegevensbuffer van meerdere verzendaanroepen samenvoegt en het verzenden ervan vertraagt totdat een ACK voor het vorige verzonden gegevenspakket is ontvangen van de externe host. Hier volgen twee uitzonderingen op het Nagle-algoritme:

  • Als de stack een gegevensbuffer heeft verzameld die groter is dan de Maximum Transmission Unit (MTU), wordt er onmiddellijk een pakket op volledige grootte verzonden zonder te wachten op de ACK van de externe host. Op een Ethernet-netwerk is de MTU voor TCP/IP 1460 bytes.

  • De TCP_NODELAY socketoptie wordt toegepast om het Nagle-algoritme uit te schakelen, zodat de kleine gegevenspakketten zonder vertraging aan de externe host worden geleverd.

Om de prestaties op de toepassingslaag te optimaliseren, kopieert Winsock gegevensbuffers van toepassingsverzendingsaanroepen naar een Winsock-kernelbuffer. Vervolgens gebruikt de stack zijn eigen heuristieken (zoals het Nagle-algoritme) om te bepalen wanneer het pakket daadwerkelijk op de draad moet worden geplaatst. U kunt de hoeveelheid Winsock-kernelbuffer die is toegewezen aan de socket wijzigen met behulp van de SO_SNDBUF optie (dit is standaard 8K). Indien nodig kan Winsock meer bufferen dan de SO_SNDBUF buffergrootte. In de meeste gevallen geeft de voltooiing van het verzenden in de toepassing alleen aan dat de gegevensbuffer in een toepassingsverzendingsaanroep is gekopieerd naar de Winsock-kernelbuffer en niet aangeeft dat de gegevens het netwerkmedium hebben bereikt. De enige uitzondering is wanneer u de Winsock-buffering uitschakelt door in te stellen SO_SNDBUF op 0.

Winsock gebruikt de volgende regels om een voltooiing van het verzenden naar de toepassing aan te geven (afhankelijk van hoe het verzenden wordt aangeroepen, kan de voltooiingsmelding de functie zijn die terugkeert van een blokkeringsaanroep, het signaleren van een gebeurtenis of het aanroepen van een meldingsfunctie, enzovoort):

  • Als de socket zich nog steeds binnen SO_SNDBUF quotum bevindt, kopieert Winsock de gegevens uit de toepassingsverzending en geeft de voltooiing van het verzenden aan de toepassing aan.

  • Als de socket het quotum overschrijdt SO_SNDBUF en er nog maar één eerder gebufferde verzendbewerking in de stack-kernelbuffer is, kopieert Winsock de gegevens van de toepassingsverzending en geeft de voltooiing van het verzenden naar de toepassing aan.

  • Als de socket het quotum overschrijdt SO_SNDBUF en er meer dan één eerder gebufferde verzenden in de stack-kernelbuffer is, kopieert Winsock de gegevens uit de toepassingsverzending. Winsock geeft de voltooiing van het verzenden naar de toepassing pas aan als de stack voldoende verzendbewerkingen heeft voltooid om de socket binnen SO_SNDBUF het quotum of slechts één openstaande verzendvoorwaarde te plaatsen.

Casestudy 1

Een Winsock TCP-client moet 10000 records verzenden naar een Winsock TCP-server om op te slaan in een database. De grootte van de records varieert van 20 bytes tot 100 bytes lang. Om de toepassingslogica te vereenvoudigen, is het ontwerp als volgt:

  • De client blokkeert alleen verzenden. De server blokkeert recv alleen.
  • De clientsocket stelt de SO_SNDBUF in op 0, zodat elke record in één gegevenssegment wordt uitgevoerd.
  • De server roept recv in een lus aan. De buffer die wordt gepost in recv is 200 bytes, zodat elke record in één recv aanroep kan worden ontvangen.

Prestatie

Tijdens het testen vindt de ontwikkelaar dat de client slechts vijf records per seconde naar de server kan verzenden. De totale 10000 records, maximaal 976 kb aan gegevens (10000 * 100 / 1024), duurt meer dan een half uur om naar de server te verzenden.

Analyse

Omdat de client de TCP_NODELAY optie niet instelt, dwingt het Nagle-algoritme de TCP-stack af te wachten op een ACK voordat een ander pakket op de draad kan worden verzonden. De client heeft de Winsock-buffering echter uitgeschakeld door de SO_SNDBUF optie in te stellen op 0. Daarom moeten de 10000 verzendoproepen afzonderlijk worden verzonden en ACK'ed. Elke ACK is 200 ms vertraagd omdat het volgende optreedt op de TCP-stack van de server:

  • Wanneer de server een pakket ontvangt, gaat de vertragingstimer van 200 ms af.
  • De server hoeft niets terug te sturen, dus de ACK kan niet worden meegelift.
  • De client verzendt geen ander pakket, tenzij het vorige pakket is bevestigd.
  • De vertragingstimer op de server verloopt en de ACK wordt teruggestuurd.

Verbeteren

Er zijn twee problemen met dit ontwerp. Ten eerste is er het probleem met de vertragingstimer. De client moet binnen 200 ms twee pakketten naar de server kunnen verzenden. Omdat de client standaard het Nagle-algoritme gebruikt, moet deze alleen de standaard Winsock-buffering gebruiken en niet ingesteld SO_SNDBUF op 0. Zodra de TCP-stack een buffer heeft gepoold die groter is dan de Maximum Transmission Unit (MTU), wordt er onmiddellijk een volledig pakket verzonden zonder te wachten op de ACK van de externe host.

Ten tweede roept dit ontwerp één send aan voor elke record van zo'n klein formaat. Het verzenden van deze kleine grootte is niet efficiënt. In dit geval wil de ontwikkelaar mogelijk elke record op 100 bytes instellen en 80 records tegelijk verzenden vanaf één clientaanroep. Om de server te laten weten hoeveel records in totaal worden verzonden, wil de client de communicatie starten met een header met het aantal records dat moet worden gevolgd.

Casestudy 2

Een Winsock TCP-clienttoepassing opent twee verbindingen met een Winsock TCP-servertoepassing die voorraadkoersenservice biedt. De eerste verbinding wordt gebruikt als een opdrachtkanaal om het voorraadsymbool naar de server te verzenden. De tweede verbinding wordt gebruikt als een gegevenskanaal om de aandelenkoers te ontvangen. Nadat de twee verbindingen tot stand zijn gebracht, verzendt de client een voorraadsymbool naar de server via het opdrachtkanaal en wacht tot de aandelenkoers via het gegevenskanaal terugkomt. De volgende voorraadsymboolaanvraag wordt pas naar de server verzonden nadat de eerste aandelenkoers is ontvangen. De client en de server stellen de SO_SNDBUF optie en TCP_NODELAY niet in.

  • Prestatie

    Tijdens het testen vindt de ontwikkelaar dat de client slechts vijf aanhalingstekens per seconde kan krijgen.

  • Analyse

    Dit ontwerp staat slechts één openstaande offerteaanvraag tegelijk toe. Het eerste voorraadsymbool wordt via het opdrachtkanaal (verbinding) naar de server verzonden en er wordt onmiddellijk een antwoord teruggestuurd van de server naar de client via het gegevenskanaal (verbinding). Vervolgens verzendt de client onmiddellijk de aanvraag voor het tweede voorraadsymbool en de verzending retourneert onmiddellijk wanneer de aanvraagbuffer in de verzendaanroep wordt gekopieerd naar de Winsock-kernelbuffer. De TCP-clientstack kan de aanvraag echter niet onmiddellijk verzenden vanuit de kernelbuffer, omdat de eerste verzendbewerking via het opdrachtkanaal nog niet is bevestigd. Nadat de vertragingstimer van 200 ms op het serveropdrachtkanaal is verlopen, komt de ACK voor de eerste symboolaanvraag terug naar de client. Vervolgens wordt de tweede offerteaanvraag verzonden naar de server nadat deze 200 ms is vertraagd. De offerte voor het tweede aandelensymbool komt onmiddellijk terug via het gegevenskanaal, omdat op dit moment de vertragingstimer op het clientgegevenskanaal is verlopen. Een ACK voor het vorige offerteantwoord wordt ontvangen door de server. (Houd er rekening mee dat de client geen tweede offerteaanvraag voor 200 ms kon verzenden, waardoor de vertragingstimer op de client kon verlopen en een ACK naar de server kon verzenden.) Als gevolg hiervan krijgt de client het tweede offerteantwoord en kan een andere offerteaanvraag indienen, die onderhevig is aan dezelfde cyclus.

  • Verbeteren

    Het ontwerp van twee verbindingen (kanalen) is hier niet nodig. Als u slechts één verbinding gebruikt voor de offerteaanvraag en het antwoord, kan de ACK voor de offerteaanvraag worden meegelift op het offerteantwoord en onmiddellijk terugkomen. Om de prestaties verder te verbeteren, kan de client meerdere aanvragen voor aandelenkoersen multiplexen in één aanroep naar de server en kan de server ook meerdere offerteantwoorden multiplexen in één aanroep naar de client. Als het ontwerp van twee unidirectionele kanalen om een of andere reden nodig is, moeten beide zijden de TCP_NODELAY optie zo instellen dat de kleine pakketten onmiddellijk kunnen worden verzonden zonder te hoeven wachten op een ACK voor het vorige pakket.

Aanbevelingen

Hoewel deze twee casestudy's zijn gemaakt, helpen ze om enkele worst case scenario's te illustreren. Wanneer u een toepassing ontwerpt waarbij uitgebreide kleine gegevenssegmenten en worden verzonden, recvsmoet u rekening houden met de volgende richtlijnen:

  • Als de gegevenssegmenten niet tijdskritiek zijn, moet de toepassing deze samenvoegen tot een groter gegevensblok om door te geven aan een verzendoproep. Omdat de verzendbuffer waarschijnlijk wordt gekopieerd naar de Winsock-kernelbuffer, mag de buffer niet te groot zijn. Iets minder dan 8K is effectief. Zolang de Winsock-kernel een blok krijgt dat groter is dan de MTU, worden meerdere volledige pakketten en een laatste pakket verzonden met wat er nog over is. De verzendzijde, met uitzondering van het laatste pakket, wordt niet geraakt door de vertragingstimer van 200 ms. Het laatste pakket, als het toevallig een oneven pakket is, is nog steeds onderhevig aan het vertraagde bevestigingsalgoritmen. Als de verzendende eindstack een ander blok krijgt dat groter is dan de MTU, kan het Nagle-algoritme nog steeds worden omzeild.

  • Vermijd indien mogelijk socketverbindingen met een unidirectionele gegevensstroom. Communicatie via unidirectionele sockets wordt gemakkelijker beïnvloed door de Nagle- en vertraagde bevestigingsalgoritmen. Als de communicatie een aanvraag- en antwoordstroom volgt, moet u één socket gebruiken om beide verzendingen uit te voeren, recvs zodat de ACK kan worden meegelift op het antwoord.

  • Als alle kleine gegevenssegmenten onmiddellijk moeten worden verzonden, stelt u TCP_NODELAY de optie in aan de verzendzijde.

  • Tenzij u wilt garanderen dat een pakket op de draad wordt verzonden wanneer een verzend voltooid wordt aangegeven door Winsock, moet u de SO_SNDBUF niet instellen op nul. In feite is de standaard 8K-buffer heuristisch ingesteld om goed te werken voor de meeste situaties en u moet deze niet wijzigen, tenzij u hebt getest dat uw nieuwe Winsock-bufferinstelling u betere prestaties geeft dan de standaardinstelling. Bovendien is de instelling SO_SNDBUF op nul vooral nuttig voor toepassingen die bulkgegevensoverdracht uitvoeren. Zelfs dan moet u het voor maximale efficiëntie gebruiken in combinatie met dubbele buffering (meer dan één openstaande verzendbewerking op elk gewenst moment) en overlappende I/O.

  • Als de gegevenslevering niet hoeft te worden gegarandeerd, gebruikt u UDP.

Verwijzingen

Zie het volgende voor meer informatie over vertraagde bevestiging en het Nagle-algoritme:

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