Motivation

MIT App Inventor ist eine kostenlose, webbasierte Entwicklungsumgebung, mit der besonders Einsteiger und Schüler durch visuelle "Drag-and-Drop"-Programmierung (Block-Code) eigene Android-Apps erstellen können. Die Erweiterung UrsTcpClient ermöglicht es, Nachrichten (Texte) via TCP zu senden und zu empfangen. Was fehlte war ein (Windows-) Konsolenprogramm, das in der Lage ist, mit dieser Erweiterung zu kommunizieren und dabei die über eine einfache TCP-Kommunikation hinausgehenden Eigenschaften zu nutzen.


Version Anpassungen
1.0 (2026-04-26) Initiale Version

In­halts­ver­zeich­nis

Download

Verwendung

TCP-Server

TCP-Client

Implementierungshinweise

Steuerelemente (Controls)

Arbeitsklassen

Eingehende Verbindungsanforderung

Eigehende Daten

Verbindungstest

Download

Das ZIP-Archiv TCPConsole.zip zum Download. Das Archiv enthält das Visual Studio Projekt mit dem Quellcode und ein übersetztes Binary. Zum Aufruf des Binaries ist das Net 9.0 Framework notwendig. Der Download des Frameworks wird angefordert, wenn es noch nicht installiert ist.

Verwendung

TCP-Server

Das Programm startet mit dem Dialog für die Einstellungen des Servers:

Programmstart

Auf der linken Seite muss der Port angegeben werden, über den sich TCP-Clients mit dem Server verbinden können. Die Voreinstellung ist 8010. Wenn der gewünschte Port angegeben wurde, kann der Server über die Schaltfläche Start gestartet werden.

Eingehende Verbindungsanforderungen werden im Feld Log protokolliert:

Serverprotokoll

Auf der rechten Seite befinden sich Voreistellungen für die Kommunikation mit dem dem neu verbundenen TCP-Client:

TCP-Client

Wenn eine Verbindungsanforderung eintrifft, wird für jeden Client ein neues Fenster geöffnet:

TCP Client Einstellungen

Die Voreinstellung für die Kommunikation sind mit den im Server-Fenster gemachten Einstellungen vorbelegt, können aber geändert werden. Zum Start die Kommunikation muss die Schaltfläche Start angeklickt werden. Danach werden die Elemente zur Einstellung deaktiviert und die zur Kommunikation aktiviert. Wurde im Server-Fenster die Option Start Client directly angehakt, startet die Kommunikation mit dem Client automatisch.

TCP Client Kommunikation

Im Bereich Transmit gibt es ein Textfeld, in das die zu senden Zeichenkette eingegeben werden kann. Der Versand erfolgt nach Anklicken der Schaltfläche daneben, in der angezeigt wird, welche Zeichen an den zu sendenden Text angehängt werden. In diesem Fall ist dies die Zeichenkombination CRLF (s.o.). Der Versand kann alternativ durch das Drücken der Enter-Taste ausgelöst werden.

Auf der linken Seite wird die Übertragung protokolliert. In rot werden die Kommunikationsaktivitäten protokolliert. Diese Angaben sind recht ausführlich, da dieses Programm i.W. zum Test der Kommunikation gedacht ist. Zur Anzeige der normalerweise nicht sichtbaren Steuerzeichen werden diese in spitzen Klammen ausgegeben (z.B. <CR>). Die empfangenen Pakete werden einzeln mit der Kennung Received: dargestellt. Im der obigen Abbildung erkennt man z.B., dass der Client den Text Hallo Server! und das Zeilenendesymbol CRLF in zwei separaten Paketen gesendet hat.

Die Nettoempfangsdaten werden in grün ausgegeben und die Nettosendedaten in blau.

Implementierungshinweise

Steuerelemente (Controls)

Zur farbigen Anzeige der Protokolle dient das Steuerelement ColoredListBox, eine Ableitung des ListBox-Steuerelements, das im DrawMode OwnerDrawFixed betrieben wird. Über Instanzen der Klasse ColoredItem können, neben dem auszugebenden Text, zusätzliche Farbinformationen übergeben werden.

Zur Eingabe der Port-Nummer dient das Steuerelement NumericTextbox, eine Ableitung des TextBox-Steuerelements. I.W. wird die Eingabe von nicht numerischen Zeichen durch Auswertung des KeyPress-Ereignisses unterbunden.

Benutzersteuerelemente

Arbeitsklassen

Die oben in ihrer Funktion beschriebenen Fensterklassen sind frmServer und frmClient. Die Klasse ClientControl dient zur Übergabe der Voreinstellungen für die Kommunikation mit dem verbunden Client. Die Enumerationen TextAppendType und EncodingType legen die möglichen Werte typsicher fest. Die Klasse UrsTcpServer (TcpServer gibt es bereits) übernimmt die Serverfunktionen. Es gibt noch eine weitere Klasse UrsTcpClient. Diese dient aber nur zum Test während der Entwicklung.

Klassendiagramm

Eingehende Verbindungsanforderung

Damit die Benutzeroberfläche nicht einfriert, wird die TCP-Kommunikation innerhalb eines BackgroundWorker betrieben. Der BackgroundWorker in der Klasse UrsTcpServer wartet auf eigehende Verbindungsanforderungen und meldet diese über ein Ereignis(Event). Die beiden Ereignisse sind Report (sendet Nachrichten über den Prozessverlauf) und SocketAccepted mit den Daten der Verbindungsanforderung.

Im Eventhandler für das Ereignis SocketAccepted wird ein neues frmClient-Fenster geöffnet. Die geöffneten Client-Fenster werden in der Liste clientForms gesammelt. Wenn ein Client-Fenster geschlossen wird, meldet es sich durch Aufruf der Methode RemoveClientForm beim Server-Fenster ab.

readonly List<frmClient> clientForms = [];

void SocketAcceptedHandler(Socket clientSocket) {
   frmClient clientForm = new(clientSocket, this, new ClientControl(textAppendType, encodingType, cbStartDirectly.Checked));
   clientForms.Add(clientForm);
   lblConnected.Text = "Connected Clients: " + clientForms.Count;
   clientForm.Show();
}

public void RemoveClientForm(frmClient clientForm) {
   if (!isClosing)
      clientForms.Remove(clientForm);
   lblConnected.Text = "Connected Clients: " + clientForms.Count;
}

Über die Liste clientForms kann beim Schließen des Server-Fensters sicher gestellt werden, dass auch alle noch offenen Client-Fenster geschlossen werden:

private void frmServer_FormClosing(object sender, FormClosingEventArgs e) {
   if (clientForms.Count > 0) {
      var result = MessageBox.Show("There are still client connections open. They will now be closed.", "Warning", MessageBoxButtons.OKCancel, MessageBoxIcon.Warning);
      if (result == DialogResult.OK) {
         isClosing = true;
         foreach (frmClient clientForm in clientForms) {
            clientForm.Close();
         }
      }
      else {
         e.Cancel = true; // Schließen abbrechen
      }
   }
}

Eigehende Daten

Das Einlesen der Empfangsdaten erfolgt in der Arbeitsmethode des BackgroundWorker. Die empfangenen Bytes werden zunächst in eine Zeichenkette transformiert. Wenn ein Zeilenendezeichen vereinbart wurde, werden die empfangenen Zeichen sollen zeichenweise in den Eingangspuffer incomingData geschrieben, bis das Zeilenendezeichen erkannt wurde. Dann wird der aufgespeicherte Inhalt über das Ereignis ReportProgress mit der Kennung o gemeldet:

/// <summary>
/// Führt die Arbeit im Hintergrund aus.
/// </summary>
/// <param name="sender">Die Quelle des Ereignisses.</param>
/// <param name="args">Die Ereignisdaten.</param>
void bgw_DoWork(object? sender, DoWorkEventArgs args) {
   while (!bgw.CancellationPending) {
      if (connectedClientSocket.Available > 0) {
         byte[] b = new byte[connectedClientSocket.Available];
         int k = connectedClientSocket.Receive(b);
         string result = Encoding.UTF8.GetString(b, 0, k);
         bgw.ReportProgress(1, "Received: " + TranslateControlChars(result));

         if (textAppendType == TextAppendType.None) {
            bgw.ReportProgress(0, TranslateControlChars(result));
         }
         else {
            foreach (char c in result) {
               incomingData += c;
               if (incomingData.EndsWith(textToAppend, StringComparison.Ordinal)) {
                  bgw.ReportProgress(0, TranslateControlChars(incomingData));
                  incomingData = "";
               }
            }
         }
      }
      else {
         Thread.Sleep(100); // Vermeidet CPU-Überlastung
      }
   }

Verbindungstest

Eigentlich sollte bei einer TCP-Verbindung garantiert sein, dass Daten entweder korrekt versendet werden oder ein Fehler gemeldet wird. Dies ist leider nicht der Fall. Verbindungsabbrüche werden erst dann erkannt, wenn zwei Schreiboperationen mit genügend großen zeitlichen Abstand ausgeführt werden. Dies gilt auch für den Fall, dass die Gegenstelle die Verbindung ordnungsgemäß schließt (disconnect, close, ...). Der zeitliche Abstand ist notwendig, weil der sich der Nagle-Algorithmus nicht abschalten lässt.

Zum Testen der Verbindung werden mit zeitlicher Verzögerung (s.o.) zweimal das Steuerzeichen 6 (ACK, Acknowledge) gesendet.

private void cmdTest_Click(object sender, EventArgs e) {
   lbLog.AddItem("Transmitting \\0x06 twice", Color.Red);
   byte[] ba ={ 0x06};
   try {
      connectedClientSocket.Send(ba);
      Thread.Sleep(200);
      connectedClientSocket.Send(ba);
   }
   catch (Exception) {
      lbLog.AddItem("Client not connected. Cannot send message.", Color.Red);
   }
}