PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : c# Asynchrone Socket Programmierung


grakaman
2003-10-24, 22:32:47
Hallo

Ich möchte meine POP3 Komponente nun so modifizieren, dass sie eine Socket mit CallBack Methode benutzt. Bisher habe ich die TcpClient Klasse verwendet, was ja auch ohne Probleme funktionierte. Da ich mich aber mit der Materie etwas beschäftigen will, möchte ich das so abändern. Ich habe mich dazu am Bsp. von der SDK Dokumentation gehalten und versucht das auf meine App zu münzen. Mein Problem ist nun, dass ich einfach keine Daten empfange. Deswegen habe ich so gut wie gar nichts erst mal an Funktion implementiert und möchte es nur erst mal so hinbekommen, dass ich die Daten ausgeben kann. In der Doku stand auch irgend etwas von ManualResetEvent. Das habe ich aber mal auskommentiert, da sonst meine App immer eingefrohren ist und ich auch keine Ahnung habe, was ich damit Anfangen soll.


public class POP3Message
{
// Felder für die Socketverbindung
private string _serverName;
private string _userName;
private string _userPass;
private int _port;
private Socket client;

private static ManualResetEvent receiveDone = new ManualResetEvent(false);

// Feld für die zuletzt empfangenen Daten
public string lastMessage;

public POP3Message(string server, string user, string pass)
{
this._serverName = server;
this._port = 110;
this._userName = user;
this._userPass = pass;
}

// Methode zum Herstellen einer Verbindung zu einem POP3 Server
public void Connect()
{
try
{
// Erstellen des Remote Endpoints für den Socket
IPHostEntry ipHostInfo = Dns.Resolve(this._serverName);
IPAddress ipAddress = ipHostInfo.AddressList[0];
IPEndPoint remoteEP = new IPEndPoint(ipAddress, this._port);

// Erstellen einer TCP Socket
this.client = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp);

// Herstellen der Verbindung
this.client.Connect(remoteEP);

// Beginnen des Datenempfangs
Receive(this.client);

}
catch(Exception ex)
{
throw new POP3Exception("An error occured while connecting to the POP3 server: " + ex.Message);
}
}

// Methode zum Beenden der TCP/IP Verbindung
public void Close()
{
try
{
this.client.Close();
}
catch(Exception ex)
{
throw new POP3Exception("An error occured while closing the connection: " + ex.Message);
}
}

// Methode zum Empfangen der Daten
private void Receive(Socket client)
{
StateObject state = new StateObject();
state.workSocket = client;

// Startet den Datenempfang und ruft die CallBack Methode auf
this.client.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
new AsyncCallback(ReceiveCallback), state);
}

// Asynchrone CallBack Methode
private void ReceiveCallback(IAsyncResult ar)
{
try
{
// Emfangen des State Object
StateObject state = (StateObject) ar.AsyncState;
Socket client = state.workSocket;

// Abrufen der Daten vom POP3 Server
int bytesRead = client.EndReceive(ar);

if(bytesRead > 0)
{
// Wenn Daten vorhanden sind, werden sie im State Objekt gespeichert
state.sb.Append(Encoding.ASCII.GetString(state.buffer, 0, bytesRead));


client.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
new AsyncCallback(ReceiveCallback), state);
}
else
{
// Wenn keine Daten mehr empfangen werden, speicher den String.
if(state.sb.Length > 1)
{
this.lastMessage = state.sb.ToString();
}
//receiveDone.Set();
}
}
catch(Exception ex)
{
throw new POP3Exception("An error occured while processing the CallBack method: " + ex.Message);
}
}

// Methode sendet einen POP3 Befehl an den Server
private void Send(Socket client, String data)
{
try
{
byte[] byteData = Encoding.ASCII.GetBytes(data);
client.Send(byteData);
}
catch(Exception ex)
{
throw new POP3Exception("An error occured while sending a POP3 command: " + ex.Message);
}
}

// Methode authentifiziert einen Benutzer am POP3 Server
public void Login()
{
StringBuilder s = new StringBuilder();
char[] crlf = {(char)13, (char)(10)};

s.Append("USER ");
s.Append(this._userName);
s.Append(crlf);
Send(this.client, s.ToString());
Receive(this.client);
//receiveDone.WaitOne();
}

}


meine Testapp:


Console.WriteLine("Bitte geben Sie den POP3 Server ein");
string server = Console.ReadLine();
Console.WriteLine("Bitte geben Sie den User ein");
string user = Console.ReadLine();
Console.WriteLine("Bitte geben Sie das Passwort ein");
string pass = Console.ReadLine();

POP3Message pop = new POP3Message(server, user, pass);
pop.Connect();
pop.Login();
Console.WriteLine(pop.lastMessage);
pop.Close();


Theoretisch müsste ja irgend etwas ausgegeben werden, da der Server immer mit +OK... oder -Err... antwortet.

MfG

Demirug
2003-10-24, 23:02:02
Ich habe jetzt nur kurz darüber geschaut aber das kann so nicht funktionieren.

Dein Login wartet ja nicht bis Daten eingetroffen sind sondern kehrt sofort zurück. Die nächste Anweisung ist dann ein Close womit der Socket wieder geschlossen wird.

Damit das nicht passiert ist eigentlich das ManualResetEvent zuständig.

Warum du jetzt aber keine Daten bekommst ist mir nicht ganz klar. Wird die ReceiveCallback Methode überhaupt aufgerufen (wenn das Event nicht auskommentiert ist)?

grakaman
2003-10-24, 23:07:32
Original geschrieben von Demirug
Ich habe jetzt nur kurz darüber geschaut aber das kann so nicht funktionieren.

Dein Login wartet ja nicht bis Daten eingetroffen sind sondern kehrt sofort zurück. Die nächste Anweisung ist dann ein Close womit der Socket wieder geschlossen wird.

Damit das nicht passiert ist eigentlich das ManualResetEvent zuständig.

Warum du jetzt aber keine Daten bekommst ist mir nicht ganz klar. Wird die ReceiveCallback Methode überhaupt aufgerufen (wenn das Event nicht auskommentiert ist)?

Mein Login ruft doch aber die Receive Methode auf, die wiederum die CallBack aufruft. Theoretisch müsste doch etwas in lastMessage stehen, tut es aber so nicht. Zumindest wird es nicht angezeigt.

MfG

Demirug
2003-10-24, 23:29:46
Original geschrieben von grakaman
Mein Login ruft doch aber die Receive Methode auf, die wiederum die CallBack aufruft. Theoretisch müsste doch etwas in lastMessage stehen, tut es aber so nicht. Zumindest wird es nicht angezeigt.

MfG

Nein, eben nicht. Die Receive Methode startet ja nur den Datenempfang und kehrt dann sofort zurück. Das ManualResetEvent würde dann diesen Thread aufhalten damit ein anderer Thread die Callback Methode aufrufen kann und lastMessage mit Daten versorgt und den anderen Thread wieder freigibt.

Ich habe aber einen Verdacht warum das ganze nicht funktioniert. Die zugrunde liegende Socketimplementierung kommt mit dem mix aus asyncronen und syncronen aufrufen nicht klar.

grakaman
2003-10-24, 23:39:19
Original geschrieben von Demirug
Nein, eben nicht. Die Receive Methode startet ja nur den Datenempfang und kehrt dann sofort zurück. Das ManualResetEvent würde dann diesen Thread aufhalten damit ein anderer Thread die Callback Methode aufrufen kann und lastMessage mit Daten versorgt und den anderen Thread wieder freigibt.

Ich habe aber einen Verdacht warum das ganze nicht funktioniert. Die zugrunde liegende Socketimplementierung kommt mit dem mix aus asyncronen und syncronen aufrufen nicht klar.

Du meinst also, ich sollte für die anderen Socket Methoden (connect, send) ebenfalls eine CallBack Methode erstellen?

MfG

Demirug
2003-10-24, 23:45:02
Original geschrieben von grakaman
Du meinst also, ich sollte für die anderen Socket Methoden (connect, send) ebenfalls eine CallBack Methode erstellen?

MfG

Ja, im Entwicklerhandbuch gibt es ein Beispiel für einen Client mit Asyncronen Sockets.

Ich kenne das von der Win32 Socketprogrammierung. Dort durfte man einen Socket auch nicht asyncron und syncron gleichzeitig benutzten. In der Regel macht sowas ja auch keinen Sinn.

grakaman
2003-10-25, 00:05:28
Original geschrieben von Demirug
Ja, im Entwicklerhandbuch gibt es ein Beispiel für einen Client mit Asyncronen Sockets.

Ich kenne das von der Win32 Socketprogrammierung. Dort durfte man einen Socket auch nicht asyncron und syncron gleichzeitig benutzten. In der Regel macht sowas ja auch keinen Sinn.

Ich hab das aber auch schon woanders gesehen und da hat man dann aber mit Threads rumhantiert. Nur so recht kenne ich mich noch nicht damit aus. Ich versuche morgen erst einmal alles asynchron zu gestalten.

MfG

grakaman
2003-10-25, 16:38:18
Es geht leider immer noch nicht. Wenn ich jetzt das Passwort eingebe, bleibt mein Programm einfach hängen.
Hier der abgeänderte Code:


public class POP3Message
{
// Felder für die Socketverbindung
private string _serverName;
private string _userName;
private string _userPass;
private int _port;
private Socket client;

// Events für den Status der Asynchronen Socket
private static ManualResetEvent connectDone = new ManualResetEvent(false);
private static ManualResetEvent sendDone = new ManualResetEvent(false);
private static ManualResetEvent receiveDone = new ManualResetEvent(false);

// Feld für die zuletzt empfangenen Daten
public string lastMessage;

public POP3Message(string server, string user, string pass)
{
this._serverName = server;
this._port = 110;
this._userName = user;
this._userPass = pass;
}

// Methode zum Herstellen einer Verbindung zu einem POP3 Server
public void Connect()
{
try
{
// Erstellen des Remote Endpoints für den Socket
IPHostEntry ipHostInfo = Dns.Resolve(this._serverName);
IPAddress ipAddress = ipHostInfo.AddressList[0];
IPEndPoint remoteEP = new IPEndPoint(ipAddress, this._port);

// Erstellen einer TCP Socket
this.client = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp);

// Verbinden zum Remote Endpoint und starten der Callback Methode
client.BeginConnect(remoteEP, new AsyncCallback(ConnectCallback), client);
connectDone.WaitOne();

// Beginnen des Datenempfangs
Receive(this.client);
receiveDone.WaitOne();

}
catch(Exception ex)
{
throw new POP3Exception("An error occured while connecting to the POP3 server: " + ex.Message);
}
}

// Asynchrone Callback Methode für die Verbindung zum Remote Endpoint
private void ConnectCallback(IAsyncResult ar)
{
try
{
// Emfangen des State Object
Socket client = (Socket) ar.AsyncState;

// Herstellen der Verbindung
client.EndConnect(ar);

// Event signalisiert dass die Verbindung aufgebaut ist.
connectDone.Set();
}
catch(Exception ex)
{
throw new POP3Exception("An error occured while connecting to the POP3 server: " + ex.Message);
}
}

// Methode zum Beenden der TCP/IP Verbindung
public void Close()
{
try
{
this.client.Close();
}
catch(Exception ex)
{
throw new POP3Exception("An error occured while closing the connection: " + ex.Message);
}
}

// Methode sendet ein POP3 Befehl an den Server
private void Send(Socket client, String data)
{
try
{
byte[] byteData = Encoding.ASCII.GetBytes(data);

// Startet das Senden der Daten und ruft die Callback Methode auf
client.BeginSend(byteData, 0, byteData.Length, 0,
new AsyncCallback(SendCallback), client);
}
catch(Exception ex)
{
throw new POP3Exception("An error occured while sending a POP3 command: " + ex.Message);
}
}

// Asynchrone CallBack Methode zum Senden der Daten
private void SendCallback(IAsyncResult ar)
{
try
{
// Emfangen des State Object
Socket client = (Socket) ar.AsyncState;

// Beendet das Senden der Daten
int bytesSent = client.EndSend(ar);

// Event signalisiert dass die Daten gesendet sind.
sendDone.Set();
}
catch(Exception ex)
{
throw new POP3Exception("An error occured while sending a POP3 command: " + ex.Message);
}
}

// Methode zum Empfangen der Daten
private void Receive(Socket client)
{
StateObject state = new StateObject();
state.workSocket = client;

// Startet den Datenempfang und ruft die CallBack Methode auf
this.client.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
new AsyncCallback(ReceiveCallback), state);
}

// Asynchrone CallBack Methode zum Empfangen der Daten
private void ReceiveCallback(IAsyncResult ar)
{
try
{
// Emfangen des State Object
StateObject state = (StateObject) ar.AsyncState;
Socket client = state.workSocket;

// Abrufen der Daten vom POP3 Server
int bytesRead = client.EndReceive(ar);

if(bytesRead > 0)
{
// Wenn Daten vorhanden sind, werden sie im State Objekt gespeichert
state.sb.Append(Encoding.ASCII.GetString(state.buffer, 0, bytesRead));


client.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
new AsyncCallback(ReceiveCallback), state);
}
else
{
// Wenn keine Daten mehr empfangen werden, speicher den String.
if(state.sb.Length > 1)
{
this.lastMessage = state.sb.ToString();
}

// Event signalisiert dass die Daten empfangen wurden.
receiveDone.Set();
}
}
catch(Exception ex)
{
throw new POP3Exception("An error occured while processing the CallBack method: " + ex.Message);
}
}

// Methode authentifiziert einen Benutzer am POP3 Server
public void Login()
{
StringBuilder s = new StringBuilder();
char[] crlf = {(char)13, (char)(10)};

s.Append("USER ");
s.Append(this._userName);
s.Append(crlf);
Send(this.client, s.ToString());
Receive(this.client);
receiveDone.WaitOne();
}

}

Demirug
2003-10-25, 17:21:49
Hast du mal ein paar Breakpoints reingesetzt? Oder fügen mal ein paar Console.Writeline ein.

grakaman
2003-10-25, 17:57:35
Ich habe mal ein Console.WriteLine in den CallBack Methoden gesetzt, einmal vor und dann nach dem ManualResetEvent. In der Connect Callback wird beides ohne Probleme angezeigt. In der Receive Callback wird Console.WriteLine angezeigt, wenn ich es ganz am Anfang schreibe, im If Block oder ganz am Ende. Wenn ich Console.WriteLine aber im Else Block schreibe (egal ob vor oder nach ManualResetEvent), wird nichts angezeigt. Das ist aber imho verdächtig, da er ja immer in den Else Block gehen müsste. In der SendCallback wird hingegen gar nichts angezeigt.

MfG

grakaman
2003-10-29, 23:07:12
Hab mich erst einmal genau über Sockets aufklären lassen und das Problem gelöst. Mein vorheriges Bsp. kann nicht funktionieren, da bytesRead nur 0 wird, wenn die Socket geschlossen ist, nicht aber wenn keine Daten mehr empfangen werden. Und die BufferSize im State Objekt gibt lediglich die maximale Größe der empfangenen Daten an. Die können also vollkommen fragmentiert ankommen. Außerdem hab ich da noch receiveDone.Reset() vergessen, was den Thread wieder in den unsignalisierten Status setzt. Hier noch mal der veränderte Code:


public class POP3Message
{
// Felder für die Socketverbindung
private string _serverName;
private string _userName;
private string _userPass;
private int _port;
private Socket client;

// Event für den Status der Asynchronen Socket
private static ManualResetEvent receiveDone = new ManualResetEvent(false);

// Feld für die zuletzt empfangenen Daten
public string lastMessage;

// Status der POP3 Session
private ConnectionState state;

// Anzahl der Bytes der aktuellen Mail
public int sizeMessage;
public int aktSizeMessage;


public POP3Message(string server, string user, string pass)
{
this._serverName = server;
this._port = 110;
this._userName = user;
this._userPass = pass;
}

// Methode zum Herstellen einer Verbindung zu einem POP3 Server
public void Connect()
{
try
{
// Erstellen des Remote Endpoints für den Socket
IPHostEntry ipHostInfo = Dns.Resolve(this._serverName);
IPAddress ipAddress = ipHostInfo.AddressList[0];
IPEndPoint remoteEP = new IPEndPoint(ipAddress, this._port);

// Erstellen einer TCP Socket
this.client = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp);

// Stellt Verbindung zum Remote Endpoint her
this.client.Connect(remoteEP);

// Beginnen des Datenempfangs
this.state = ConnectionState.INIT;
Receive(this.client);
receiveDone.WaitOne();
receiveDone.Reset();

}
catch(Exception ex)
{
throw new POP3Exception("An error occured while connecting to the POP3 server: " + ex.Message);
}
}

// Methode zum Beenden der TCP/IP Verbindung
public void Close()
{
try
{
this.client.Close();
}
catch(Exception ex)
{
throw new POP3Exception("An error occured while closing the connection: " + ex.Message);
}
}

// Methode sendet ein POP3 Befehl an den Server
private void Send(Socket client, String data)
{
try
{
byte[] byteData = Encoding.ASCII.GetBytes(data);

// Sendet Daten and den POP3 Server
this.client.Send(byteData);
}
catch(Exception ex)
{
throw new POP3Exception("An error occured while sending a POP3 command: " + ex.Message);
}
}

// Methode zum Empfangen der Daten
private void Receive(Socket client)
{
StateObject state = new StateObject();
state.workSocket = client;

// Startet den Datenempfang und ruft die CallBack Methode auf
this.client.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
new AsyncCallback(ReceiveCallback), state);
}

// Asynchrone CallBack Methode zum Empfangen der Daten
private void ReceiveCallback(IAsyncResult ar)
{
try
{
// Emfangen des State Object
StateObject state = (StateObject) ar.AsyncState;
Socket client = state.workSocket;

// Abrufen der Daten vom POP3 Server
int bytesRead = client.EndReceive(ar);

if(bytesRead > 0)
{
// Wenn Daten vorhanden sind, werden sie im State Objekt gespeichert
string aktString = Encoding.ASCII.GetString(state.buffer, 0, bytesRead);
state.sb.Append(aktString);

if(state.sb.Length > 1)
{
this.lastMessage = state.sb.ToString();
}

if(this.state == ConnectionState.RETR)
{
Console.WriteLine(aktString);
if(!aktString.StartsWith("+OK"))
{
this.aktSizeMessage += aktString.Length;
}

if(this.sizeMessage == (this.aktSizeMessage))
{
receiveDone.Set();
}
else
{
this.client.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
new AsyncCallback(ReceiveCallback), state);
}
}
else
{
receiveDone.Set();
}

}

}
catch(Exception ex)
{
throw new POP3Exception("An error occured while processing the CallBack method: " + ex.Message);
}
}

// Methode authentifiziert einen Benutzer am POP3 Server
public void Login()
{
StringBuilder s = new StringBuilder();
char[] crlf = {(char)13, (char)(10)};

this.state = ConnectionState.USER;
s.Append("USER ");
s.Append(this._userName);
s.Append(crlf);
Send(this.client, s.ToString());
Receive(this.client);
receiveDone.WaitOne();
receiveDone.Reset();

this.state = ConnectionState.PASS;
s = new StringBuilder();
s.Append("PASS ");
s.Append(this._userPass);
s.Append(crlf);
Send(this.client, s.ToString());
Receive(this.client);
receiveDone.WaitOne();
receiveDone.Reset();
}

public MailInfo Status()
{
StringBuilder s = new StringBuilder();
char[] crlf = {(char)13, (char)(10)};

this.state = ConnectionState.STAT;
s.Append("STAT");
s.Append(crlf);
Send(this.client, s.ToString());
Receive(this.client);
receiveDone.WaitOne();
receiveDone.Reset();

char[] seperator={(char)32};
string[] split= this.lastMessage.Split(seperator);

int numMessages = 0;
int sizeMessages = 0;

if(split.Length > 1)
{
numMessages = System.Convert.ToInt32(split[1].Trim().ToString());
sizeMessages = System.Convert.ToInt32(split[2].Trim().ToString());
}

MailInfo info = new MailInfo(numMessages, sizeMessages);
return info;
}

public void Message(int i)
{
StringBuilder s = new StringBuilder();
char[] crlf = {(char)13, (char)(10)};

this.state = ConnectionState.LIST;
s.Append("List ");
s.Append(i.ToString());
s.Append(crlf);
Send(this.client, s.ToString());
Receive(this.client);
receiveDone.WaitOne();
receiveDone.Reset();

char[] seperator={(char)32};
string[] split= this.lastMessage.Split(seperator);

if(split.Length > 1)
{
this.sizeMessage = System.Convert.ToInt32(split[2].Trim().ToString());
}

s = new StringBuilder();
this.state = ConnectionState.RETR;
s.Append("RETR ");
s.Append(i.ToString());
s.Append(crlf);
Send(this.client, s.ToString());
Receive(this.client);
receiveDone.WaitOne();
receiveDone.Reset();

}

}