C / C ++


The Walrus : Les sockets C/C++ démystifiés.
-- Introduction
Beaucoup de monde parlent des sockets en C/C++ comme étant quelque chose de vraiment difficile. Je dois avouer que le manque de documentation sur l'internet cause cette réaction. J'ai beaucoup cherché sur le sujet et mes résultats ont étés plutôt déçevants. J'ai trouvé quelques documents que je me permet de citer, comme référence ou bien pour complémenter celle-ci. J'ai aussi fait le choix de l'écrire en français parce qu'il n'y a aucune documentation sur les sockets en français. Enfin, très peu.

Cette documentation n'est pas complète. Il manque beaucoup de théorie sur la réseautique, les couches TCP/IP, les *host byte order*, les socket DGRAM et beaucoup d'autre choses. Avec ceci, vous devriez être en capable de faire des sockets en utilisant le language C/C++, mais pas de les maitriser.


-- Qu'est-ce qu'un socket?
Pour communiquer entre deux applications ou ordinateurs, il vous faut un téléphone, un socket. Un socket est attaché à un port ( une porte au sens imagé ). Vous ouvrez votre porte et attendez qu'un colis sois acheminé à celle-ci. Une fois le colis reçu, vous pouvez soit envoyer un autre colis ou bien fermer la porte, le port. Vous devez initialiser le socket, faire un lien avec le port, attendre pour un paquet, et fermer le socket. Pour avoir plus d'informations sur les protocoles de transfert TCP/IP ou bien sur les socket en général, voir la section Références.


-- Utilisation du socket
Créer un socket en C/C++ est relativement simple, contrairement à ce que la majorité du monde pensent. La création d'un socket est faite par une chaîne de commandes. Pour commencer, il faut initialiser WSAStartup(). Un groupe de variables seront aussi nécéssaire pour faire cela. Bon, assez de bavardage et passons au code lui-même.


	#include <winsock2.h>
	#pragma comment(lib, "ws2_32.lib")

	void main()
	{
		WSADATA WSAData;
		WSAStartup(MAKEWORD(2,0), &WSAData);
	}

winsock2.h, la librairie que vous vous servirez avec les sockets. Certaines personnes choisissent la librairie de la version 1, winsock.h. Dépendant de vos besoins. Si vous choisissez la version 1, prenez bien soin de choisir la librairie "wsock32.lib" au lieu de "ws2_32.lib".

WSADATA WSAData, l'initialisation d'une variable WSADATA. La variable va être utilisée pour le démarrage de WSAStartup(). Elle n'aura pas d'autre utilisation, généralement. WSAStartup(MAKEWORD(2,0), &WSAData), ce qui dis à votre ordinateur que vous allez utiliser des sockets. Il y a deux paramètres à se rappeller, MAKEWORD(2,0) la version de winsock que vous désirez utiliser Ça peut varier, dépendant des #include que vous aurez choisis. Si vous avez choisi d'utiliser winsock 1 (winsock.h), remplacez le par MAKEWORD(1,0). Le deuxieme paramètre à se rappeller, &WSAData, vraiment simple. Vous y mettez la variable de type WSADATA que vous avez définis plus haut. À la fin de votre programme, il est préférable de nettoyer votre WSA, en faisant WSACleanup(); Attention, ne pas faire de WSACleanup() tant que vous n'aurez pas terminé avec les sockets, sinon vous devrez initialiser WSAStartup() une deuxième fois.

Votre winsock est maintenant initialisé. Maintenant,il suffit de créer le socket lui-même.


	SOCKET sock;
	SOCKADDR_IN sin;

	sin.sin_addr.s_addr	= inet_addr("127.0.0.1");
	sin.sin_family		= AF_INET;
	sin.sin_port		= htons(4148);

	sock = socket(AF_INET,SOCK_STREAM,0);
	bind(sock, (SOCKADDR *)&sin, sizeof(sin));

Et voilà. Votre socket est initialisé. Notez par contre que celui-ci est initialisé pour vous connecter sur vous-même au port 4148. L'utilité est donc nulle, sauf si vous avez un serveur à ce port-ci. Voyez la section "Exemples de codes" pour avoir des exemples plus pertinents.

SOCKET sock, initialisez une variable du type SOCKET qui sera utilisée pour définir le socket. SOCKADDR_IN sin, le struct du SOCKADDR contient les informations techniques du socket. Par exemple, .sin_addr.s_addr, qui définis l'addresse du server. Si vous codez un serveur, vous n'avez pas à définir d'addresse, vous utiliseriez donc:


	sin.sin_addr.s_addr = htonl(INADDR_ANY);

.sin_family, la *famille* du socket, le type si on veut. Pour l'internet, les programmeurs utilisent généralement AF_INET. Le dernier paramètre du struct à définir, .sin_port. Le port sur lequel vous voulez vous connecter ou bien écouter. htons(4148) pourrait être remplacé par htons(23) si vous voulez le port telnet, etc. Pour d'avantage d'explications sur les fonctions htons, htonl, noths, nothl, je vous suggère de voir 'The beej guide' référencié dans la section "Références".

socket(AF_INET, SOCK_STREAM, 0), la création du socket en tant que tel. Le 1er paramètre est la famille du socket, comme vous l'avez configuré au par avant dans la structure du SOCKADDR_IN. AF_INET dans ce cas-ci. La 2ième option, SOCK_STREAM, c'est le type de socket. Il existe aussi SOCK_DGRAM, dont je parlerai plus loin dans le texte. Les SOCK_STREAM ouvrent une conn entre les 2 ordinateurs directe et pourra ensuite envoyer les paquets que vous désirez, tandis que le SOCK_DGRAM envoie un paquet directement à la destination sans faire d'accept() ou de connect().

bind(sock, (SOCKADDR *)&sin, sizeof(sin));, la commande qui va attacher votre socket directement au port et à l'adresse que vous avez défini dans le struct SOCKADDR_IN. Trois paramètres à retenir ici, sock, le socket que vous avez initialisé plut tôt. La structure SOCKADDR_IN, la deuxième chose à retenir, la structure que vous aurez définis plus haut. Et finalement, la taille, sizeof(sin).

Maintenant votre chemin se divise en deux choix, le serveur ou le client. Si vous voulez faire un serveur, Vous devrez faire une boucle qui accept() les connections. Dans ce cas, c'est un petit peu plus compliqué.


	listen(sock, 0);
	int val = 0;
	while(1)
	{
		val = accept(sock, (SOCKADDR *)&csin, sizeof(csin))
		if(val != INVALID_SOCKET)
		{
			// Fonctions à éxécuter sur le socket.
		}
	}

Ok, procédons étape par étape. listen(sock, 0), listen va écouter le port sur le socket. La 1ere valeur, sock, est le socket sur lequel le listen() écoutera. La 2eme valeur, le BACKLOG. Le nombre maximum de connections qui seront écoutées en même temps. La variable int val sera utilisée pour prendre la valeur de retour du accept(). accept(sock, (SOCKADDR *)&sin, sizeof(csin)), la fonction qui va nous permettre d'accepter une connection. Une autre fonction très simple, 1ere valeur: le socket. la 2eme valeur est votre SOCKADDR_IN que vous avez crée pour prendre les informations du client connecté sur votre serveur. Et finalement, la dernière valeur, sizeof(sin), la grosseur totale.

J'ai rajouté un if() pour vérifier si le socket est accepté, si quelqu'un est connecté, et si il l'est, il va effectuer les fonctions() que vous aurez mis au préallable dans le if(), par exemple un send().

Maintenant, passons au deuxième choix, le client. Rien de plus simple. Il n'y à qu'a faire un connect().


	connect(sock, (SOCKADDR *)&sin, sizeof(sin))

connect, relativement semblable à accept(). Ces fonctions sont presque égales puisque la seule différence, le &sin. Vous mettez *VOTRE* SOCKADDR_IN au lieu de celui du client dans le accept().

Bravo, maintenant vous êtes connectés. Mais qu'est-ce que je vais faire a mon socket, maintenant que je suis connecté?! Nous y arrivons.


-- Commandes reliées au sockets.
Les commandes reliées au socketing les plus utilisées sont send(), recv(), sendto(), recvfrom(), closesocket(), shutdown(), getpeername(), gethostname().

send()
Send, la commande pour envoyer une string au client ou au serveur. Tellement simple d'utilisation. send(socket, message, grosseur, 0); Le 1er paramètre étant le socket, le 2eme étant le message à envoyer. Notez qu'un simple \n en socketing est \r\n, si vous ne faites pas de \r\n, le serveur/client ne le considérera pas comme un retour de chariot. La grosseur, sizeof(message). Le dernier paramètre ne vous sera probablement jamais utile, vous marquerez donc 0.
Exemple: send(sock, "Hello world!\r\n", 14, 0);

recv()
Recv est une autre commande très simple. En fait elle est presque identique au send(). recv(socket, buffer, grosseur, 0); Les paramètres sont identiques au send() sauf qu'au lieu d'envoyer une string, vous le stockez dans une variable, le buffer.
Exemple: recv(sock, buff, sizeof(buff), 0);

sendto()
Avant de faire du SOCK_DGRAM, je suggère fortement de commencer par le SOCK_STREAM. C'est plus simple pour commencer, et de toutes façons c'est ce qui est le plus utilisé. L'art d'envoyer un paquet à un IP particulier, sans avoir à se connect()er. Initialisez votre WSAStartup() et vous êtes prêts à l'utiliser. Il est TRÈS important de noter que lors de la création du socket(), il faut préciser SOCK_DGRAM et non pas SOCK_STREAM. sendto(socket, message, longueur, 0, sin, sizeof(sin));. Le premier paramètre, le socket lui-même. Ensuite, vous tapez votre message, la string à être envoyée, qui peut aussi bien être un buffer stocké dans une variable, suivi de la longueur du message. Vous continuerez avec un nouveau SOCKADDR_IN qui aura les informations de la destination que vous aurez programmé à l'avance. Terminez cela avec un sizeof(sin). Remplacez la variable sin par celle du SOCKADDR_IN, bien sur.

recvfrom()
La fonction recvfrom est presque identique à sendto(). Vous avez besoin d'utiliser SOCK_DGRAM lors de la création du socket(), comme pour le sendto(). recvfrom(socket, message, longueur, 0, sin, sizeof(sin)); Le sin sera celui du client qui vous aura envoyé un paquet. Notez que vous devrez configurer le sin comme si vous feriez un serveur régulier. Voir plus haut pour plus d'informations.

closesocket()
L'art de fermer un socket d'une façon facile et propre. Pour résumer cette fonction en une ligne, closesocket(socket).

shutdown()
C'est exactement comme un close(), mais vous avez beaucoup plus de contrôle avec cette fonction. Par exemple, vous pouvez bloquer le flux reçu, envoyé ou les deux. Deux paramètres à retenir, le 1er, le socket. Et le deuxième, un int 0 a 3.
	0 = Les recv ne seront plus acceptés
	1 = Les send ne seront plus acceptés
	2 = Ni les send, ni les recv ne seront acceptés, comme un close().


getpeername()
Cette fonction, relativement utile, sert à savoir qui est connecté sur vous, des infos sur le client. Vous devez créer un SOCKADDR_IN pour stocker les informations, par contre. L'utilisation de cette fonction va comme-ci: getpeername(socket, sin, sizeof(sin)); Le sin étant le nouveau SOCKADDR_IN crée pour les besoins de la cause.

gethostname()
Même principe que getpeername() en plus simple. gethostname(*hostname, sizeof(hostname)); *hostname étant un array stockant le *hostname* de votre propre machine.


-- Exemples de codes.
Voici un client simple pour se connecter à irc. Notez que les recv() et les send() pour s'enregistrer et pouvoir l'utiliser comme un client ne sont pas incorporés.


	#include 
	#pragma comment(lib, "ws2_32.lib")

	void main()
	{
		WSADATA WSAData;
		WSAStartup(MAKEWORD(2,0), &WSAData);
	
		SOCKET sock;
		SOCKADDR_IN sin;
		char *buffer = new char[255];

		/* Tout est configuré pour se connecter sur IRC, haarlem, Undernet. */
		sock = socket(AF_INET, SOCK_STREAM, 0);	
	
		sin.sin_addr.s_addr			= inet_addr("62.250.14.6");
		sin.sin_family				= AF_INET;
		sin.sin_port				= htons(6667);

		connect(sock, (SOCKADDR *)&sin, sizeof(sin));
		recv(sock, buffer, sizeof(buffer), 0);
		closesocket(sock);
		WSACleanup();
	}

Maintenant, voici un serveur simple qui envoie un Hello world! a quiconque se connecte.


	#include 
	#pragma comment(lib, "ws2_32.lib")

	void main()
	{
		WSADATA WSAData;
		WSAStartup(MAKEWORD(2,0), &WSAData);
	
		SOCKET sock;
		SOCKET csock;
		SOCKADDR_IN sin;
		SOCKADDR_IN csin;

		sock = socket(AF_INET, SOCK_STREAM, 0);

		sin.sin_addr.s_addr			= INADDR_ANY;
		sin.sin_family				= AF_INET;
		sin.sin_port				= htons(23);

		bind(sock, (SOCKADDR *)&sin, sizeof(sin));
		listen(sock, 0);

		while(1)
		{
			int sinsize = sizeof(csin);
			if((csock = accept(sock, (SOCKADDR *)&csin, &sinsize)) != INVALID_SOCKET)
			{
				send(csock, "Hello world!\r\n", 14, 0);
			}
		}
	}

Ces code sources sont vraiment basiques, mais ils pourront toujours vous aider pour voir la structure d'un code C/C++ utilisant les socket. Avec ces instruments et quelques autre documents sur les sockets, vous devriez être capable de maitriser les principes de bases et beaucoup plus. Je vous suggère fortement de jeter un oeil sur la section référence pour plus d'informations sur le sujet en général.


-- Références
Voici une liste de références dont je me suis servi pour apprendre les sockets et pour vous les expliquer. Certains sont complèts, d'autre non. Ils sont tous de bonne qualité, par contre. Notez qu'ils sont en anglais, malheureusement ils sont en anglais.
	"Beej's guide to network programming" 
- http://www.ecst.csuchico.edu/~beej/guide/net/
Un des guides les plus interessant sur l'internet. Relativement complet. Toutefois, le tutoriel touche à l'environnement UNIX inclusivement. Il est bien de noter que les sockets en UNIX ne sont pas plus compliqués qu'en Windows, je dirais même plus simple. Quelques changements de librairies, enlevez les WSA* et vous avez des socket UNIX. "BSD Sockets: A Quick And Dirty Primer" - http://world.std.com/~jimf/papers/sockets/sockets.html Encore là, nous avons un joli tutoriel, qui explique simplement comment utiliser les sockets, pour BSD. Mais encore très utile pour Windows, si vous faite les changements appropriés. "RFC1180: A tutorial to TCP/IP" - http://www.faqs.org/rfc/rfc1180.txt Voici un texte sur le TCP/IP. Ce n'est pas totalement complet, il manque l'historique du TCP/IP et certaines autres choses, mais en général, c'est relativement complet. Un tutorial sur le TCP/IP compacté en 30 pages. Je suggère aussi de regarder faqs.org pour d'autre documentations complètes.



-- Disclaimer
Cette documentation n'est pas protégée. Elle n'a aucun *copyright*. Je fais toutefois appel à votre intelligence et votre éthique pour ce qui concerne les droits d'utilisation du document. Je suggère fortement de référencier le document complet et mon nom. C'est une question de respect mutuel et d'intelligence.

Aussi, je tiens à remercier un bon ami à moi, QDerf, pour l'aide qu'il m'a apporté, même si ça pouvait parraître moindre.

Si vous avez, pour quelconque raison, besoin de me rejoindre par courriel, vous pouvez envoyer à The Walrus. Je ne répondrai pas aux questions du style "Comment je fais un virus?" ou bien "Ou je pourrais trouver des textes sur les sockets?". Si vous m'envoyez un courriel, soyez pertinent. Commentaires et suggestions bienvenues.

- The Walrus


Écrit par: The Walrus, 2002