Le guide d'Iczelion pour programmer un Socket Win32

Un Winsock ou un socket Windows reprend le concept d'Unix. L'idée qui se cache derrière les sockets c'est qu'on peut les employer comme des dispositifs de communication entre des machines différentes d'un réseau. Chaque machine peut créer un ou plusieurs sockets pour se connecter à d'autres machines. Les sockets utilisent les protocole TCP/IP comme tel, ils opèrent dans les hautes sphères du réseau. Nous utilisons des sockets dans n'importe quel haut protocole qui veut se servir du TCP/IP comme médias de transport, tel que le HTTP, le FTP et cetera. Le socket lui-même ne comprend pas ou se ne soucie pas du contenu qui passe par lui. Il fait juste son travail d'envoi et de réception de données à son homologue sur l'autre machine.

Les Types de Sockets
Il y a deux types de socket : les 'sockets stream (flot)' et les 'sockets datagram'. Un socket stream se sert du TCP, ainsi sa transmission exige que la connexion soit fiable. Vous employez un socket stream quand vous voulez envoyer de gros fichiers à travers le net ou bien que vous souhaitez envoyer vos données par paquets séquencés (c'est-à-dire que l'ordre des paquets de données est important). Un socket datagram fournit un transfert simple de données. On ne garantit pas qu'il soit fiable (les données peuvent ne pas atteindre leur destination), ni séquencés (les paquets de données peuvent arriver dans un ordre différent que celui qui est attendu), ou non-dupliqués (le socket de destination peut recevoir deux ou même plusieurs copies identiques des mêmes paquets de données). Les protocoles HTTP et FTP et quelques autres emploient les sockets stream tandis que certains autres protocoles d'émission utilisent les sockets datagram.
Vous pouvez vous représenter la connexion par socket stream comme un simple appel téléphonique. Vous devez d'abord faire un appel et si l'appel est couronné de succès, une connexion est établie. Alors vous pouvez échanger des informations avec l'autre ordinateur d'une façon fiable. Si un quelconque problème de communication se produit vous le saurez immédiatement et pourrez prendre des mesures pour le rectifier. La connexion par socket datagram c'est un peu comme l'envoi de plusieurs courriers à quelqu'un. Vous ne pouvez pas savoir à l'avance s'ils atteindront la personne destinée. Même s'il les a reçus, il n'y a aucune garantie que ces courriers lui parviendront dans le même ordre que celui où vous les lui avez envoyés. Et les courriers peuvent très bien ne pas atteindre la cible du tout.
Le modèle d'un socket emploie parfois le concept client-serveur c'est-à-dire qu'on se représente le socket en tant que serveur puisque il propose ses services à d'autre sockets, de l'autre côté c'est le client, lui il demande des services au serveur. Les utilisations HTTP et FTP sont des concepts client-serveur.

Ordre d'Octet
Puisque nous devons traiter avec des adresses IP dans la programmation d'un winsock, nous devons d'abord nous familiariser avec les différents types d'ordonnancement de bytes. Il y a deux types de rangement de bytes : le 'Grand Endian' et le 'Petit Endian'. Dans l'arrangement 'Grand Endian', l'octet extrême gauche est stocké dans l'octet le plus significatif (byte de poids fort). Pour l'arrangement 'Petit Endian' c'est le contraire. Par exemple, si votre adresse IP est 205.34.67.24, alors l'arrangement 'Grand Endian' sera représenté en tant que 205 34 67 24. Mais dans l'arrangement 'Petit Endian', ce sera 24 67 34 205.
Le CPU Intel utlise le 'Petit Endian' tandis que d'autres CPU comme Motorola travaillent en 'Grand Endian'. Le mot de la fin c'est qu': Internet emploie l'arrangement 'Grand Endian', donc si vous utilisez un CPU qui classe en 'Petit Endian', vous devez d'abord convertir les adresses IP avant de les utiliser avec le Net.

Modes : Bloqué et Débloqué
Les fonctions des sockets peuvent fonctionner sur deux modes : bloqué et débloqué. À l'origine, dans l'exécution Berkeley d'Unix, les sockets fonctionnaient en mode bloqué, c'est-à-dire qu'une fonction d'un socket ne retournait pas avant que l'opération ne soit achevée. Le "mode bloqué " est une opération synchrone alors que le "mode débloqué" est une opération asynchrone. L'opération de blocage peut prendre une longue période de temps non définie pour s'achever et attente que les données atteignent le socket éloigné. Pendant ce temps-là, le programme semblera gelé. C'est en général plutôt inacceptable dans l'environnement de Windows. Donc Windows inclut des versions d'API de sockets (débloqués) asynchrones, ce sont des évolutions des APIs originaux bloqués. Vous devez utiliser les versions débloquées chaque fois que c'est possible puisqu'ils se conforment au paradigme de Windows et fournissent de meilleurs résultats que ceux bloqués.

Les Ports de Communication
Quand vous créez un socket et le connectez à un autre socket éloigné, vous devez spécifier le port que les sockets prendront pour communiquer entre eux. Les ports dans ce cas ne sont pas des ports matériel comme COM1 ou COM2. Les ports dans la programmation d'un winsock sont virtuels dans l'unique but d'établir une communication. Peut-être qu'un exemple rendra ça plus clair. Sachez que, le serveur crée le socket et le charge d'écouter la connexion entrante sur le port 21, bien qu'il puisse y avoir beaucoup de paquets entrants vers le serveur, seuls ces paquets qui sont destinés au port numéro 21 seront routés vers le socket. Plusieurs protocoles Internet ont leurs propres ports par défaut. Le port du protocole HTTP est le port 80 (décimale) et le FTP emploie le port 21 (décimale). Ceux sont deux ports par défaut. Ça signifie que si le client et le serveur consentent tous deux à communiquer via d'autres ports, ils peuvent le faire sans que ça pose problème.

Vue d'ensemble sur la programmation d'un Winsock
La programmation d'un winsock inclut normalement ces étapes:

  1. Initialisation de la Dll winsock
  2. Création du socket
  3. Choix du mode d'opération : bloqué ou débloqué
  4. Connection du socket
  5. Exécution de certaines tâches grâce au socket, tel que l'envoi ou la réception des données
  6. Fermeture du socket
  7. Libération de la Dll winsock
Nous allons explorer ces étapes plus en détail.

Initialisation de la Bibliothèque du socket
Vous devez appeler WSAStartup pour initialiser la Dll winsock avant de pouvoir utiliser n'importe laquelle des fonctions winsock. Cette fonction a la syntaxe suivante : Paramètres
wVersionRequried  == est la version du winsock que votre application souhaite utiliser. Aujourd'hui, les versions 1.1 et 2 sont disponibles. La version 1.1 est pour Windows 95, alors que les versions 2.x proviennent de Windows NT 4.0. Si vous voulez déclarer la version 1.1 utilisez 101h, si vous voulez employer la version 2.0 utilisez 200h
lpWSADATA == est le pointer qui est sur la structure WSADATA, laquelle sera remplie par la Dll winsock. Cette structure contient les détails d'exécution de la Dll winsock. Normalement, vous avez juste à créer votre structure WSADATA non-initialisée, et on passe son adresse à WSAStartup pour qu'il la remplisse.

Valeur de Retour
WSAStartup renvoie un NULL si l'appel est couronné de succès. Autrement il renvoie le code d'erreur. Vous pouvez chercher les codes d'erreur dans la doc winsock.hlp. C'est la seule fonction winsock qui renvoie le code d'erreur réel parce que vous ne pouvez pas appeler WSAGetLastError si la Dll socket n'est pas initialisée auparavent.

Commentaires
Si cette fonction n'est pas couronnée de succès, vous ne pourrez employer aucune des fonctions winsock. En fait, cette fonction sert aussi de routine de négociation pour les meilleures versions d'API winsock, pour votre application. Après que l'appel soit couronné de succès, la structure WSADATA sera remplie avec les capacités du winsock. Un des membres, est la plus haute version du winsock que la bibliothèque doit soutienir. Si votre application requière au minimum la version 1.1 et que la bibliothèque winsock courante peut soutenir la version 2, alors l'appel de WSAStartup sera couronné de succès et ainsi la bibliothèque winsock retournera la valeur 200h dans le membre concerné de WSADATA. Si l'application peut se servir de l'appui de la version winsock 2, on pourra appeler WSACleanup pour refermer l'initialisation précédente et appeler WSAStartup à nouveau, cette fois-ci avec 200h dans son paramètre wVersionRequired. Si votre application ne supporte que winsock 1.1, vous ne devez pas examiner la structure WSADATA.

Petit bout de Code :


Création du Socket
Après que l'appel de WSAStartup soit couronné de succès, vous pouvez continuer à créer votre socket. Paramètres
af == est le format de l'adresse, encore aujourd'hui il n'y a que : PF_INET, mais ça évolura peut être.
type == est le type du socket que vous souhaitez créer, stream ou datagram. Si vous voulez un socket stream, employez SOCK_STREAM sinon utilisez SOCK_DGRAM
protocol == Si le paramètre 'af' est af_UNSPEC (non indiqué), vous devez spécifier le protocole ici. Cependant, puisque nous employons toujours PF_INET, vous pouvez mettre 0.

Valeur de Retour
Si cet appel échoue, la valeur en retour est INVALID_SOCKET et vous pouvez appeler la fonction WSAGetLastError pour retrouver le code de l'erreur réelle. Si l'appel est couronné de succès, il renvoie le descripteur du socket, lequel vous devrez utiliser pour vos fonctions winsock suivantes.

Commentaire:
Vous devez créer au moins un socket pour l'employer en tant que dispositif de communication de part. Après l'appel couronné de succès, vous devez sauvegarder les retours du descripteur de socket pour l'utiliser avec les fonctions winsock suivantes.

Petit bout de Code:


Spécification des Options du socket
Après que le socket est été créé, il est par défaut en mode bloqué. Si vous voulez qu'il fonctionne en mode débloqué, vous devez mettre setsockopt ou WSAAsyncSelect. Normalement, vous devez employer WSAAsyncSelect pour faire passer le socket en mode débloqué. Donc ici notre démonstration utilisera WSAAsyncSelect. setsockopt n'est pas difficile à utiliser, vous pourrez le voir vous-même. setsockopt peut modifier pas mal de caractéristiques du socket, donc il vaudrait mieux que vous y jetiez un petit coup d'oeil plus tard. Paramètres
socket est le descripteur de socket, lequel est retourné par l'appel socket .
hwnd est le handle de la fenêtre qui recevra les notifications (les informations) d'événements winsock. (événements de type winsock, ou événements en relations avec le socket)
msg sert à définir un message construit sur mesure, vous le créez. En fait on souhaite que la bibliothèque winsock envoie ce message à votre fenêtre dans le cas où un événement intéressant se produit.
Event représente les événements winsock qui sont dignes d'intéresser votre application. Vous pouvez spécifier une des constantes ci-dessous ou les associers entre elles si votre application s'intéresse à plusieurs événements winsock en même temps. Valeur de Retour
Si l'appel est couronné de succès, la valeur de retour est NULL. Autrement c'est SOCKET_ERROR qui sera renvoyé et vous pourrez appeler WSAGetLastError pour retrouver le code d'erreur réel.

Commentaires
Cette fonction est l'instrument du paradigme 'débloqué'. Elle permet de spécifier quels événements winsock sont intéressants pour notre application, et lorsque un de ces événements winsock pré-enregistré se produit, la bibliothèque winsock enverra le message indiqué à la fenêtre. Cette fonction change aussi le mode du socket pour le mettre à 'débloqué'. Je recommande que vous employiez WSAAsyncSelect chaque fois que c'est possible puisque il se conforme au paradigme de Windows.
Si vous voulez que la bibliothèque winsock arrête d'envoyer des informations à votre fenêtre, vous devez appeler WSAAsyncSelect avec la valeur 0 dans le paramètre lEvent. Cependant, prenez garde qu'il peut rester des messages winsock dans la file d'attente avant que vous n'ayez annulez la réception de ces messages d'informations winsock. Vous devez être préparés à les traiter. Normalement vous devez les extraire de la file d'attente des messages grâce à l'appel PeekMessage avec le flag PM_REMOVE.
Quand l'application est intéressée par l'arrivée d'un événement winsock, le message indiqué par le paramètre msg sera envoyé à la procédure de la fenêtre retrouvée par le paramètre hwnd. Notre message 'msg' construit sur mesure par nous-même fonctionne ainsi : wParam contient le descripteur du socket, le mot de poids fort de lParam contient le code d'erreur (s'il y en a une) et le mot de poids faible de lParam contient l'événement.

Petit Bout de Code



Connexion à un socket éloigné

Après que le socket est été créé, vous avez deux choix : Soit vous attendez une connexion entrante, soit au contraire c'est vous qui prenez l'initiative de joindre un socket éloigné. Si vous attendez la connexion entrante, vous devez appeler listen pour écouter une connexion entrante. Inversement, servez vous de l'appel accept pour établir votre connexion avec un socket éloigné. Si vous voulez vous connecter à un socket éloigné, vous devez appeler connect, cette fonction présente la syntaxe suivante : Paramètres
socket est le 'descripteur de socket' de notre socket. Vous pouvez passer ce descripteur de socket.
lpSockAddr_in est le pointer qui est sur la structure SOCKADDR_IN laquelle est déclarée comme suit: namelen est la taille de la structure SOCKADDR_IN.

Valeurs de Retour
Ça dépend du mode de fonctionnement du socket.

Un Petit Bout de Code Si vous avez seulement obtenu une chaîne de caractères de l'URL comme "http://members.xoom.com/Iczel", vous devez faire une analyse de la syntaxe de cette chaîne pour en extraire le nom de l'hôte, dans ce cas c'est "members.xoom.com". Et ainsi pouvoir passer l'adresse du nom de cet hôte en tant que paramètre de la fonction gethostbyname, laquelle présente la syntaxe suivante : Paramètres
lphostname est le pointer qui est sur la chaîne de caractères qui représente le nom de l'hôte.

Valeur de retour
Si le retour est couronné de succès, il renvoie le pointer qui est sur la structure hostent laquelle est réservée par Windows pendant l'exécution du socket. Si l'appel échoue, il renvoye un NULL et vous pouvez appeler WSAGetLastError pour retrouver le code d'erreur.

Notez que dans le fichier Windows.inc qui fait parti du hutch, la structure hostent est nommée hostentStru. Commentaire
Cette fonction est utilisée pour retrouver l'adresse(s) IP de l'hôte indiqué sous forme d'une chaîne de caractères. Remarquez que ce qui vous intéresse réellement se trouve dans le membre h_list.

Petit Bout de Code
 .data
sin SOCKADDR_IN <>
hostname db "members.xoom.com",0
Port dd 80                    ; On utilise le port 80, le port HTTP pour notre démonstration

.data?
socket dd ?

.code
........
mov sin.sin_family, AF_INET
invoke htons, Port                    ; On converti d'abord le numéro du port dans l'organisation d'octets                                                                       ; pour réseau
mov sin.sin_port,ax                  ; Notez que ce membre est un paramètre de taille 'Word'.
invoke gethostbyname, addr hostname
mov eax,[eax+12]                ; Transfère le membre h_list dans eax
mov eax,[eax]                      ; Copie le pointer qui est sur l'addresse IP actuelle dans eax
mov eax,[eax]                      ; Copie l'addresse de l'IP dans eax
mov sin.sin_addr,eax
invoke connect,socket,addr sin,sizeof sin
.if eax==SOCKET_ERROR            ; En supposant qu'on utilise le mode débloqué, la connexion
                                                            ; retournera toujours le flag SOCKET_ERROR.
    invoke WSAGetLastError            ; <-- Cette fonction retrouve le code de l'erreur réel,
    .if eax!=WSAEWOULDBLOCK        ; si ce n'est pas WSAEWOULDBLOCK
        <Mettez votre code de traitement d'erreurs ici>        ; ça signifie qu'une vraie erreur s'est produite.
    .endif
.endif


Opérations sur le Socket
Il y a plusieurs opérations que vous pouvez exécuter avec un socket tels que l'envoi ou la réception des données. Nous examinerons ces deux cas en détail.
Envoi de données vers un socket éloigné
Nous utilisons send pour envoyer des données grâce à un socket stream et sendto pour envoyer des données grâce à un socket datagram. J'examinerai ici send puisque beaucoup de protocoles populaires comme le HTTP et le FTP utilisent les sockets de type stream. Paramètres
socket est le descripteur du socket
buffer est l'adresse des données que vous souhaitez envoyer. Ces données n'ont pas besoin de se terminer par un caractère NULL puisque leur taille est spécifiée dans le paramètre len.
len est la taille des données à envoyer
flags sont les Flags qui déterminent le comportement de la fonction. Il y a deux Flags que vous pouvez utiliser: Valeur de Retour
Si l'appel échoue, la valeur SOCKET_ERROR est renvoyée dans eax. Si il est couronné de succès, le nombre réel d'octets transmit, est renvoyé dans eax. Notez que ce nombre ne peut pas être égal au paramètre len.

Commentaires
Cette fonction lance des données vers un socket éloigné et connecté. Il ne se soucie pas, ni ne connaît les données qu'il envoie.

Petit Bout de Code

Lecture des données du socket
Il y a deux variantes de cet appel de lecture des données du socket : recv et recvfrom. recv s'utilise avec un socket stream tandis que recvfrom s'utilise avec un socket datagram. Paramètres
socket est le descripteur du socket
buffer est l'adresse du bloc de mémoire qui sert à stocker les données entrantes.
len est la taille de ce bloc de mémoire
flags représente les Flags qui définissent le comportement de la fonction. Il y a deux Flags que vous pouvez utiliser: Valeurs de Retour
Si l'appel est couronné de succès, il renvoie le nombre d'octets qu'a lu le socket. Si il échoue, la valeur SOCKET_ERROR est retournée. Si la valeur de retour est 0, c'est que le socket éloigné a été fermé.

Commentaires
Cette fonction lit les données du socket et les stocke dans le buffer indiqué. Une question surgit : comment savoir combien d'octets valides ont été lus dans le socket ? La réponse vient de la fonction ioctlsocket. Elle possède la syntaxe suivante :

Paramètres
socket est le descripteur du socket
cmd est la commande à exécuter sur le socket. Il y a trois commandes :FIONBIO, FIONREAD, et SIOCATMARK. Puisque nous voulons récupérer la taille des données valides dans le socket, nous nous concentrerons sur FIONREAD. La commande FIONREAD détermine la quantité de données qui peuvent être lues dans le socket. La quantité de données est stockée à l'emplacement indiqué par lpArgument.
lpArgument est l'adresse d'un paramètre supplémentaire pour cmd.

Valeur de Retour
S'il est couronné de succès, la valeur de retour dans eax est NULL. Sinon la valeur SOCKET_ERROR est retournée et vous pouvez appeler WSAGetLastError pour retrouver le code d'erreur.

Commentaires
Cette fonction est employée pour contrôler le mode du socket. Avec la commande FIONBIO, on peut commuter le socket du mode bloqué au mode débloqué et inversement. La commande FIONREAD renvoie la quantité de données valides dans le socket. Grâce à la commande SIOCATMARK, on vérifie vraiment si toutes les données hors-norme ont été lues.

Petit Bout de Code


Fermeture du Socket
Après que vous ayez finis d'utiliser le socket, vous devez le refermer en appelant closesocket. Paramètre
socket est le descripteur du socket

Valeur de Retour
Si elle est couronnée de succès, elle retourne un NULL. Sinon SOCKET_ERROR est retourné et vous pouvez appeler WSAGetLastError pour retrouver le code d'erreur.

Commentaires
Cette fonction referme le socket. Chaque ressource du socket sera également terminé.

Petit Bout de Code


Libération (déchargement) de la Bibliothèque Winsock
Lorsque l'application n'a plus besoin d'utiliser la bibliothèque winsock, on appelle WSACleanup. Paramètres
Cette fonction ne prend aucun paramètre.

Valeur de Retour
Si elle est couronnée de succès, elle retourne un NULL. Autrement elle retourne SOCKET_ERROR.

Commentaires
Il doit exister dans votre programme autant d'appels de fonctions WSAStartup que de fonctions WSACleanup. Si vous appelez WSAStartup trois fois, vous devez également appeler WSACLEANUP trois fois autrement la bibliothèque winsock sera incapable de libérer les ressources inutilisées quelles qu'elles soient. C'est la dernière fonction que vous appelez lorsque vous en avez terminé avec la bibliothèque winsock.

Petit Bout de Code


[Iczelion's Winsock Section][Iczelion's Win32 Assembly Homepage]


Traduit par Morgatte