Home » Tutorials » Netzwerk und Internet » Sockets mit WinAPI

Sockets mit WinAPI

UDP-Socket zum Senden (Client)

Und, wie ganz am Anfang mal versprochen, braucht man nur 3 Befehle der WinAPI um Daten zu versenden. Dies geht allerdings nur mit UDP. 2 Befehle (WSAStartup und Socket) haben wir schon. Bleibt noch einer übrig. Und da wir ja etwas versenden wollen, heißt dieser Befehl auch Sendto

  • unser Sockethandle
  • einen Buffer zum Senden (indem die Nachricht steht)
  • die Länge des Buffers
  • Flags (werden hier nicht weiter betrachtet; wird auf 0 gesetzt)
  • Adressrecord
  • Größe des Adressrecords

Der Adressrecord

Die ersten drei Parameter sind uns vertraut. Den Vierten setzen wir einfach auf 0. Fehlt uns noch der Adressrecord. Dem schauen wir mal genauer auf die Füße:

sockaddr_in = record  //nur für IPv4
  case Integer of
    0:   (sin_family: u_short;
          sin_port: u_short;
          sin_addr: TInAddr;
          sin_zero: array[0..7] of Char);
    1:   (sa_family: u_short;
          sa_data: array[0..13] of Char)
end;

TSockAddrIn = sockaddr_in;

Uns interessiert nur der Fall „case 0“. Sin_zero bleibt, wie der Name schon sagt einfach unberührt (bzw. auf 0) Die anderen 3 Variablen müssen wir setzen:

  • sin_family: bezieht sich wieder auf die Adressfamilie und muss den gleichen Wert haben, wie das Socket (bei uns also: AF_INET)
  • sin_port: ist der Port der Adresse
  • sin_addr: beinhaltet die IP Adresse als 32-bit-Wert (TInAddr)

TInAddr ist folgendermaßen aufgebaut:

in_addr = record
  case integer of
    0: (S_un_b: SunB);
    1: (S_un_w: SunW);
    2: (S_addr: u_long);
end;

TInAddr = in_addr;

Das soll auch dafür reichen. Wir nutzen eigentlich nur den direkten 32-bit-Wert (S_addr). Und wie das sehen wir gleich. (es ist sicher einleuchtend, dass für IPv6 TInAddr etwas anders aussieht)

Unser erstes gesendetes Datenpaket

Bevor wir nun endgültig zu Sendto kommen, muss ich noch ein wenig mogeln und trotz der versprochenen 3 Befehle noch zwei weitere ergänzen, sodass es jetzt 5 wären. Als Verteidigung meinerseits kann ich aber anbringen, dass man das Adressrecord auch hardgecodet im Programm schon festlegen kann, dann benötigt man die beiden Funktionen nicht. Die beiden Probleme sind folgende:
1.      Wer kann schon eine IP spontan aus einem String (z.B. 127.0.0.1) in einen 32-bit-Wert wandeln? Natürlich könnte man aus TInAddr das S_un_B benutzen und dabei den String auseinanderbasteln. Aber die Socket DLL bietet uns da gleich eine Funktion.
2.      Windows arbeitet im Little Endian, das Netzwerk im Big Endian. Bei der IP-Adresse brauchen wir da nicht mehr aufpassen, dass macht die eine Funktion (umwandeln String à 32 bit Integer) bereits. Die Portnummer müssen wir aber noch umwandeln.
Dazu benutzen wir die beiden Funktionen inet_addr und htons (beide auch nur für IPv4). Insgesamt sieht das dann so aus:

procedure sendbuf_via_UDP(IP:string; Port: Word; var buf; buflen: Integer);
var SockAddr: TSockAddrIn;
    AddrLen: Integer;
    FSocket: TSocket;
begin
  FSocket := createSocket_UDP;

  AddrLen := SizeOf(SockAddr);

  SockAddr.sin_family := AF_INET;
  SockAddr.sin_Port := htons(Port);
  SockAddr.sin_Addr.S_Addr := inet_addr(PChar(IP));

  if SendTo( FSocket, buf, buflen, 0, SockAddr, AddrLen) = SOCKET_ERROR then
    HandleError;
end;

Aufgerufen werden kann diese Funktion dann zum Beispiel so:

Mystring := 'Hallo Du da!';
Sendbuf_via_UDP('127.0.0.1', 6000, MyString[1], length(Mystring));

Soweit, so gut. Wie versprochen haben wir in 3 (+2) Befehlen einen kleinen Text versendet. Der Text geht jetzt an unseren eigenen Rechner (127.0.0.1) und an Port 6000. Wir haben aber keine Ahnung, ob der Text richtig ankommt. Dafür ist ja UDP auch nicht ausgelegt. SendTo liefert nur einen Fehler, wenn es nicht möglich ist, das Paket ins Netz zu senden. Eine Empfangsbestätigung gibt es nicht. Und höchstwahrscheinlich lauscht auch an Port 6000 niemand, sodass unser Paket einfach verfällt (wenn wir Glück haben, bekommen wir sogar eine „Host down“ Meldung. Wir sollten uns aber nicht darauf verlassen).

Socket schließen

Wenn wir das allerdings auf die oben beschriebene Art und Weise mehrmals machen, entsteht natürlich ein Problem. Wir eröffnen jedes Mal ein neues Socket und schließen das Alte nicht. Also müsste man das CreateSocket_UDP an einer anderen Stelle im Programm aufrufen oder wir schließen nach dem Senden unser Socket wieder. Das geht einfach über CloseSocket.

procedure sendbuf_via_UDP(IP: String; Port: Word; var buf; buflen: Integer);
var SockAddr: TSockAddrIn;
    AddrLen: Integer;
    FSocket: TSocket;
begin
  FSocket := createSocket_UDP;

  AddrLen := SizeOf(SockAddr);

  SockAddr.sin_family := AF_INET;
  SockAddr.sin_Port := htons(Port);
  SockAddr.sin_Addr.S_Addr := inet_addr(PChar(IP));

  if SendTo( FSocket, buf, buflen, 0, SockAddr, AddrLen) = SOCKET_ERROR then
    HandleError;

  CloseSocket( FSocket);
end;

Auch CloseSocket kann man wieder auf SOCKET_ERROR testen. Das spare ich uns aber mal.
Abschließend wäre noch zu erwähnen das Sendto, wenn es nicht SOCKET_ERROR zurückliefert, die Anzahl der gesendeten Bytes verrät. Dies nur der Vollständigkeit halber.
Unser erstes Socket-Programm um UDP gebunden Daten zu versenden befindet sich hier.

5 Gedanken zu „Sockets mit WinAPI“

  1. Wo findet sich denn der Link, um die Programmbeispiele herunterzuladen?
    Ich suche jetzt schon eine Weile, finde aber nichts.

  2. Eine weitere – aus meiner Sicht sehr wichtige – Antwort auf die Frage, warum man die Sockets selbst über die Windows API programmieren sollte, wäre noch:

    Ich hatte vor ca. 10 Jahren einen „Chat“ über die Komponenten von Delphi programmiert. Wir haben hier in der Firma einen Linux-Server zu stehen. Da die Leute das lustig fanden, ist der Chat heute fester Bestandteil. Linux bedeutet aber insoweit Probleme, weil ich das Programm emulieren lassen muss. Dafür würde sich theoretisch wine anbieten.
    Der Haken ist aber, dass die Komponenten alle fest in der VCL verankert sind und man mit wine nicht weit kommt. Man braucht also eine virtuelle Maschine mit Windows, um den Server laufen zu lassen. Insoweit erhoffe ich mir, dieses Problem eventuell lösen zu können. Wenn es mir gelingt, mit dieser Anleitung eine eigene Klasse ganz ohne jede VCL erstellen zu können, dann kann ich den Server als Konsolen-Programm erstellen und (endlich) ohne den ganzen Overhead unter wine laufen lassen.

    Falls den Seitenadmin dieser Kommentar stört, kann er ihn gern wieder gelöscht werden. Aber auf der Suche nach Sockets ohne VCL, wäre dies als Fund bei Suchmaschinen schon vor langer Zeit sehr hilfreich für mich gewesen. Suchmaschinen finden aber immer nur den Text, der auf den Seiten tatsächlich irgendwo steht und wenn man nach „nonvcl“ im Zusammenhang mit Sockets unter Delphi sucht, sieht es dünn aus mit den Suchergebnissen.

  3. Hallo, ich kann leider auch deine Programmbeispiele als Datei nicht finden. Sind die nicht mehr verlinkt?

    Ansonsten gutes Tutorial 🙂

Kommentare sind geschlossen.