Zur Implementierung des Android-TCP-Severs wird eine Klasse SimpleTcpServer als Wrapper für die Klasse AsyncTask angelegt. Dies vereinfacht die Ansteuerung. Die Klasse muss im benutzenden Programm abgeleitet werden, um die Methode onMessageArrived (s.u.) mit einem sinnvollen Inhalt überschreiben.
Das API der Klasse stellt zwei Methoden bereit. void start(int port) dient zu Starten des Servers und die Callback-Funktion void onMessageArrived(String ClientIp, String message) übermittelt die Client-IP und den Nahrichteninhalt eine eingehenden Verbindung.
/**
* SimpleTcpServer
* Implementiert einen einfachen TCP-Server
*
* @author Ullis Roboter Seite
* @version 1.0
* @since 2014-08-29
*/
public class SimpleTcpServer {
/**
* Startet den TCP-Server.
*
* @param Port
* Service-Port, unter dem auf eingehende Verbindungen gewartet wird.
*/
public void start(int Port) {
// ...
}
/**
* Rückruffunktion zur Übergabe von IP und Nachrichten-Inhalt bei Eingang
* einer Nachricht.
*
* @param ClientIp
* IP des verbundenen Clients.
* @param Message
* Inhalt der übermittelten Nachricht.
*/
protected void onMessageArrived(String ClientIp, String Message) {
}
}
Die wesentliche Methode des AsyncTask ist doInBackground. Hier wird ein ServerSocket angelegt, das dann in einer Endlos-Schleife auf eine eingehende Verbindunganforderung wartet (tcpServer.accept()). Wird eine Verbindung hergestellt, werden Reader- und Writer-Objekte für den vom Client-Socket bereitgestellten Stream angelegt. Über den Reader wird die übermittelte Nachricht ausgelesen. Über die von AsyncTask bereitgestellte Methode publishProgress kann thread-übergreifend die Client-IP und die Nachricht gemeldet werden. Die Nachricht wird ein wenig modifiziert und als Antwort zurückgesandt. Wichtig ist der Aufruf von flush() um sicher zu stellen, dass die Nachricht unmittelbar versandt wird.
Zum Schluss wird die Verbindung getrennt.
Eine detaillierte Fehlerbehandlung gibt es nicht.
/**
* Stellt einen asynchronen Task zur Annahme von eingehenden
* Verbindungsanforderungen bereit.
*/
class SimpleTcpServerAsyncTask extends AsyncTask<Integer, String, String> {
ServerSocket tcpServer;
Socket client;
// Der Hintergrund-Thread
@Override
protected String doInBackground(Integer... Params) {
int port = Params[0]; // Port-Nummer ist im ersten Element enthalten
// Server starten
try {
tcpServer = new ServerSocket(port);
} catch (IOException e) {
return e.toString();
}
try {
while (true) {
// auf Verbindungsanforderung eines Clients warten
client = tcpServer.accept();
BufferedReader reader = new BufferedReader(new InputStreamReader(client.getInputStream()));
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(client.getOutputStream()));
// auf eine Nachricht des Clients warten
String incomingMsg = reader.readLine();
// Client-IP und Nachricht melden
publishProgress(client.getInetAddress().getHostAddress(), incomingMsg);
// Antwort aufbereiten
String outgoingMsg = prepAnswer(incomingMsg) + System.getProperty("line.separator");
// Antwort senden
writer.write(outgoingMsg);
writer.flush();
// Verbindung trennen
client.close();
} // while
} catch (Exception e) {
return e.toString();
} // try
} // doInBackground
/**
* Wrapper für das 'onProgressUpdate'-Ereignis. Wandelt die generischen
* Parameter in benannte Eigenschaften.
*/
protected void onProgressUpdate(String... Progress) {
onMessageArrived(Progress[0], Progress[1]);
}
}
Die Klasse kann wie folgt genutzt werden:
class SpecialSimpleTcpServer extends SimpleTcpServer {
protected void onMessageArrived(String clientIp, String message) {
// clientIP & message an Steuerelemente übergeben
// z.B. textRemoteClientMsg.setText(message);
}
}
// ...
SpecialSimpleTcpServer specialSimpleTcpServer;
// ...
specialSimpleTcpServer = new SpecialSimpleTcpServer();
specialSimpleTcpServer.start(ServerPort);
Die VB-Implementierung ist sehr ähnlich. Auch hier wurde eine Klasse entworfen, die einen standardisierten Hintergrund-Task kapselt. In .Net ist dies die Klasse BackgroundWorker. Das API besteht wie oben aus der Methode start und –statt der Callback-Funktion– einem Event onMessageArrived zur Übermittlung der Client-IP und Nachricht.
''' <summary>
''' Implementiert einen einfachen TCP-Server.
''' </summary>
Public Class SimpleTcpServer
''' <summary>
''' Event zur Übergabe von IP und Nachrichten-Inhalt bei Eingang einer Nachricht.
''' </summary>
''' <param name="ClientIp">IP des verbundenen Clients.</param>
''' <param name="message">Inhalt der übermittelten Nachricht.</param>
Public Event onMessageArrived(ClientIp As String, message As String)
''' <summary>
''' Startet den TCP-Server.
''' </summary>
''' <param name="port">Service-Port, unter dem auf eingehende Verbindungen gewartet wird.</param>
Public Sub start(port As Integer)
' Thread einrichten
serverThread = New BackgroundWorker
serverThread.WorkerReportsProgress = True
serverThread.RunWorkerAsync(port)
End Sub
End Class
Die Implementierung der Kernfunktion ist analog der Java-Implementierung. Nur das Übermitteln der Verbindungsdaten geschieht ein wenig anders. Hierfür ist beim BackgroundWorker die Methode ReportProgress zuständig. Im ersten Parameter wird eine Angabe zum prozentualen Prozessfortschritt erwartet, im zweiten kann ein beliebiges Objekt übermittelt werden. Um zwei String zu übermitteln, müssen diese also zusammengefasst werden. Dazu dient die Structure State. In der Callback-Funktion serverThread_ProgressChanged von BackgroundWorker wird diese Structure wieder aufgelöst.
Private Sub ServerThread_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles serverThread.DoWork
' Server Starten
Dim tcpServer As New TcpListener(IPAddress.Any, CInt(e.Argument))
tcpServer.Start()
While True
' auf Verbindungsanforderung eines Clients warten
Dim client As TcpClient = tcpServer.AcceptTcpClient
Dim reader As StreamReader = New StreamReader(client.GetStream)
Dim writer As StreamWriter = New StreamWriter(client.GetStream)
'auf eine Nachricht des Clients warten
Dim incomingMsg As String = reader.ReadLine
'Client-IP und Nachricht melden
Dim clientIP = DirectCast(client.Client.RemoteEndPoint, IPEndPoint).Address.ToString
serverThread.ReportProgress(0, New State(clientIP, incomingMsg))
' Antwort aufbereiten
Dim outgoingMsg As String = prepAnswer(incomingMsg)
' Antwort senden
writer.WriteLine(outgoingMsg)
writer.Flush()
'Verbindung trennen
client.Close()
End While
End Sub
Private Structure State
Public ip As String
Public msg As String
Sub New(ip As String, msg As String)
Me.ip = ip
Me.msg = msg
End Sub
End Structure
Private Sub serverThread_ProgressChanged(sender As Object, e As ProgressChangedEventArgs)
Handles serverThread.ProgressChanged
Dim state As State = CType(e.UserState, SimpleTcpServer.State)
RaiseEvent onMessageArrived(state.ip, state.msg)
End Sub