[C#] Laatste block in stream lezen duurt lang

Pagina: 1
Acties:

Acties:
  • 0 Henk 'm!

  • Mnstrspeed
  • Registratie: Oktober 2007
  • Laatst online: 17-09 20:11
(jarig!)
Waarschijnlijk een standaard vraag, maar ik heb tot nu toe nergens een gepaste oplossing kunnen vinden...

Ik ben bezig met het maken van een hele usenet newsreader (voor leerdoeleinden), maar bij het leegslurpen van de NetworkStream waarop de data binnen vloeit loop ik tegen een probleem aan. De 42k bytes die gedownload moeten worden, splits ik op in blokken van 1024 bytes. De eerste 41 iteraties voor het downloaden gaan goed, maar bij het laatste blok wordt de buffer niet helemaal gevuld, waardoor NetworkStream.Read blijft wachten op aanvullende data tot de timeout voorbij is (standaard ~60s). Dit is natuurlijk niet acceptabel bij het downloaden van 300 van deze files ;)

C#:
1
2
3
4
5
6
7
8
9
10
11
byte[] buffer = new byte[1024];
int bytesRead;
int totalBytesRead = 0;

while ((bytesRead = networkStream.Read(buffer, 0, buffer.Length)) > 0)
{
    fileStream.Write(buffer, 0, bytesRead);
    totalBytesRead += bytesRead;

    Console.WriteLine("{0} - {1} bytes so far", DateTime.Now.ToString("HH:mm:ss:fff"), totalBytesRead);
}


met als output:
Connection established
Login successful
15:48:24:699 - 1024 bytes so far
15:48:24:699 - 1460 bytes so far
15:48:24:700 - 2484 bytes so far
[...]
15:48:24:944 - 40880 bytes so far
15:48:24:946 - 41502 bytes so far
15:49:25:602 - 41542 bytes so far
Download 005d4a96$0$8186$c3e8da3@news.astraweb.com complete
Press any key to continue...

(minuut verschil tussen de laatste twee blocks)

Wat heb ik al geprobeerd?
- Ik heb via NetworkStream.DataAvailable gecheckt wanneer er geen data beschikbaar meer is, echter zit je hier met het probleem dat de connectie naar de server traag kan zijn en er misschien 300ms over doet om nieuwe data binnen te halen.
- NetworkStream.ReadTimeOut lager instellen, maar ook hier moet je weer rekening houden met tragere verbindingen, waardoor je alsnog een seconde wacht.

Ik neem toch aan dat er een manier is om te stoppen met het ophalen van data zonder met timeouts te gaan werken?

Mijn excuses voor de onduidelijke titel, maar dit was het beste waar ik op kwam :+

Ehhh wat?


Acties:
  • 0 Henk 'm!

  • Soultaker
  • Registratie: September 2000
  • Laatst online: 16:36
Het fundamentele probleem is dat je gegevens leest uit een stream, terwijl je niet weet hoeveel bytes je kunt verwachten. Ik moet wel zeggen dat ik vind dat die NetworkStream zich raar gedraagt, aangezien 'ie blijkbaar wél blockt, maar níet probeert de buffer maximaal te vullen.

Dat neemt niet weg dat het fundamentele probleem is dat als je meerdere bestanden (of wat voor stukken data dan ook) over één socket inleest, je van te voren moet weten hoe groot die bestanden zijn, anders weet je niet waar het ene bestand begint en het volgende eindigt. FTP lost dit op door aan het eind van het bestand de verbinding te sluiten (dan returnt de read() methode ook meteen, neem ik aan), maar bij dit protocol lijken meerdere bestanden over één verbinding ingelezen te worden. Als je dan blocking I/O wil gebruiken, moet je zorgen dat je bufferruimte overeen komt met hoeveel bytes je verwacht, want het besturingssysteem kan niet zien wanneer het bestand compleet is.

(Overigens zou ik dan zelf ook gewoon die buffer van te voren alloceren in plaats van stukken van 1024 bytes te gaan lezen, en die vervolgens weer te kopiëren.)

Acties:
  • 0 Henk 'm!

  • CodeCaster
  • Registratie: Juni 2003
  • Niet online

CodeCaster

Can I get uhm...

4. The lines of the block MUST be followed by a terminating line
consisting of a single termination octet followed by a CRLF pair
in the normal way. Thus, unless it is empty, a multi-line block
is always terminated with the five octets CRLF "." CRLF
(%x0D.0A.2E.0D.0A).
Dat wordt dus byte-voor-byte lezen, of van tevoren zien te bepalen hoe groot een artikel is. Is vast ook wel op die RFC te lezen ;)

https://oneerlijkewoz.nl
Op papier is hij aan het tekenen, maar in de praktijk...


Acties:
  • 0 Henk 'm!

  • pedorus
  • Registratie: Januari 2008
  • Niet online
CodeCaster schreef op zondag 01 november 2009 @ 17:09:
[...]

Dat wordt dus byte-voor-byte lezen, of van tevoren zien te bepalen hoe groot een artikel is. Is vast ook wel op die RFC te lezen ;)
Dat is natuurlijk erg traag. Lijkt me meer iets voor SocketsFlags.Partial en dan scannen eigenlijk... En misschien is zelfs die Flag niet eens echt nodig, geen ervaring.

Vitamine D tekorten in Nederland | Dodelijk coronaforum gesloten


Acties:
  • 0 Henk 'm!

Verwijderd

Je kan hem natuurlijk ook gewoon opvullen de array tijdens het versturen, zodat het altijd op 1024 uitkomt.

Acties:
  • 0 Henk 'm!

  • Soultaker
  • Registratie: September 2000
  • Laatst online: 16:36
@sanderbosm: ik gok dat de TS geen controle over de server of het gebruikte protocol heeft...

Wat pedorus zegt klinkt zinnig. Ik kan niet vinden wat SocketFlags.Partial (zonder s ;)) precies doet (MSDN beschrijft het weinig behulpzaam als Partial send or receive for message.) maar als Read() daarmee returnt direct als de receive buffer leeg is (maar wel blokkeert tot er minstens één byte gelezen is of de socket gesloten wordt) dan zou dat ideaal zijn.

Het alternatief is zelf zoiets te bouwen door non-blocking socket te gebruiken in combinatie met select. (Geen idee hoe dat precies gaat in .NET maar daar zijn vast wel tutorials over te vinden.)

edit:
Er is trouwens ook gewoon een Socket.Receive() methode. Als je toch zelf gaat parsen kun je die beter gebruiken, vermoed ik. Ik vraag me überhaupt af wat de meerwaarde is van die NetworkStream die blijkbaar ook buffers niet vult (je moet dus zelf alsnog de boel concateneren of in een grotere buffer lezen).

Heeft .NET niet ook een soort BufferedReader zoals Java? Dat lijkt namelijk te zijn wat je wil.

offtopic:
Wat is MSDN toch een onoverzichtelijk, ongeordend zootje zeg. Blegh.

[ Voor 27% gewijzigd door Soultaker op 01-11-2009 18:48 ]


Acties:
  • 0 Henk 'm!

  • Mnstrspeed
  • Registratie: Oktober 2007
  • Laatst online: 17-09 20:11
(jarig!)
Het werkt :D

Er bleek inderdaad een "\r\n.\r\n" aan het einde van de body te staan, dus heb ik het maar zo gedaan dat hij voor ieder block de laatste 5 bytes checkt en dat werkt uitstekend :)

C#:
1
2
3
4
5
6
7
while (...)
{
    ...
    if (buffer.IsEndOfBody(bytesRead))
    break;
    ...
}

En verderop
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
    public static bool IsEndOfBody(this byte[] bytes, int length)
    {
        byte[] endOfBodyBytes = { 13, 10, 46, 13, 10 };

        for (int i = 0; i < endOfBodyBytes.Length; i++)
        {
            if (bytes[length - endOfBodyBytes.Length + i] != endOfBodyBytes[i])
            {
                return false;
            }
        }

        return true;
    }

Ehhh wat?


Acties:
  • 0 Henk 'm!

  • Woy
  • Registratie: April 2000
  • Niet online

Woy

Moderator Devschuur®
Een beetje off-topic maar:
Mnstrspeed schreef op zondag 01 november 2009 @ 20:41:
C#:
1
2
3
4
5
6
7
while (...)
{
    ...
    if (buffer.IsEndOfBody(bytesRead))
    break;
    ...
}
Waarom maak je daar dan niet gewoon een do/while van?
C#:
1
2
3
do
{
} while ( someCondition && !buffer.IsEndOfBody(bytesRead) )

Dat is wat overzichtelijker dan een break midden in je loop.
Mnstrspeed schreef op zondag 01 november 2009 @ 16:17:
C#:
1
Console.WriteLine("{0} - {1} bytes so far", DateTime.Now.ToString("HH:mm:ss:fff"), totalBytesRead);
Dit is ook nogal gek, waarom geef je het formaat van je datum niet gewoon in je format string mee?
C#:
1
Console.WriteLine("{0:HH:mm:ss:fff} - {1} bytes so far", DateTime.Now, totalBytesRead);


overigens vind ik het ook nogal gek om IsEndOfBody (of eigenlijk ContainsEndOfBody) een extension method te maken. Als je straks ook een ander protocol wil implementeren die een EndOfBody heeft, dan ga je naming conflicts krijgen. Ik zou het gewoon een (static) member method maken van de class die verantwoordelijk is voor het NTTP protocol, en de buffer gewoon meegeven.

[ Voor 17% gewijzigd door Woy op 02-11-2009 09:32 ]

“Build a man a fire, and he'll be warm for a day. Set a man on fire, and he'll be warm for the rest of his life.”


Acties:
  • 0 Henk 'm!

  • Haan
  • Registratie: Februari 2004
  • Laatst online: 17:15

Haan

dotnetter

Woy schreef op maandag 02 november 2009 @ 09:04:

Dit is ook nogal gek, waarom geef je het formaat van je datum niet gewoon in je format string mee?
C#:
1
Console.WriteLine("{0:HH:mm:ss:fff} - {1} bytes so far", totalBytesRead);
Ik moet bekennen dat ik bovenstaande constructie ook niet ken, ik ga eens wat code opzoeken waar dat toepasbaar is :)

Ik neem overigens aan dat je dit bedoelt:
C#:
1
Console.WriteLine("{0:HH:mm:ss:fff} - {1} bytes so far", DateTime.Now, totalBytesRead);

Kater? Eerst water, de rest komt later


Acties:
  • 0 Henk 'm!

  • Woy
  • Registratie: April 2000
  • Niet online

Woy

Moderator Devschuur®
Haan schreef op maandag 02 november 2009 @ 09:08:
[...]

Ik moet bekennen dat ik bovenstaande constructie ook niet ken, ik ga eens wat code opzoeken waar dat toepasbaar is :)
Dat is juist de hele kracht van String.Format. In je format string kun je opgeven hoe iets geformateerd moet worden. Als je toch al elke object los gaat formateren, kun je bijna net zo goed strings aan elkaar gaan plakken.
Ik neem overigens aan dat je dit bedoelt:
C#:
1
Console.WriteLine("{0:HH:mm:ss:fff} - {1} bytes so far", DateTime.Now, totalBytesRead);
Ja je hebt gelijk, copy paste foutje.

“Build a man a fire, and he'll be warm for a day. Set a man on fire, and he'll be warm for the rest of his life.”


Acties:
  • 0 Henk 'm!

  • Mnstrspeed
  • Registratie: Oktober 2007
  • Laatst online: 17-09 20:11
(jarig!)
Woy schreef op maandag 02 november 2009 @ 09:04:
Waarom maak je daar dan niet gewoon een do/while van?
C#:
1
2
3
do
{
} while ( someCondition && !buffer.IsEndOfBody(bytesRead) )

Dat is wat overzichtelijker dan een break midden in je loop.
Nog niet eens aan gedacht, inderdaad veel logischer :)
Dit is ook nogal gek, waarom geef je het formaat van je datum niet gewoon in je format string mee?
C#:
1
Console.WriteLine("{0:HH:mm:ss:fff} - {1} bytes so far", DateTime.Now, totalBytesRead);
Nooit geweten dat dit kon :/ Die code is er inmiddels al lang uitgesloopt (was puur om te kijken waar het fout ging), maar he, dit soort dingen komen altijd wel van pas :)
overigens vind ik het ook nogal gek om IsEndOfBody (of eigenlijk ContainsEndOfBody) een extension method te maken. Als je straks ook een ander protocol wil implementeren die een EndOfBody heeft, dan ga je naming conflicts krijgen. Ik zou het gewoon een (static) member method maken van de class die verantwoordelijk is voor het NTTP protocol, en de buffer gewoon meegeven.
Kan je wederom niets dan gelijk geven :P

Toch weer mooi wat erbij geleerd :)

Ehhh wat?

Pagina: 1