L'API Winsock


Pour programmer par Socket sous Delphi, on a recours aux fonctions de l'API Winsock.

La première fonction a utiliser impérativement est la fonction d'initialisation, propre à Windows : WSAStartup.
Sous Delphi, on pourra profiter de la présence de la partie Initialization des unites pour invoquer automatiquement cette fonction.

unit Sockets;

interface

uses
  Winsock;

implementation

procedure InitWinsock;
var
  wsa: TWSAData;
begin
  WSAStartup($101, wsa); // on réclame une pile IP de version 1.1
end;

initialization
  InitWinsock; // Initialisation automatique
finalization
  WSACleanup; // on ajoute aussi le cleanup
end.

L'étape suivant va consister à ouvrir un socket, c'est ce qui va nous permettre de travailler avec l'ensemble des fonctions de l'API.

var
  so: TSocket;
begin
// so := socket(AF_INET, SOCK_DGRAM, IPROTO_UDP); pour un socket UDP
  so := socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); // pour un socket TCP
  if so = INVALID_SOCKET then 
    Exit; // impossible d'allouer le socket !
end;

En cas d'erreur, c'est la fonction WSAGetLastError qui nous retournera le code erreur de la dernière fonction utilisée (il faut donc l'invoquer avant tout autre fonction de l'API sinon le code erreur sera perdu !).

Dans un premier temps, nous allons exploiter ce socket pour se connecter en TCP sur un serveur public : www.google.fr :)

var
  sa: TSockAddr;
begin
  FillChar(sa, SizeOf(sa), 0);
  sa.sin_family := AF_INET; // connexion IP
  sa.sin_port := htons(80); // sur le port 80
  sa.sin_addr.S_addr := INetAddr('www.google.fr'); // vers www.google.fr
  if sa.sin_addr.S_addr = INADDR_NONE then  // adresse IP non trouvée
    Exit; 
  if connect(so, sa, SizeOf(sa)) = SOCKET_ERROR then  // erreur de connexion
    Exit; 
end;

J'utilise ici une fontion INetAddr de mon cru qui fonctionne aussi bien avec une adresse au format numérique qu'avec un nom d'hôte :
function INetAddr(const Host: string): Integer;
var
  pHost: PChar;
  HostEnt: PHostEnt;
begin
  if Host = '' then // juste au cas ou ...
    Result := INADDR_NONE
  else 
  begin
    pHost := PChar(Host);
    Result := inet_addr(pHost); // est-ce une adresse au format x.y.z.w ?
    if Result = INADDR_NONE then 
    begin
      HostEnt := gethostbyname(pHost); // est-ce un nom d'hote ? (résolution DNS)
      if HostEnt <> nil then 
        Result := Integer(Pointer(HostEnt^.h_addr^)^);
    end;
  end;
end;

Bien, maintenant que nous avons contacté le serveur et que celui a décroché répondu, on va pouvoir lui parler...que se passe-t-il donc si on lui dit "Allo ?" :D

Pour ce faire, nous allons utiliser une fonction qui permet d'envoyer une chaine de caractères sur un socket.
function SendStr(so: TSocket; const Str: string): Boolean;
var
  l: Integer;
  x: Integer;
  i: Integer;
begin
  Result := False;
  l := Length(Str);
  if l=0 then
    Exit;
  x := 1;
  while l > 0 do 
  begin
    i := send(so, Str[x], l ,0);
    if i <= 0 then 
      Exit; // 0 = déconnexion, si <0 alors erreur Winsock
    Dec(l, i);
    Inc(x, i);
  end;
  Result := True;
end;

begin
  SendStr(so, 'Allo ?'#13#10); // on ajoute un retour à la ligne sinon le serveur HTTP ne répondra pas
end;
Il ne reste plus qu'à lire la réponse; HTTP fonctionnant, comme beaucoup de protocoles IP en mode texte (c'est pourquoi j'ai développé il fut un temps l'unité CrtSock), nous allons déclarer une fonction de lecture de chaines.
// cette fonction lit le socket pour remplir un cache
function FillCache(so: TSocket; var Str: string): Boolean;
var
  b: string;
  i: Integer;
begin
  Result := False;
  SetLength(b, 1024);
  i := recv(so, b[1], 1024, 0);
  if i<=0 then 
    Exit;
  Str := Str + copy(b, 1, i);
  Result := True;
end;

// cette fonction retourne la première ligne du cache se terminant par un CR/LF
function ReadString(so: TSocket; var Cache, Str: string): Boolean;
var
  i: Integer;
begin
  Result := False;
  i := pos(#13#10, Cache);
  while i=0 do 
  begin
    if not FillCache(so, Cache) then 
    begin
      if Cache='' then 
        Exit;
      i := length(cache);
      Break;
    end;
    i := pos(#13#10, Cache);
  end;
  Str := copy(Cache, 1, i-1);
  Delete(Cache, 1, i+1);
  Result := True;
end;

var
  cache: string;
  line : string;
begin
  cache := '';
  AllocConsole; // pour afficher une console depuis une application graphique
  while ReadString(so, cache, line) do 
    WriteLn(Line);
end;

Avec ça, on a déjà une réponse du serveur :
<html><head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<title>400 Bad Request</title>...

Bad Request :D et oui ! faut lui parler HTTP pour qu'il soit content :)
Date de dernière modification : 01/07/2009