[C#] NetworkStream asynchroon uitlezen

Pagina: 1
Acties:

  • maikel
  • Registratie: Januari 2001
  • Laatst online: 20-08 09:32
Ik ben nu met een programmaatje bezig dat kan communiceren met zowel een POP3- als een SMTP-server. Daarvoor heb ik zelf een connection-class gebouwd die een verbinding opzet en commando's kan versturen.
De data die hij ontvangt wil ik dan weer (asynchroon) uitlezen.

Ik ben al zo ver dat ie netjes de CallBack-functie aanroept:
C#:
1
2
3
4
5
6
client                  = new TcpClient();
client.Connect(server, port);
NetworkStream stream      = client.GetStream();
client.ReceiveTimeout    = timeOut;
client.SendTimeout      = timeOut;
stream.BeginRead(new byte[0], 0, 0, new AsyncCallback(Read), 0);


en
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private void Read(IAsyncResult result)
{
    NetworkStream stream = client.GetStream();
    result.AsyncWaitHandle.WaitOne(timeOut, true);
    if (result.IsCompleted)
    {
        System.Collections.ArrayList bytes = new System.Collections.ArrayList();
        while (stream.DataAvailable)
        {
            bytes.Add((byte)stream.ReadByte());
        }
        string line = System.Text.Encoding.ASCII.GetString((byte[])bytes.ToArray(typeof(byte)));
        stream.EndRead(result);
        if (line.Length > 0)
        {
            OnMessageReceived(new MessageReceivedEventArgs(line));
        }
    }
}


Dit werkt perfect als ik met een POP3-server connect, maar als ik probeer om gegevens te versturen naar een SMTP-server gaat het op een gegeven moment fout. Ik krijg dan een heel vaag bericht:
- de helft van het verwachtte bericht
- deel van een eerder bericht er voor
- letters allemaal door elkaar

Waar kan dit aan liggen?
Ik heb m'n code ook al eens omgebouwd zodat ie op de code uit het help-voorbeeld lijkt, maar toen kreeg ik alleen een string van "\0\0\0..." (even lang als m'n buffersize) terug.

Op Internet komen ze ook steeds met diezelfde oplossing aan of doen ze het gewoon synchroon, dus daar kom ik ook niet verder mee.

edit:

PS. Graag geen hele discussies over "waarom gebruik je niet gewoon synchroon?" of "gebruik gewoon een bestaand component daarvoor", etc.

[ Voor 8% gewijzigd door maikel op 28-09-2003 13:56 ]


  • Prozaq
  • Registratie: Juni 2000
  • Laatst online: 21-08 14:04
dit lijkt mij niet goed

stream.BeginRead(new byte[0], 0, 0, new AsyncCallback(Read), 0);

Op deze manier schrijft de stream zijn ingelezen data weg naar een buffer die nergens meer te benaderen is. je leest data in die meteen verloren gaat. Gebruik dus een variabele en niet die new byte[0] wat overigens ook nog eens een te kleine buffer is.

Even later ga je weer rustig synchroon je data inlezen met stream.ReadByte. Dit is vreemd omdat de ingelezen data al in die buffer staat (eerste parameter van beginread).

  • maikel
  • Registratie: Januari 2001
  • Laatst online: 20-08 09:32
Maar waarom gaat dat dan wel altijd goed als ik met mijn POP3-server connect ?

Overigens is dat zo ook gedaan in het help-voorbeeld, maar dan met een buffersize van 1024 en dat werkte helemaal niet.

En 'stream.ReadByte()' is volgens mij niet per definitie synchroon hoor: ik lees die byte pas als er data aangeboden wordt (in de callback-functie dus), niet meteen na het versturen van mijn commando.

  • Woy
  • Registratie: April 2000
  • Niet online

Woy

Moderator Devschuur®
Je moet inderdaad je buffer groter initialiseren. Als je dit doet dan wordt de data die de Reader inleest in deze buffer opgeslagen. Als dan jouw Callback functie wordt aangeroepen moet je deze data uit deze buffer lezen en niet via de ReadByte() methode lijkt mij ( de data is namelijk al uit de stream gelezen voordat jouw functie aangeroepen wordt ).
Het gaat nou waarschijnlijk wel goed omdat je een buffer van lengte 0 hebt en die zit natuurlijk gewoon meteen vol en dan begin jij met het synchroon lezen van data.
Ook vindt ik het nogal vreemd dat je elke byte appart in een ArrayList zet. Dit lijkt me nou niet echt de snelste manier als je grote hoeveelheden data binnen krijgt.

[ Voor 7% gewijzigd door Woy op 29-09-2003 09:18 ]

“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.”


  • maikel
  • Registratie: Januari 2001
  • Laatst online: 20-08 09:32
Ik heb dus ook al geprobeerd om het volgens het help-voorbeeld te doen (met een buffer van 1024 bijv.), maar toen kreeg ik een string met enkel '\0' erin.
Waar kan dat dan aan liggen?

(ik kan het hier nu niet wijzigen/testen)

In het help-voorbeeld riepen ze inderdaad steeds weer 'stream.BeginRead()' aan in een while-loop. Dat heb ik nu dus niet, dus dat moet ik nog aanpassen.
Maar hoe kom ik er dan achter wanneer het bericht van de server ophoudt?
Het is theoretisch gezien mogelijk dat ie inmiddels al weer een nieuw bericht stuurt.
Door een '\0' op het eind?

[ Voor 48% gewijzigd door maikel op 29-09-2003 10:54 ]


  • farlane
  • Registratie: Maart 2000
  • Laatst online: 18:33
maikel schreef op 29 September 2003 @ 10:36:
Maar hoe kom ik er dan achter wanneer het bericht van de server ophoudt?
Door (een) eindkarakter(s), door een lengteveld in het protocol of door een timeout.

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.


  • Woy
  • Registratie: April 2000
  • Niet online

Woy

Moderator Devschuur®
Volgens mij zou je het meer zo moeten doen.
C#:
1
2
3
4
5
6
7
client = new TcpClient();
client.Connect(server, port);
NetworkStream stream      = client.GetStream();
client.ReceiveTimeout    = timeOut;
client.SendTimeout      = timeOut;
byte[] buffer = new byte[ bufferSize ];
stream.BeginRead( buffer , 0, buffer.Length , new AsyncCallback(Read), 0);


en
C#:
1
2
3
4
5
private void Read(IAsyncResult result)
{
    string line = System.Text.Encoding.ASCII.GetString( buffer );
    stream.EndRead(result);
}


En dan natuurlijk zorgen dat je je buffer in je Read methode kan benaderen door middel van een propertie of member.
Deze code is natuurlijk nog niet compleet maar het gaat even om het idee.

“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.”


  • maikel
  • Registratie: Januari 2001
  • Laatst online: 20-08 09:32
@farlane:
SMTP / POP3 hebben geen lengteveld en op een timeout wachten is niet echt handig: dan duurt het altijd de tijd van de timeout. En het eindkarakter is toch de '\0' ?

@rwb:
Zoals jij nu aangeeft was inderdaad wat ik vanavond wilde gaan proberen. Maar dan in de Read-method nog wel een nieuwe 'BeginRead()', anders lees ik misschien niet alle data omdat de buffer niet groot genoeg is bijv. (werd in het voorbeeld ook zo gedaan).

Ik hoop dat het nu wel gaat werken, want met het voorbeeld uit de help kreeg ik alleen maar '\0'-en in m'n buffer. :?

  • Woy
  • Registratie: April 2000
  • Niet online

Woy

Moderator Devschuur®
maikel schreef op 29 september 2003 @ 13:45:
@farlane:
SMTP / POP3 hebben geen lengteveld en op een timeout wachten is niet echt handig: dan duurt het altijd de tijd van de timeout. En het eindkarakter is toch de '\0' ?

@rwb:
Zoals jij nu aangeeft was inderdaad wat ik vanavond wilde gaan proberen. Maar dan in de Read-method nog wel een nieuwe 'BeginRead()', anders lees ik misschien niet alle data omdat de buffer niet groot genoeg is bijv. (werd in het voorbeeld ook zo gedaan).

Ik hoop dat het nu wel gaat werken, want met het voorbeeld uit de help kreeg ik alleen maar '\0'-en in m'n buffer. :?
Hoe je het einde van een SMTP of POP3 stream kan bepalen kun je gewoon in de rfc van het protocol terug vinden. Het zou inderdaad best kunnen dat dit een \0 is. Dat van die BeginRead() erbij is natuurlijk logisch maar dat zei ik ook al in mij post dat het niet compleet was maar het gaat om het idee.

“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.”


  • maikel
  • Registratie: Januari 2001
  • Laatst online: 20-08 09:32
OK, dan zijn we 't daar over eens. :)
Ik zal het vanavond eens proberen.

---------------------------------
Later.....


Nu doet ie het !
Er stond dus een fout in het voorbeeld in de help:
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public static void myReadCallBack(IAsyncResult ar ){

    NetworkStream myNetworkStream = (NetworkStream)ar.AsyncState;
    byte[] myReadBuffer = new byte[1024];
    String myCompleteMessage = "";
    int numberOfBytesRead;

    numberOfBytesRead = myNetworkStream.EndRead(ar);
    myCompleteMessage = 
        String.Concat(myCompleteMessage, Encoding.ASCII.GetString(myReadBuffer, 0, numberOfBytesRead));    
    
    // message received may be larger than buffer size so loop through until you have it all.
    while(myNetworkStream.DataAvailable){
        
        myNetworkStream.BeginRead(myReadBuffer, 0, myReadBuffer.Length, 
                                                   new AsyncCallback(NetworkStream_ASync_Send_Receive.myReadCallBack), 
                                                   myNetworkStream);  

    }

    // Print out the received message to the console.
    Console.WriteLine("You received the following message : " +
                                myCompleteMessage);
}

Hier doen ze eerst "byte[] myReadBuffer = new byte[1024];" voordat ze 'm uitlezen. Dan is ie uiteraard weer leeg!
Ik heb 'm nu als globale variabele gedefinieerd en nu werkt ie goed!

Thnx!

[ Voor 91% gewijzigd door maikel op 29-09-2003 22:36 ]


  • Prozaq
  • Registratie: Juni 2000
  • Laatst online: 21-08 14:04
Volgens mij geeft een punt (dus: .) het einde weer van een bericht wanneer je met pop3 een bericht ophaalt.
Pagina: 1