Häufig wird mir die Frage gestellt wie man per Sockets mit Standardservern wie POP3 kommuniziert. Aus diesem Grund habe ich ein einfaches Beispiel erzeugt welches über den TCPClient mit einem POP3 Server kommuniziert und E-Mails vom POP3 Server abruft.
POP3 ist ein Internetstandardprotokoll welches mit einem ganz geringem ASCII Befehlssatz auskommt wobei die Befehle über das TCP Protokoll versendet werden.
Um diese Befehle nicht jedes Mal eintippen zu müssen habe ich eine innere Klasse in der POP3 Klasse welche den Minimum Befehlssatz des POP3 Protokolls darstellt:
private class Commands { #region "datamember" public const string Delete = "DELE "; public const string GetMessage = "RETR "; public const string List = "LIST\r\n"; public const string Password = "PASS "; public const string Quit = "QUIT\r\n"; public const string ServerConfirm = "+OK"; public const string ServerNoMoreData = "."; public const string User = "USER "; #endregion #region "ctor" private Commands() { } #endregion }; |
In der Klasse Pop3Client müssen als erstes die zwei wichtigen Instanzvariablen
private TcpClient m_client = new TcpClient();
private NetworkStream m_stream;
deklariert werden. m_client ist unsere TCP Verbindung zum POP3 Server. Ob man den TcpClient gleich instanziert ist Geschmackssache. m_stream ist der Datenstrom welcher vom POP3 Server hin und zurück gesendet wird.
Als nächstes benötigen wir Funktionen die uns die binären Daten in ASCII Format für die Übertragung umwandeln:
private void Send(string message ){ // Send the command in ASCII format. byte[] messagebytes = Encoding.ASCII.GetBytes(message); this.m_stream.Write(messagebytes, 0, messagebytes.Length); } private string GetResponse(){ string text3 = string.Empty; char ch1; System.Text.StringBuilder strBld = new StringBuilder(); do { ch1 = System.Convert.ToChar(this.m_stream.ReadByte()); strBld.Append(ch1.ToString()); } while (String.Compare(ch1.ToString(), "\r", false, CultureInfo.CurrentCulture) != 0); char[] chArray1 = new char[2] { '\r', '\n' }; text3 = strBld.ToString(); text3 = text3.Trim(chArray1); return text3; } |
Der nächste Schritt ist nun die Verbindung zum POP3 Server herzustellen und wieder zu schließen:
public void Connect(string serverName, string userName, string password) { if (this.m_connected) { this.Disconnect(); } try { this.m_client.Connect(serverName, 110); this.m_stream = this.m_client.GetStream(); this.CheckResponse(this.GetResponse()); this.Send(Commands.User + userName + "\r\n"); this.CheckResponse(this.GetResponse()); this.Send(Commands.Password + password + "\r\n"); this.CheckResponse(this.GetResponse()); this.m_connected = true; } catch (Exception e) { this.Disconnect(); throw new ApplicationException(e.Message, e); } } |
Bitte beachten Sie hier dass uns der POP3 Server uns ständig Rückantworten zurückgibt, aus diesem Grund wird nachdem der Client über die m_client.Connect("", int) Methode verbunden wird der Datenstrom ausgelesen und überprüft ( this.CheckResponse(string) )ob der Server die Anfrage korrekt verarbeiten konnte. Außerdem muss ich darauf hinweisen, dass dies ein einfaches Beispiel ist, bei manchen POP3 Servern ist es Pflicht das Passwort verschlüsselt zu senden.
public void Disconnect() { if (this.m_connected) { this.Send(Commands.Quit); this.CheckResponse(this.GetResponse()); this.m_connected = false; this.m_client.Close(); } }
private void CheckResponse(string response) { if (String.Compare(response.Substring(0, 3), Commands.ServerConfirm, false, CultureInfo.CurrentCulture) != 0) { this.m_client.Close(); this.m_connected = false; throw new ApplicationException("Response " + response + " not expected."); } }
|
Nun folgt die eigentliche Arbeit. Wir benötigen eine Methode die uns das Postfach ausliest, d.h. die Anzahl der Nachrichten ausliest. Standardmäßig wird das im POP3 Protokoll so definiert das die Nachricht mit einer MessageId versehen ist mit der später die Nachricht vom Server geholt wird. Außerdem bekommen wir noch die Größe der Nachricht zurück. Diese Metainformationen habe ich in einer eigenen Klasse MessageHeader implementiert, welche nun verwendet wird:
public MessageHeader[] GetMessageList() { string text1 = string.Empty; if (!this.m_connected) { throw new InvalidOperationException("Not connected."); } this.Send(Commands.List); this.CheckResponse(this.GetResponse()); ArrayList list1 = new ArrayList(); while (true) { text1 = this.GetResponse(); if (string.Compare(text1, Commands.ServerNoMoreData, false, CultureInfo.CurrentCulture) == 0)
{ return (MessageHeader[])list1.ToArray(typeof(MessageHeader)); } else { string[] textArray1 = text1.Split(new char[0]); MessageHeader header1 = new MessageHeader((int)Math.Round(double.Parse(textArray1[0],
NumberFormatInfo.CurrentInfo)),
(int)Math.Round(Double.Parse(textArray1[1], NumberFormatInfo.CurrentInfo))); list1.Add(header1); } } }
|
Nun müssen wir nur noch die Methode implementieren die eine bestimmte Nachricht vom POP3 Server liest. Dies wird wie bereits oben erwähnt über eine NachrichtenId ermöglicht.
public string GetMessageContent(int messageNumber) { System.Text.StringBuilder strBldContent = new StringBuilder(); if (!this.m_connected) { throw new InvalidOperationException("Not connected."); } this.Send(Commands.GetMessage + messageNumber.ToString("G", NumberFormatInfo.CurrentInfo) + "\r\n"); this.CheckResponse(this.GetResponse()); string text1 = string.Empty; while (true) { text1 = this.GetResponse(); if (string.Compare(text1, Commands.ServerNoMoreData, false, CultureInfo.CurrentCulture) ==
0) { return strBldContent.ToString(); } else { strBldContent.Append(text1); strBldContent.Append("\r\n"); } } }
|
Wir lesen nun aus dem Datenstrom solange aus, bis der Server uns das OK gibt das nichts mehr nachkommt.
Normalerweise kann man in modernen POP3 Clients einstellen ob die Nachrichten nach dem Abholvorgang vom Server gelöscht werden sollen. Diese Funktionalität ist hier implementiert:
public void DeleteMessage(int messageNumber) { if (!this.m_connected) { throw new InvalidOperationException("Not connected."); } this.Send(Commands.Delete + messageNumber.ToString("G", NumberFormatInfo.CurrentInfo) + "\r\n"); this.CheckResponse(this.GetResponse()); } |
Was wir nun nur noch brauchen ist ein kleines Client Programm welche unsere POP3 Klasse verwendet.
Imports Tu.Net.Pop3 Public Class Form1 '... Private m_pop3client As New tu.net.pop3.Pop3Client
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
End Sub
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click Try Me.m_pop3client.Connect(Me.txtServername.Text, Me.txtUid.Text, Me.txtPwd.Text) 'retrieve a list of all message Dim Messages() As MessageHeader = Me.m_pop3client.GetMessageList() Me.lblMessage.Text = Messages.Length().ToString() & " messages. You're still connected. Move your mouse over a
message id to read the message."
Dim Message As MessageHeader For Each Message In Messages Dim strSubItem() As String = {Message.Number.ToString(), Message.Size.ToString()} Dim myItem As New ListViewItem(strSubItem) Me.ListView1.Items.Add(myItem) Next Catch ex As Exception MessageBox.Show(ex.ToString()) End Try
End Sub
Private Sub ListView1_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles
ListView1.SelectedIndexChanged If (Me.ListView1.SelectedIndices.Count <> 0) Then Dim messageId As Integer = Integer.Parse(Me.ListView1.SelectedItems(0).SubItems(0).Text) Dim strMessage As String = Me.m_pop3client.GetMessageContent(messageId) Me.RichTextBox1.Text = strMessage End If End Sub
Private Sub Form1_FormClosing(ByVal sender As System.Object, ByVal e As System.Windows.Forms.FormClosingEventArgs)
Handles MyBase.FormClosing If (Me.m_pop3client.Connected) Then Me.m_pop3client.Disconnect() Me.m_pop3client.Dispose() End If End Sub '... End Class |