Winsock Tutorial von c-worker.ch (Teil 2: Hostnamen auflösen)

Dieses Tutorial stammt von www.c-worker.ch.
Es darf auf einer eigenen Seite veröffentlicht werden sofern es unverändert bleibt, und die Seite www.c-worker.ch als Quelle erwähnt wird.
Für die Richtigkeit aller in diesem Dokument beschriebenen Angaben und für eventuelle Schäden die durch dieses Dokument entstehen könnten wird nicht gehaftet.

Hinweise

Falls beim kompilieren einige "Neudefinition" Fehler kommen entfernt die "#include <winsock2.h>" Zeile (wurde in diesem Fall schon in windows.h includiert)
Manche Leute berichten auch, dass Sie den Fehler beheben können indem sie winsock2.h vor windows.h includieren.

Falls der Compiler INADDR_ANY nicht finden kann verwendet ADDR_ANY (oder umgekert, eins von beiden geht schon)
Das Progamm muss gegen ws2_32.lib  gelinkt werden. Falls man Visual Studio verwendet muss man bei den Projekteigenschaften unter Linker ws2_32.lib zu den Libraries hinzufügen.
Eventuell schafft aber auch folgende Zeile am Anfang des Quellcodes Abhilfe: #pragma comment( lib, "ws2_32.lib" )

 

1. Einleitung

Im ersten Tutorial wurde unter anderem gezeigt wie man eine Verbingung zu einem Server aufbaut. Das ganze war jedoch nicht sehr flexibel da wir die IP des Servers in unseren Quellcode reingeschrieben haben. Besser wäre es doch wenn man die IP oder noch besser den Hostnamen als Parameter an das Programm übergeben könnte. Und genau das werden wir hier machen. Der Client vom letzten Tutorial (sock.c) wird hier so erweitert das man als ersten Parameter einen Hostnamen oder eine IP angeben kann, zu welchem dann die Verbindung hergestellt wird.

 

2. Hostnamen in eine IP auflösen

Um einen Hostnamen in eine IP aufzulösen steht die Funktion gethostbyname() zur verfügug:

struct hostent FAR * gethostbyname (
const char FAR * name
);

-name: ein Hostname für den die IP herausgefunden werden soll

Als Rückgabewert liefert die Funktion einen Pointer auf eine HOSTENT Struktur, diese ist folgendermassen definiert:

struct hostent {
char FAR * h_name;
char FAR * FAR * h_aliases;
short h_addrtype;
short h_length;
char FAR * FAR * h_addr_list;
};

Dabei interessiert uns nur h_addr_list, welches eine mit 0 terminierte Liste von IP Adressen ist, und zwar in Network Byte Order (also kein htonl() nötig).
Bei einem Fehler, oder falls die IP für den entsprechenden Hostnamen nicht gefunden werden konnte liefert die Funktion NULL zurück.

Wir schreiben nun eine eigene Funktion die all diese Arbeit abnimmt, dabei übernimmt sie als ersten Parameter einen String (char*) und als zweiten Parameter einen Pointer auf eine SOCKADDR_IN Strucktur, welche von der Funktion abgefüllt wird (allerdings nur das sin_addr Feld). Als Rückgabewert liefert sie SOCKET_ERROR bei einem Fehler.
Der erste Parameter kann dabei entweder eine IP als String sein, also zb "192.168.0.1" oder ein Hostname, zB "www.c-worker.ch". Zuerst wird unsere Funktion inet_addr() verwenden, wenn inet_addr() gelingt, war im übergebenen String eine IP, falls nicht rufen wir gethostbyname() auf, ging auch das nicht liefern wir SOCKET_ERROR zurück.
Hier nun die Funktion:

long getAddrFromString(char* hostnameOrIp, SOCKADDR_IN* addr)
{
  long rc;
  unsigned long ip;

  HOSTENT* he;
  /* Parameter prüfen */

  if(hostnameOrIp==NULL || addr==NULL)
    return SOCKET_ERROR;
    
  /* eine IP in hostnameOrIp ? */
  ip=inet_addr(hostnameOrIp);
  
  /* bei einem fehler liefert inet_addr den Rückgabewert INADDR_NONE */
  if(ip!=INADDR_NONE)
  {
    addr->sin_addr.s_addr=ip;
    return 0;
  }
  else
  {
    /* Hostname in hostnameOrIp auflösen */
    he=gethostbyname(hostnameOrIp);
    if(he==NULL)
    {
      return SOCKET_ERROR;
    }
    else
    {
      /*die 4 Bytes der IP von he nach addr kopieren */
      memcpy(&(addr->sin_addr),he->h_addr_list[0],4);
    }
    return 0;
  }
}


Das was es eigentlich schon. Nun müssen wir die Funktion noch in unser Beispiel sock.c einbauen. Unter anderem muss main() nun angepasst werden damit auch die übergebenen Parameter verwendet werden können, und die Anzahl der Parameter muss geprüft werden. Die Änderungen sind wieder fett hervorgehoben, einige Zeilen müssen auch entfernt werden, diese werden hier einfach fett auskommentiert.

//Prototypen
int startWinsock(void);
long getAddrFromString(char* hostnameOrIp, SOCKADDR_IN* addr);

int main(int argc, char** argv)
{
  long rc;
  SOCKET s;
  SOCKADDR_IN addr;
  char buf[256];
  
  if(argc<2)
  {
    printf("Usage: sock <hostname oder ip des servers>\n");
    return 1;
  }



  .....



  // Verbinden
  memset(&addr,0,sizeof(SOCKADDR_IN)); // zuerst alles auf 0 setzten
  addr.sin_family=AF_INET;
  addr.sin_port=htons(12345); // wir verwenden mal port 12345
  //addr.sin_addr.s_addr=inet_addr("127.0.0.1");  
  rc=getAddrFromString(argv[1],&addr);
  if(rc==SOCKET_ERROR)
  {
    printf("IP für %s konnte nicht aufgeloest werden\n", argv[1]);
    return 1;
  }
  else
  {
    printf("IP aufgeloest!\n");
  }
  



  .....

Der komplette Quellcode für den neuen Client kann auch hier heruntergeladen werden: sock2.c

Um es zu testen muss natürlich wieder zuerst in einer anderen Konsole socksrv gestartet werden. Dann kann sock2 zB folgendermassen aufgerufen werden:
sock2 localhost

Und localhost sollte erfolgreich in eine IP aufgelöst werden, und die Verbindung zustande kommen. Falls man ein eigenes Netzwerk hat kann man das nun auch richtig übers Netz testen. Man startet socksrv auf einem Rechner (zB mit dem namen machine1) und sock2 auf einem anderen Rechner, als ersten Parameter gibt man sock2 machine1 an, und das ganze sollte eigentlich einwandfrei laufen, vorausgesetzt es ist ein DNS verfügbar der machine1 in eine IP umwandeln kann oder man hat lokal in den entsprechenden Dateien einen Eintrag.

Das war es auch schon wieder.