.. ,.-.. ,..,
| | .` `. | |
|''| |`'---'`| |`'-`|
|<<| | _____ | _,,..|[}{]|.,,,
_,|<<|,, | \ D / | ,-`` |[}{]| ``',
.` `..' `. | |A| | |-,, ``--'' _,-'|
| `''--''` | | |T| | | ``'''------''`` |
| [ O3 ] | | |A| | | [ Mindkind ] |
`. ~~ .' | / \ | `-,, ] 1010 [ _,-'
`''--''` _,,,,,,...|| C ||.....,,,,,,_ ``'''------''```
|<<|.-''```` _,,,,,...|[ O ]|...,,,,,,_ ```''-|[}{]|
,``` .,-''``` |[ R ]| ``''-., |[}{]|,
| `. _,,,,...|| E ||...,,,,, '|[}{]| |
|`-,_ `'-.,,_,`********| \ / |********`._,,.-'` |[}{]|'|
| `''-.,,,_ `````'''''---------'''''````` _,,,.-|[}{]| |
|`-,_ `````''''''---------'''''''````` |[}{]|'|
| `''[ ]|[}{]| |
|`-,_ [ NSocket - Socket Layer ]|[}{]|'|
| `''[ ] |[}{]| |
`-,_ [ ] _,,..|[}{]|.,,,
|<;`'-.,,,_ ,-`` |[}{]| ``',
,` ; ```;;;;;;;;---------;;;;;;;;;|-,, ``--'' _,-'|
|`-,_ `'-.,,_,`********|[ !?! ]|********`| ``'''------''`` |
|`-,_`''-.,,,_ `````'''''---------'''''````| ] Ninja [ |
|`-,_`''-.,,, `````''''''---------'''''''```-,, _,-'
`-,_`''-.,,,[ ]``'''------''``
|<`|'-.,,,_[ ]_,,,.-|[}{]|
|<<| `````''''''---------'''''''````` |[}{]|
|<<| |]Aka: [| |[}{]|
|<<| ___,,| _____ |....--------------|[}{]|,,,,,,,,,,__
|<<|,,.--'''````` __| \ D / |....--------------|[}{]|,,,,,,,,_ `````'''--..,,_
_.-'`` ,,.--'````` | |A| | |[}{]| `````''-.,, ``'-.,
; -` | |T| |,.....----------..|[}{]|,,,_ `' ;
|`'-.,, `''-..,,,_.-`| |A| |******************|[}{]|****````-_,,,,,.--'` _,,-'`|
| ```''--..,,,,,_ ```````''''''''--------------''''''''``````` __,,,,..--''``` |
| ````````''''''''''--------------''''''''''```````` |
| |
Ninja
Avril 2006
Résumé
"Ninja Production Present" un autre texte sympa ou batard, selon le
lecteur, pour votre bénéfice personnel ou qui sera le "petit quelque chose"
qui vous manquait pour vous tirez devant un train. Sans plus tarder, comme
vous l'aurez surement remarqué, je vous ai préparé une implémentation,
TRÈS non complète, d'une couche d'abstraction pour les sockets sous windows
ET linux, w00t.
Tables des matières
1. Introduction
2. Pourquoi
3. Portabilité
3.1 Windows vs Linux
3.2 IPv4 vs IPv6
4. Comment
4.1 Fonctions de base
4.2 Autres fonctions
5. Code
6. Conclusion
1. Introduction
La définition simple et rapide d'un socket, selon moi no flame s.v.p., est
la suivante: une extrémité d'une liaison bidirectionnelle entre deux
programmes fonctionnant sur le réseau. Un socket est lié à un port de sorte
que la couche TCP, ou n'importe quelle couche "Transport" du modèle OSI,
puisse identifier à quelle application seront destinées les données reçues.
2. Pourquoi
Par lâcheté? Pour la réutilisation de code? <Entrez votre raison ici>? Les
sockets peuvent être un vrai "pain in the ass": Vérifier si c'est bien un
IP, version doted ou long, sinon faire la requête DNS pour obtenir le IP
associé au nom de l'hôte; vérifier si le port est valide; initialisation
du winsock (sous windows duh!); envoyer toutes les données d'un buffeur
quelconque; manipuler les sockets non bloquant avec un système de polling
[select()]; etc. Alors, pourquoi ne pas faire une fois pour toutes un
ensemble de fonctions qui vont faciliter ces tâches en réduisant le code à
produire lors de futures projets.
3. Portabilité
3.1 Windows vs Linux
Pour ceux qui ont eu l'occasion de jouer avec les sockets sur Linux ET
sur Windows, la manipulation des sockets est très similaire à quelques
détails près. L'ensemble des fonctions de base comme socket(), listen(),
recv(), send(), accept(), etc. sont présente sur les deux plateformes et
utilise les mêmes paramètres. Mais il y à quelques petites différences du
genre un socket sous linux est de type INT mais sous windows c'est un
UNSIGNED INT. Sous windows on doit absolument initialisé winsock avant
pouvoir manipuler un socket. De plus, certaines fonctions sous linux ne
se retrouvent pas sous windows ou ne prennent simplement pas les mêmes
arguments.
Il est donc désagréable, pour quelqu'un qui tente de créer une
application portable sur ces deux plateformes, d'écrire une application
qui utilise les sockets. Il serait donc plus pratique de se créer une
librairie de fonctions manipulant les sockets qui serait indépendante de
la plateforme sous laquelle elle est compilée. Dans ce cours texte, nous
allons nous concentrer uniquement sur linux et windows.
3.2 IPv4 vs IPv6
Malgré le fait que IPv6 ne soit pas grandement répandu, il se peut
qu'un jour nous aurons besoin de coder une application qui utilisera
IPv6. Comme IPv6 passe certains paramètres qui diffèrent de ce que IPv4
utilise généralement et certaines fonctions ne sont compatibles qu'avec
IPv4. Ces différences augmentent l'effort et le temps qu'un progammeur
doit investir lors du développement d'une application utilisant les
sockets. Nous allons nous concentrer seulement sur le proticol IPv4
puisqu'il s'agit simplement d'un texte explicatif/éducatif et il sera
plus facile de lire et comprendre le code en réduisant le nombre de
#ifdef, #ifndef, etc.
4. Comment
4.1 Fonctions de base
Il existe plusieurs fonctions pour la manipulation des sockets,
certaines sont essentielles, d'autre moins. Certaines sont
majoritairement utilisées comme support pour d'autres fonctions plus
primordiales. Voici donc la liste des fonctions de base que nous allons
tenter de porter:
socket() - crée un socket
bind() - attache un socket a une adresse et/ou un port
connect() - ouvre une connexion sur un hôte distant
listen() - écoute pour des connexions entrantes
accept() - autorise une connexion entrante
read() - lire les données reçues de l'hôte
write() - envoie des données à l'hôte
close() - ferme un socket
4.2 Autres fonctions
Toutes autres fonctions ne seront pas portées même si certaines seront
utilisées dans les wrapes que nous allons créer. Le but de ce texte
n'étant pas de fournir une librairie complète, mais bien de produire
un template sur lequel nous pourrons travailler et, idéalement,
améliorer.
Nous allons néanmoins créer quelques fonctions qui nous seront utiles
pour la manipulation des sockets. Elles serviront, entre autres, à
valider une adresse ip en format dotted (192.158.0.1), d'initialiser
l'environnement (majoritairement sous windows), de récupérer les erreurs
socket, etc.
5. Code
Nous voici donc au moment attendu, l'implémentation de notre librairie. Le
code sera divisé par fichier. Les délimiteurs suivants marque le début et la
fin du code d'un fichier:
=---------------------------BEGIN file.x --------------------------------=
=---------------------------END file.x ----------------------------------=
C'est un départ!
=---------------------------BEGIN nsocket.h -----------------------------=
/*
* fichier: nsocket.h
* auteur : ninja
* date : 13 May 2006
* desc :
* Déclaration des structutures et fonctions permettant une
* manipulation plus facile des sockets.
*
*/
#ifndef NSOCKET_H_INCLUDE
#define NSOCKET_H_INCLUDE
#if defined (_WIN32) || defined (WIN32)
#include <winsock2.h>
#else
#include <sys/types.h>
#include <sys/socket.h>
#define SOCKET int /* va nous permettre d'utiliser SOCKET comme type
sous linux. Sous win, SOCKET == unsigned int */
#define INVALID_SOCKET -1
#endif
#define NSOCKLISTENMAX 50 /* longueur max pour la file des connexionsen
attente*/
#define NSOCK_TCP 0x001 /* socket de type TCP. Defaut */
#define NSOCK_UDP 0x002 /* socket de type UDP. Defaut à TCP */
#define NSOCK_NONBLOCK 0x004 /* socket non-bloquant. Defaut à bloquant */
#define NSOCK_BIND 0x008 /* binder le socket. Defaut pas de bind() */
#define NSOCK_CLIENT 0x010 /* Defaut */
#define NSOCK_SERVER 0x020 /* socket en mode écoute */
#define NSOCK_CONNECTING 0x040 /* socket essaie de se connecter à un hôte */
#define NSOCK_CLOSED 0x100 /* socket fermé */
#define NSOCK_DEFAULT 0x011 /* NSOCK_TCP & NSOCK_CLIENT */
/* Permet de contrôler si nsocket doit afficher les erreur lui-même ou non */
extern int NSOCKET_DO_VERBOSE;
typedef struct nsocket_options_s nsocket_options_t, *pnsocket_options_t;
typedef struct nsocket_s nsocket_t, *pnsocket_t;
/* cette structure contiens toutes les infos de base niveau pour la manipulation
des socekts */
struct nsocket_options_s {
SOCKET sockfd; /* socket file descriptor */
int pf; /* protocol family - defaut: PF_INET */
int type; /* socket type - defaut: SOCK_STREAM */
int protocol; /* defaut: (IPPROTO_TCP) */
int af; /* address family - defaut: AF_INET */
struct sockaddr my_addr;
struct sockaddr peer_addr;
int flags; /* holds NSOCK_ flags */
};
/* contien les infos de haut niveau comme les ips en version dotted et les
ports en Host Bits Order */
struct nsocket_s {
char* peer_ip; /* remote ip */
unsigned int peer_port; /* remote port */
char* my_ip; /* my ip */
unsigned int my_port; /* my port */
struct nsocket_options_s opts;
};
/* fonctions pour la manipulation des sockets.
voir nsocket.c pour plus de détails sur leurs fonctionnalités */
int nsocket_init();
int nsocket_shutdown();
int nsocket_isvalideip(const char *ip);
int nsocket_get_error(pnsocket_t s);
int nsocket_set_nonblock(pnsocket_t s, unsigned long value);
pnsocket_t nsocket_create(int flags);
int nsocket_bind(pnsocket_t s, const char *my_ip, int my_port);
int nsocket_connect(pnsocket_t s, const char *peer_ip, int peer_port);
int nsocket_listen(pnsocket_t s);
pnsocket_t nsocket_accept(pnsocket_t s);
int nsocket_read(pnsocket_t s, char* data, int size);
int nsocket_write(pnsocket_t s, const char* data, int size);
int nsocket_close(pnsocket_t s);
int nsocket_free(pnsocket_t s);
int nsocket_opts_init(pnsocket_options_t opts);
int nsocket_opts_free(pnsocket_options_t opts);
void nsocket_perror(pnsocket_t s, const char *fname, const char *cname);
#endif /* not define NSOCKET_H_INCLUDE */
=---------------------------END nsocket.h -------------------------------=
=---------------------------BEGIN nsocket.c -----------------------------=
/*
* fichier: nsocket.c
* auteur : ninja
* date : 13 May 2006
* desc :
* Implémentation des fonctions permettant une manipulation plus facile
* des sockets.
*
*/
#if defined (_WIN32) || defined (WIN32)
#include <winsock2.h>
#else
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define _GNU_SOURCE /* needed if we want to use strdup */
#endif
#include <string.h> /* malloc(), free()[WIN32], _strdup()[WIN32] */
#include <stdlib.h> /* free() */
#include <stdio.h> /* printf(), stdout */
#include "nsocket.h"
#if defined (_WIN32) || defined (WIN32)
#define strdup _strdup
static WSADATA wsaData; /* si ça ne fonctionne pas */
/*static WSAData wsaData; /* alors essayez ceci */
#endif
/* Permet de contrôler si nsocket doit afficher les erreurs lui-même ou non.
Par défaut, nsocket affiche un message d'erreur */
int NSOCKET_DO_VERBOSE = 1;
/* nsocket_init()
* 0: w00t
* -1: operation fail
*
* Sous windows nous devons absolument initialiser l'environnement Winsock
* sinon tout appel aux fonctions de manipulation de socket va échouer.
*/
int nsocket_init() {
#if defined (_WIN32) || defined (WIN32)
WORD wVersionRequested; // version winsock demandé
wVersionRequested = MAKEWORD(2, 2);
if(WSAStartup(wVersionRequested, &wsaData) != 0)
return -1; /* aucune DLL winsock valide n'a été trouvé */
/* Vérifier si la DLL winsock support bien la version 2.2. */
/* Notez que même si la DLL support une version suppérieur à 2.2, tout en */
/* supportant la 2.2, la valeur retourner dans wVersion sera 2.2 puisque */
/* c'est ce que nous avons demandé. */
if ( LOBYTE( wsaData.wVersion ) != 2 || HIBYTE( wsaData.wVersion ) != 2 ) {
WSACleanup();
return -1; /* aucune DLL winsock valide n'a été trouvé */
}
#endif
return 0;
}
/* nsocket_shutdown()
* 0 : w00t
* non zero: operation fail
*
* Comme nous devons initialiser l'environnement Winsock avant l'utilisation
* de socket, nous devons aussi libérer/nettoyer l'environnement après
* utilisation.
*/
int nsocket_shutdown() {
#if defined (_WIN32) || defined (WIN32)
// on nettoie l'environnement winsock
return WSACleanup();
#else
return 0;
#endif
}
/* nsocket_isvalideip()
* -1: invalide dotted address
* 0: valide dotted address
*
* Valide si l'ip passé en paramètre est bien un ip en format dotted
* ex.: 127.0.0.1
*/
int nsocket_isvalideip(const char* ip)
{
#if defined (_WIN32) || defined (WIN32)
struct sockaddr addr;
int size;
/* initialise la mémoire a zéro */
memset(&addr,0,sizeof(addr));
addr.sa_family = AF_INET; /* si on ne spécifie pas, WSAStringToAddress
échoue avec WSAEINVAL (10022) */
size = sizeof(addr);
if (WSAStringToAddress((LPTSTR)ip,AF_INET,NULL,&addr,&size) == 0)
return 0;
#else
struct in_addr iaddr4;
/* nous utilisons inet_pton même si il ne s'agit pas d'un IPv4 puisque
cette fonction supporte IPv6 et IPv4. De plus elle est fortement
recommandée au lieu de inet_aton */
if (inet_pton(AF_INET,ip,&iaddr4) > 0)
return 0;
#endif
/* ip invalide */
return -1;
}
/* nsocket_get_error()
* retourne le code d'erreur du socket
*/
int nsocket_get_error(pnsocket_t s)
{
int size, err;
if(s == NULL)
return 0;
size = sizeof(int);
err = 0;
getsockopt(s->opts.sockfd, SOL_SOCKET, SO_ERROR, (char*)&err, &size);
return(err);
}
/* nsocket_set_nonblock()
* 0 : w00t
* non zero: operation fail
*
* Nous permet de mettre, ou non, le socket en mode non bloquant
*/
int nsocket_set_nonblock(pnsocket_t s, unsigned long value)
{
if(s == NULL)
return 0;
#if defined (_WIN32) || defined (WIN32)
return ioctlsocket(s->opts.sockfd, FIONBIO, &value);
#else
int oldflags = fcntl(s->opts.sockfd, F_GETFL, 0);
if (oldflags == -1) return -1;
if (value != 0) oldflags |= O_NONBLOCK;
else oldflags &= ~O_NONBLOCK;
return fcntl(s->opts.sockfd, F_SETFL, oldflags);
#endif
}
/* nsocket_create
* pnsocket: pointer sur le nouveau socket
* NULL: Erreur
*/
pnsocket_t nsocket_create(int flags) {
pnsocket_t s;
int i;
/* Sanitarisation (w00t nouveau terme) des flags ...*/
if(flags & NSOCK_UDP && flags & NSOCK_TCP) flags &= ~NSOCK_UDP;
if(flags & NSOCK_SERVER && flags & NSOCK_CLIENT) flags &= ~NSOCK_SERVER;
flags &= ~NSOCK_CONNECTING;
flags |= NSOCK_CLOSED;
/* alloue la mémoire pour le nouveau socket */
s = (pnsocket_t)malloc(sizeof(nsocket_t));
/* Initialisation des données membres... */
s->peer_ip = NULL;
s->peer_port = 0;
s->my_ip = NULL;
s->my_port = 0;
/* Initialisation des options... */
nsocket_opts_init(&s->opts);
/* Copie des flags demandé... */
s->opts.flags = flags;
/* Est-ce que le socket doit être UDP ou TCP */
if(flags & NSOCK_UDP) {
s->opts.type = SOCK_DGRAM;
s->opts.protocol = IPPROTO_UDP;
}
else {
s->opts.type = SOCK_STREAM;
s->opts.protocol = IPPROTO_TCP;
}
/* Création du socket */
if ((s->opts.sockfd = socket(s->opts.af, s->opts.type,
s->opts.protocol)) == INVALID_SOCKET) {
nsocket_perror(s, "nsocket_create()", "socket");
return NULL;
}
/* Est-ce que nous voulons un socket non bloquant? */
if (flags & NSOCK_NONBLOCK)
i=1;
else
i=0;
if(nsocket_set_nonblock(s, i) !=0) {
nsocket_perror(s, "nsocket_create()", "nsocket_set_nonblock");
return NULL;
}
return(s);
}
/* nsocket_bind
* -1 : une erreur c'est produite
* 0 : nsocket invalide ou le bit NSOCK_BIND n'est pas présent
* 1 : w00t
*/
int nsocket_bind(pnsocket_t s, const char *my_ip, int my_port) {
#if defined (_WIN32) || defined (WIN32)
struct sockaddr addr;
int addr_len;
#else
struct in_addr addr4;
#endif
int yes;
if(s == NULL)
return 0;
if (!(s->opts.flags & NSOCK_BIND || s->opts.flags & NSOCK_SERVER))
return 0;
/* Vérifie si le ip est valide */
if(nsocket_isvalideip(my_ip) != 0) {
nsocket_perror(s, "nsocket_bind()", "nsocket_isvalideip");
return -1;
}
/* Vérifie si le port de l'hôte est valide */
if (my_port < 0 || my_port > 65535) {
nsocket_perror(s,"nsocket_bind()", "port invalide");
return -1;
}
/* Libère l'espace mémoire si déjà alloué... */
if(s->my_ip) free(s->my_ip);
/* Archivage du ip et du port... */
s->my_ip = strdup(my_ip);
s->my_port = my_port;
/* On veut utiliser le port même si il est pris par une autre application.
Surtout utile pour un socket en mode listen */
yes=1;
setsockopt(s->opts.sockfd,SOL_SOCKET,SO_REUSEADDR,
(char*)&yes,sizeof(int));
/* !!! ATTENTION !!!
Le code qui suit est EXTRÊMEMENT laid, et est juste un big hack pour
contourner certains problèmes d'assignation et de typecasting.
Vous avez été prévenu...
<hack type="laid" size="gros">
*/
((struct sockaddr_in*)&s->opts.my_addr)->sin_family = AF_INET;
((struct sockaddr_in*)&s->opts.my_addr)->sin_port =
htons((u_short)my_port);
#if defined (_WIN32) || defined (WIN32)
memset(&addr,0,sizeof(addr));
((struct sockaddr_in*)&addr)->sin_family = AF_INET;
addr_len = sizeof(addr);
if (WSAStringToAddress(s->my_ip,s->opts.af,NULL,&addr,&addr_len) != 0)
return -1; /* ne devrait jamais se produire */
((struct sockaddr_in*)&s->opts.my_addr)->sin_addr =
((struct sockaddr_in*)&addr)->sin_addr;
#else
if (inet_pton(AF_INET,s->my_ip,&addr4) <= 0)
return -1; /* ne devrait jamais se produire */
((struct sockaddr_in*)&s->opts.my_addr)->sin_addr = addr4;
#endif
/* </hack> */
if (bind(s->opts.sockfd, &s->opts.my_addr,
sizeof(s->opts.my_addr)) == -1) {
nsocket_perror(s, "nsocket_bind()", "bind");
return -1;
}
return 1;
}
/* nsocket_connect
* -1: une erreur est survenue lors de la tentative/processus de connexion
* 0: erreur, nsocket invalide ou nsocket en mode SERVER (listen)
* 1: w00t
*/
int nsocket_connect(pnsocket_t s, const char *peer_ip, int peer_port) {
#if defined (_WIN32) || defined (WIN32)
struct sockaddr addr;
int addr_len;
#else
struct in_addr addr4;
#endif
if(s == NULL)
return 0;
if (!(s->opts.flags & NSOCK_CLIENT))
return 0;
/* Vérifie si le ip de l'hôte est valide */
if(nsocket_isvalideip(peer_ip) != 0) {
nsocket_perror(s, "nsocket_connect()", "nsocket_isvalideip");
return -1;
}
/* Vérifie si le port de l'hôte est valide */
if (peer_port < 0 || peer_port > 65535) {
nsocket_perror(s,"nsocket_connect()", "port invalide");
return -1;
}
/* Libère l'espace mémoire si déjà alloué... */
if(s->peer_ip) free(s->peer_ip);
/* Archivage du ip et du port... */
s->peer_ip = strdup(peer_ip);
s->peer_port = peer_port;
/* !!! ATTENTION !!!
Le code qui suit est EXTRÊMEMENT laid, et est juste un big hack pour
contourner certains problèmes d'assignation et de typecasting.
Vous avez été prévenu...
<hack type="laid" size="gros">
*/
((struct sockaddr_in*)&s->opts.peer_addr)->sin_family = AF_INET;
((struct sockaddr_in*)&s->opts.peer_addr)->sin_port =
htons((u_short)peer_port);
#if defined (_WIN32) || defined (WIN32)
memset(&addr,0,sizeof(addr));
((struct sockaddr_in*)&addr)->sin_family = AF_INET;
addr_len = sizeof(addr);
if (WSAStringToAddress(s->peer_ip,s->opts.af,NULL,&addr,&addr_len) != 0)
return -1; /* ne devrait jamais se produire */
((struct sockaddr_in*)&s->opts.peer_addr)->sin_addr =
((struct sockaddr_in*)&addr)->sin_addr;
#else
if (inet_pton(AF_INET,s->peer_ip,&addr4) <= 0)
return -1; /* ne devrait jamais se produire */
((struct sockaddr_in*)&s->opts.peer_addr)->sin_addr = addr4;
#endif
/* </hack> */
s->opts.flags |= NSOCK_CONNECTING; /* connexion en cours */
if (connect(s->opts.sockfd, &s->opts.peer_addr,
sizeof(s->opts.peer_addr)) == -1) {
/* On ne peut attendre que connect() retourne une error si nous
avons configurer le socket en mode non bloquant puisque connect()
va toujours retourner une erreur: EINPROGRESS (linux)
ou WSAEWOULBLOCK (win32) */
if (s->opts.flags & NSOCK_NONBLOCK &&
#if defined (_WIN32) || defined (WIN32)
WSAGetLastError() == WSAEWOULDBLOCK
#else
errno == EINPROGRESS
#endif
)
{
s->opts.flags &= ~NSOCK_CLOSED; /* pas vraiment l'endroit idéal
pour le faire mais ça doit
être fait */
return 1;
}
nsocket_perror(s, "nsocket_connect()", "connect");
return -1;
}
/* si nous somme ici, c'est que le socket en en mode bloquant donc
connect() à réussie */
s->opts.flags &= ~NSOCK_CONNECTING;
s->opts.flags &= ~NSOCK_CLOSED;
return 1;
}
/* nsocket_listen()
* -1 : une erreur c'est produite
* 0 : nsocket invalide où le bit NSOCK_SERVER n'est pas présent
* 1 : w00t
*/
int nsocket_listen(pnsocket_t s) {
if(s == NULL)
return 0;
if (!(s->opts.flags & NSOCK_SERVER))
return 0;
if (listen(s->opts.sockfd, NSOCKLISTENMAX) == -1) {
nsocket_perror(s, "nsocket_listen()", "listen");
return -1;
}
s->opts.flags &= ~NSOCK_CLOSED; /* l'appel à listen() a réussie,
donc le socket n'est plus dans
l'état 'fermé' */
return 1;
}
/* nsocket_accept
* pnsocket: pointer sur le nouveau socket
* NULL: Erreur
*/
pnsocket_t nsocket_accept(pnsocket_t s) {
pnsocket_t new_sock;
int sin_size, flags;
if(s == NULL)
return NULL;
flags = NSOCK_DEFAULT;
if (s->opts.flags & NSOCK_UDP)
return NULL; /* accept ne peut être appelé sur des socket UPD */
if (s->opts.flags & NSOCK_NONBLOCK)
flags |= NSOCK_NONBLOCK;
if((new_sock = nsocket_create(flags)) == NULL)
return NULL;
sin_size = sizeof(struct sockaddr);
if ((new_sock->opts.sockfd = accept(s->opts.sockfd,
&new_sock->opts.peer_addr, &sin_size)) == -1) {
nsocket_perror(s,"nsocket_accept()","accept");
return NULL;
}
new_sock->opts.flags &= ~NSOCK_CLOSED; /* l'appel à accept() a réussie,
donc le socket n'est plus dans
l'état 'fermé' */
return new_sock;
}
/* nsocket_read
* -1: une erreur c'est produite
* 0: connexion fermé par l'hôte
* >0: nombre d'octects reçus
*/
int nsocket_read(pnsocket_t s, char* data, int size) {
int nbytes;
if(s == NULL)
return -1;
if (!data || size <= 0) /* si mémoire non allouée ou taille invalide */
return 0;
if ((nbytes=recv(s->opts.sockfd, data, size, 0)) == -1) {
/* si notre socket est en mode non bloquant qu'il n'y a aucune données
dans la file d'attente du socket, recv retourne WSEWOULDBLOCK (win)
ou EAGAIN (linux) */
if ((s->opts.flags & NSOCK_NONBLOCK) &&
#if defined (_WIN32) || defined (WIN32)
WSAGetLastError() == WSAEWOULDBLOCK
#else
errno == EAGAIN
#endif
)
return -1; /* on retourne -1 mai ce n'est pas une erreur critique
il faut donc vérifier WSAGetLastError ou errno pour
s'assurer qu'il s'agit bien de WSAEWOULDBLOCK ou
EAGAIN
Je sais ce n'est pas très pratique, parce que VOUS
devez faire la vérification, mais je vais penser à
quelque chose pour arranger ça plus tard */
nsocket_perror(s, "nsocket_read()", "recv");
#if defined (_WIN32) || defined (WIN32)
if (WSAGetLastError() == WSAECONNRESET) /* connection reset
Je n'ai pas chercher pour
l'équivalent linux encore */
nsocket_close(s);
#endif
return(-1);
}
if (nbytes == 0) { /* connection closed by peer */
nsocket_perror(s, "nsocket_read()", "recv [conn. closed]");
nsocket_close(s);
return(0);
}
return(nbytes);
}
/* nsocket_write
* -1: une erreur c'est produite
* n: nombre d'otects envoyés
*/
int nsocket_write(pnsocket_t s, const char* data, int size) {
int n;
if(s == NULL)
return -1;
if (!data || size <= 0) /* si aucune donnée ou taille invalide */
return 0;
if((n = send(s->opts.sockfd, data, size, 0)) == -1) {
nsocket_perror(s, "sock_got_write()", "send");
return(-1);
}
return(n);
}
/* nsocket_close()
* -1: une erreur s?est produite
* 0: w00t
*/
int nsocket_close(pnsocket_t s) {
if(s == NULL)
return 0;
if(s->opts.sockfd == INVALID_SOCKET)
return 0;
if(s->opts.flags & NSOCK_CLOSED)
return 0;
#if defined (_WIN32) || defined (WIN32)
if (shutdown(s->opts.sockfd, SD_BOTH) == -1) {
#else
if (shutdown(s->opts.sockfd, SHUT_RDWR) == -1) {
#endif
nsocket_perror(s, "nsocket_close()", "shutdown");
return(-1);
}
#if defined (_WIN32) || defined (WIN32)
if (closesocket(s->opts.sockfd) == -1) {
#else
if (close(s->opts.sockfd) == -1) {
#endif
nsocket_perror(s, "nsocket_close()", "close");
return(-1);
}
nsocket_opts_free(&s->opts);
return(0);
}
/* nsocket_free
* -1: pointer invalide
* 0: w00t
*
* Libère un socket créer via nsocket_create ou nsocket_accept
*/
int nsocket_free(pnsocket_t s) {
if(!s)
return(-1);
if(s->peer_ip) free(s->peer_ip);
s->peer_ip = NULL;
s->peer_port = 0;
if(s->my_ip) free(s->my_ip);
s->my_ip = NULL;
s->my_port = 0;
free(s);
s = NULL;
return(0);
}
/* nsocket_opts_init
* -1: nsocket_options_t invalide
* 0: w00t
*/
int nsocket_opts_init(pnsocket_options_t opts) {
if (!opts)
return -1;
opts->sockfd = INVALID_SOCKET;
opts->pf = PF_INET;
opts->type = SOCK_STREAM;
opts->protocol = IPPROTO_TCP;
opts->af = AF_INET;
memset(&(opts->my_addr), '\0', sizeof(opts->my_addr));
memset(&(opts->peer_addr), '\0', sizeof(opts->peer_addr));
opts->flags = NSOCK_CLOSED;
return 0;
}
/* nsocket_opts_free
* -1: nsocket_options_t invalide
* 0: w00t
*/
int nsocket_opts_free(pnsocket_options_t opts) {
if (!opts)
return -1;
memset(opts, '\0', sizeof(nsocket_options_t));
return 0;
}
/* nsocket_perror()
* print errno, socket error code and on win32 platform, WSAGetLastError()
*/
void nsocket_perror(pnsocket_t s, const char *fname, const char *cname) {
if(s == NULL)
return;
if (NSOCKET_DO_VERBOSE)
#if defined (_WIN32) || defined (WIN32)
printf("\n\t[E] %s: %s - errno: %d SO_ERROR: %d WSAGetLastError: %d\n",
fname,cname,errno,nsocket_get_error(s),WSAGetLastError());
#else
printf("\n\t[E] %s: %s - errno: %d SO_ERROR: %d\n",fname,cname,errno,
nsocket_get_error(s));
#endif
}
=---------------------------END nsocket.c -------------------------------=
=---------------------------BEGIN test.c --------------------------------=
#include <stdio.h>
#include <string.h>
#include "nsocket.h"
int main(void) {
pnsocket_t s;
char buf[1024];
int nbytes;
/* initialisation */
if(nsocket_init() ==-1)
return(-1);
/* création du socket */
if ((s = nsocket_create(NSOCK_DEFAULT)) == NULL)
return(-1);
/* on lance le connect */
if (nsocket_connect(s,"72.14.203.104",80) == -1) {
nsocket_perror(s,"main()","nsocket_connect()");
nsocket_close(s);
nsocket_free(s);
return(-1);
}
strcpy(buf, "GET / HTTP/1.0\n\n");
if (nsocket_write(s,buf,strlen(buf)) == -1) {
nsocket_close(s);
nsocket_free(s);
return(-1);
}
if ((nbytes = nsocket_read(s,buf,1024)) == -1) {
nsocket_close(s);
nsocket_free(s);
return(-1);
}
fwrite("[Read]\n",sizeof(char),7,stdout);
if (nbytes)
fwrite(buf,sizeof(char),nbytes,stdout);
/* on ferme le socket */
nsocket_close(s);
nsocket_free(s);
/* clean up */
nsocket_shutdown();
return(0);
}
=---------------------------END test.c ----------------------------------=
6. Conclusion
C'est la partie que je déteste probablement le plus, alors je vais faire ça
"sweet and short " comme diraient nos copains anglophones. Si vous trouvez
cet article intéressant, utile ou que vous l'avez détesté; faites-moi-le
savoir. Bien sûr, si vous n'avez pas aimé dites-moi au moins pourquoi, de
façon constructive et non pas juste un chargement d'insulte. Croyez-moi, je
sais à qui parler pour en avoir. Sur ce, à une prochaine fois. Oh! Juste
comme ça, en passant. Ce code est loin d'être parfait. Je l'ai testé
rapidement sur windows et linux mais il y a surement de l'optimisation à
faire et peut-être un ou deux bugs. Mais le but de ce texte n'est que de
vous donner un exemple ou une base pour un éventuel re-write.