Designproblem – Skicka små datasegment via TCP med Winsock

När du behöver skicka små datapaket via TCP är utformningen av Winsock-programmet särskilt viktig. En design som inte tar hänsyn till interaktionen mellan fördröjd bekräftelse, Nagle-algoritmen och Winsock-buffring kan drastiskt påverka prestandan. Den här artikeln beskriver dessa problem med hjälp av ett par fallstudier. Den härleder också en serie rekommendationer för att skicka små datapaket effektivt från ett Winsock-program.

Ursprunglig produktversion: Winsock
Ursprungligt KB-nummer: 214397

Bakgrund

När en Microsoft TCP-stack tar emot ett datapaket försvinner en fördröjningstimer på 200 ms. När en ACK skickas återställs fördröjningstimern och startar ytterligare 200 ms fördröjning när nästa datapaket tas emot. För att öka effektiviteten i både Internet- och intranätprogram använder TCP-stacken följande kriterier för att bestämma när en ACK ska skickas på mottagna datapaket:

  • Om det andra datapaketet tas emot innan fördröjningstimern upphör att gälla skickas ACK.
  • Om det finns data som ska skickas i samma riktning som ACK innan det andra datapaketet tas emot och fördröjningstimern upphör att gälla, returneras ACK med datasegmentet och skickas omedelbart.
  • När fördröjningstimern upphör att gälla skickas ACK.

För att undvika att små datapaket överbelastar nätverket aktiverar TCP-stacken Nagle-algoritmen som standard, som sammanslår en liten databuffert från flera sändningsanrop och fördröjer sändningen tills en ACK för det tidigare datapaketet som skickades tas emot från fjärrvärden. Följande är två undantag till Nagle-algoritmen:

  • Om stacken har sammanslagit en databuffert som är större än MTU (Maximum Transmission Unit) skickas ett fullstort paket omedelbart utan att vänta på ACK från fjärrvärden. I ett Ethernet-nätverk är MTU för TCP/IP 1 460 byte.

  • Socket-alternativet TCP_NODELAY används för att inaktivera Nagle-algoritmen så att de små datapaketen levereras till fjärrvärden utan dröjsmål.

För att optimera prestanda på programlagret kopierar Winsock databuffertar från programmet och skickar anrop till en Winsock-kernelbuffert. Sedan använder stacken sin egen heuristik (till exempel Nagle-algoritm) för att avgöra när paketet faktiskt ska sättas på tråden. Du kan ändra mängden Winsock-kernelbuffert som allokerats till socketen SO_SNDBUF med hjälp av alternativet (det är 8 000 som standard). Vid behov kan Winsock buffrar mer än buffertstorleken SO_SNDBUF . I de flesta fall anger sändningskompletteringen i programmet endast att databufferten i ett programsändningsanrop kopieras till Winsock-kernelbufferten och anger inte att data har nått nätverksmediet. Det enda undantaget är när du inaktiverar Winsock-buffring genom att ange SO_SNDBUF till 0.

Winsock använder följande regler för att ange ett slutförande av sändningen till programmet (beroende på hur sändningen anropas kan slutförandemeddelandet vara funktionen som returnerar från ett blockerande anrop, signalerar en händelse eller anropar en meddelandefunktion och så vidare):

  • Om socketen fortfarande ligger inom SO_SNDBUF kvot kopierar Winsock data från programmet och anger att sändningen har slutförts till programmet.

  • Om socketen överskrider SO_SNDBUF kvoten och det bara finns en tidigare buffrad sändning i stackkärnbufferten kopierar Winsock data från programmet och anger att sändningen har slutförts till programmet.

  • Om socketen överskrider SO_SNDBUF kvoten och det finns fler än en tidigare buffrad sändning i stackkärnbufferten kopierar Winsock data från programmet som skickas. Winsock anger inte att sändningen har slutförts till programmet förrän stacken har slutförts tillräckligt många skickar för att återställa socketen inom SO_SNDBUF kvoten eller bara ett utestående sändningsvillkor.

Fallstudie 1

En Winsock TCP-klient måste skicka 1 0000 poster till en Winsock TCP-server för lagring i en databas. Storleken på posterna varierar från 20 byte till 100 byte långa. För att förenkla programlogik är designen följande:

  • Klienten blockerar endast sändning. Servern blockerar recv endast.
  • Klientsocketen SO_SNDBUF anger till 0 så att varje post går ut i ett enda datasegment.
  • Servern anropar recv i en loop. Bufferten som publiceras i recv är 200 byte så att varje post kan tas emot i ett recv anrop.

Prestanda

Under testningen upptäcker utvecklaren att klienten bara kunde skicka fem poster per sekund till servern. Det totala antalet 1 000 poster, högst 976 kB data (10 000 * 100 / 1024), tar mer än en halvtimme att skicka till servern.

Analys

Eftersom klienten inte anger TCP_NODELAY alternativet tvingar Nagle-algoritmen TCP-stacken att vänta på en ACK innan den kan skicka ett annat paket på kabeln. Klienten har dock inaktiverat Winsock-buffring genom att ange SO_SNDBUF alternativet till 0. Därför måste 10000 skicka samtal skickas och ACK'ed individuellt. Varje ACK fördröjs 200 ms eftersom följande inträffar på serverns TCP-stack:

  • När servern får ett paket avbryts dess 200 ms långa fördröjningstimer.
  • Servern behöver inte skicka något tillbaka, så ACK kan inte återställas.
  • Klienten skickar inte ett annat paket om inte föregående paket bekräftas.
  • Fördröjningstimern på servern upphör att gälla och ACK skickas tillbaka.

Så här förbättrar du

Det finns två problem med den här designen. Först har vi problemet med fördröjningstimern. Klienten måste kunna skicka två paket till servern inom 200 ms. Eftersom klienten använder Nagle-algoritmen som standard bör den bara använda Winsock-standardbuffring och inte vara inställd SO_SNDBUF på 0. När TCP-stacken har sammanslåt en buffert som är större än MTU (Maximum Transmission Unit) skickas ett fullstort paket omedelbart utan att vänta på ACK från fjärrvärden.

För det andra anropar den här designen en sändning för varje post av så liten storlek. Det är inte effektivt att skicka den här lilla storleken. I det här fallet kanske utvecklaren vill fylla ut varje post till 100 byte och skicka 80 poster i taget från ett klientanrop. För att servern ska veta hur många poster som ska skickas totalt kanske klienten vill starta kommunikationen med ett sidhuvud med korrigeringsstorlek som innehåller antalet poster som ska följas.

Fallstudie 2

Ett Winsock TCP-klientprogram öppnar två anslutningar med ett Winsock TCP-serverprogram som tillhandahåller en tjänst för aktiekurser. Den första anslutningen används som en kommandokanal för att skicka aktiesymbolen till servern. Den andra anslutningen används som en datakanal för att ta emot aktiekursen. När de två anslutningarna har upprättats skickar klienten en aktiesymbol till servern via kommandokanalen och väntar på att aktieofferten ska komma tillbaka via datakanalen. Den skickar nästa begäran om aktiesymbol till servern först efter att den första börsnotering har tagits emot. Klienten och servern anger SO_SNDBUF inte alternativet och TCP_NODELAY .

  • Prestanda

    Under testningen upptäcker utvecklaren att klienten bara kunde få fem citattecken per sekund.

  • Analys

    Den här designen tillåter endast en utestående offertbegäran i taget. Den första aktiesymbolen skickas till servern via kommandokanalen (anslutningen) och ett svar skickas omedelbart tillbaka från servern till klienten via datakanalen (anslutningen). Sedan skickar klienten omedelbart den andra aktiesymbolbegäran och sändningen returneras omedelbart när begärandebufferten i sändningsanropet kopieras till Winsock-kernelbufferten. Klientens TCP-stack kan dock inte skicka begäran från kernelbufferten omedelbart eftersom den första sändningen över kommandokanalen inte har bekräftats ännu. När 200 ms-fördröjningstimern på serverkommandokanalen upphör att gälla, kommer ACK för den första symbolbegäran tillbaka till klienten. Sedan skickas den andra offertbegäran till servern efter att ha försenats med 200 ms. Offerten för den andra aktiesymbolen kommer tillbaka omedelbart via datakanalen eftersom fördröjningstimern på klientdatakanalen för närvarande har upphört att gälla. En ACK för föregående offertsvar tas emot av servern. (Kom ihåg att klienten inte kunde skicka en andra offertbegäran på 200 ms, vilket ger tid för att fördröjningstimern på klienten ska upphöra att gälla och skicka en ACK till servern.) Därför får klienten det andra offertsvaret och kan utfärda en annan offertbegäran, som omfattas av samma cykel.

  • Så här förbättrar du

    Designen för de två anslutningarna (kanalen) är onödig här. Om du bara använder en anslutning för begäran och svar om aktieofferten kan ACK för offertbegäran returneras på offertsvaret och komma tillbaka omedelbart. För att ytterligare förbättra prestandan kan klienten multiplexera flera begäranden om aktiekurser till ett sändningsanrop till servern och servern kan också multiplexera flera offertsvar till ett skicka-anrop till klienten. Om den två enkelriktade kanaldesignen är nödvändig av någon anledning bör båda sidor ange TCP_NODELAY alternativet så att de små paketen kan skickas omedelbart utan att behöva vänta på en ACK för föregående paket.

Rekommendationer

Dessa två fallstudier är fabricerade, men de hjälper till att illustrera några värsta scenarier. När du utformar ett program som omfattar omfattande små datasegment som skickar och recvsbör du överväga följande riktlinjer:

  • Om datasegmenten inte är tidskritiska bör programmet slå samman dem i ett större datablock för att skicka till ett sändningsanrop. Eftersom sändningsbufferten sannolikt kommer att kopieras till Winsock-kernelbufferten bör bufferten inte vara för stor. Något mindre än 8K är effektivt. Så länge Winsock-kerneln får ett block som är större än MTU:t skickar den ut flera fullstora paket och ett sista paket med det som finns kvar. Den sändande sidan, förutom det sista paketet, kommer inte att drabbas av 200 ms fördröjningstimern. Det sista paketet, om det råkar vara ett udda nummerpaket, omfattas fortfarande av algoritmen för fördröjd bekräftelse. Om den sändande slutstacken får ett annat block som är större än MTU:en kan den fortfarande kringgå Nagle-algoritmen.

  • Undvik om möjligt socketanslutningar med enkelriktat dataflöde. Kommunikation via enkelriktade socketar påverkas lättare av Nagle och fördröjda bekräftelsealgoritmer. Om kommunikationen följer en begäran och ett svarsflöde bör du använda en enda socket för att göra båda skickar och recvs så att ACK kan vara piggybacked på svaret.

  • Om alla små datasegment måste skickas omedelbart anger du TCP_NODELAY alternativet på den sändande änden.

  • Såvida du inte vill garantera att ett paket skickas på kabeln när ett sändningsslut anges av Winsock bör du inte ange SO_SNDBUF till noll. I själva verket har standardbufferten på 8K varit heuristiskt fast besluten att fungera bra i de flesta situationer och du bör inte ändra den om du inte har testat att den nya Winsock-buffertinställningen ger bättre prestanda än standardinställningen. Dessutom är inställningen SO_SNDBUF till noll mest fördelaktig för program som utför massöverföring av data. Även då bör du för maximal effektivitet använda den tillsammans med dubbel buffring (mer än en utestående sändning vid en viss tidpunkt) och överlappande I/O.

  • Om dataleveransen inte behöver garanteras använder du UDP.

Referenser

Mer information om fördröjd bekräftelse och Nagle-algoritmen finns i följande:

Braden, R.[1989], RFC 1122, Krav för Internetvärdar - Kommunikationslager, Internet Engineering Task Force.