Bonjour,

Je m'arrache les cheveux depuis un certain temps sur les sockets UDP en mode broadcast, car le comportement est différent d'une plateforme à l'autre.

Je précise que je tourne sur :

- windows ( XP / 7 )
- linux ( ex : opensuse 11.4 )
- winCE 6.0 et 7.0
- iOS version 5.0.1 et 6.0.1

( android sera la prochaine étape ).

J'ai implémenté un système de socket générique sur toutes ces plateforme, mais je rencontre des difficultés sur la partie broadcast car rien ne marche comme il faut.

Par exemple :

- win et wince ont le même comportement. Jusque là, normal, c'est du microsoft. Sauf que contrairement à toutes les autres plateformes, il faut écouter ( bind ) sur l'adresse de l'interface et non sur l'adresse broadcast.
- sous linux et sous win/wince , on peut envoyer et recevoir en broadcast sur la même socket. Sous iOS , impossible.

Je donne un exemple de code :

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
 
int s_recv = socket( AF_INET , SOCK_DGRAM , IPPROTO_UDP );
int s_send = socket( AF_INET , SOCK_DGRAM , IPPROTO_UDP );
 
int ret ;
 
ULONG hote_l =INADDR_NONE;
 
hote_l = inet_addr( "172.17.30.1" );
struct sockaddr_in adresse_send;
memset ( &adresse_send , 0 , sizeof( adresse_send ) );
adresse_send.sin_family		= AF_INET;
adresse_send.sin_addr.s_addr = hote_l;
adresse_send.sin_port		= htons ( 65003 ); 
 
hote_l = inet_addr( "172.17.255.255" );
struct sockaddr_in adresse_recv;
memset ( &adresse_recv , 0 , sizeof( adresse_recv ) );
adresse_recv.sin_family		= AF_INET;
adresse_recv.sin_addr.s_addr = hote_l;
adresse_recv.sin_port		= htons ( 65003 ); 
 
int oui = 1;
 
ret = setsockopt( s_send , SOL_SOCKET, SO_BROADCAST, (char *)&oui, sizeof(oui));
 
if ( ret < 0 )
{
    TRACE(("setsockopt broadcast %d send" , errno ));
}
 
ret = setsockopt( s_recv , SOL_SOCKET, SO_BROADCAST, (char *)&oui, sizeof(oui));
 
if ( ret < 0 )
{
    TRACE(("setsockopt broadcast %d recv" , errno ));
}
 
ret = bind( s_send , (struct sockaddr *) &adresse_send , sizeof (adresse_send ) );
 
if ( ret < 0 )
{
    TRACE(("bind for send %d" , errno ));
}
else
{
    TRACE(("bind for send OK"));
}
 
ret = bind( s_recv , ( struct sockaddr *) &adresse_recv , sizeof ( adresse_recv ));
 
if ( ret < 0 )
{
    TRACE(("bind for receive %d" , errno ));
}
else
{
    TRACE(("bind for receive OK"));
}
while ( 1 )
{
    fd_set      fdin;
    timeval     tv;
 
    char buf[256];
 
    int data	= 0;
    int recu	= 0;
 
    FD_ZERO(&fdin);
    FD_SET( s_recv , &fdin);
    tv.tv_sec = 2 ;
    tv.tv_usec = 0 ;
 
    data = select(((int)s_recv)+1, &fdin, NULL, NULL, &tv);
 
    if (data == -1)
    {
        TRACE((" Erreur select : %d",errno));
    }
    else if (FD_ISSET(s_recv, &fdin) != 0)
    {
        socklen_t   ret_emetteur_len = sizeof(struct sockaddr_in);
        struct sockaddr_in ret_emetteur;
 
        recu = recvfrom( s_recv , buf, 256, 0, (struct sockaddr*) &ret_emetteur, &ret_emetteur_len );
 
        TRACE(("recu %s (%d)" , buf+12, recu));
    }
    else
    {
        TRACE(("rien recu"));
    }
 
    sprintf(buf,"aurevoir");
 
    ret = sendto( s_send , buf , 256 , 0 , (struct sockaddr *) &adresse_recv , sizeof ( adresse_recv ));
 
    if ( ret > 0)
    {
        TRACE(("envoye %d" , ret ));
    }
    else
    {
        TRACE(("Erreur envoi %d %d" , ret , errno));
    }
    milli_sleep(1000);
}
 
close( s_send );
close( s_recv );
iOS :

Ce code est obligatoire si je veux envoyer et recevoir en broadcast sur le même port UDP. j'envoie sur la socket s_send, et je reçois sur la socket s_recv. Petit ( gros ) inconvénient : ce que j'envoie sur s_send, je ne le reçoit pas sur s_recv ( ca risque de m'être utile dans un futur proche ).

Si je transforme ce code pour écouter et recevoir sur une même socket :

- si je fais le bind sur l'adresse d'interface ( ici 172.17.30.1 dans l'exemple ) : je ne reçois rien ( select retourne 0 ) . Normal me direz-vous, le bind sert à dire qui on veut écouter, donc si on met sa propre adresse, on ne va s'écouter que soi-même et donc ne pas recevoir les broadcast. Sauf que c'est ce qu'il faut mettre sous win/wince pour recevoir en broadcast, bref ... L'envoi se passe bien en broadcast par contre à condition qu'on fasse un sendto vers l'adresse broadcast ( et positionner avec setsockopt la socket en mode broadcast ).

- si je fais le bind sur l'adresse broadcast ( ici 172.17.255.255 ) - comme on devrait le faire sur tout système POSIX , donc linux ( qui marche d'ailleurs ) - je reçois bien les broadcast. Normal, c'est ce qu'il faut faire. Sauf que le sendto cette fois-ci ne marche plus , j'ai un errno = 49 ( can't assign address ).

De plus, je ne peux pas recevoir mes propres packets UDP avec cette méthode.

Résumé : si je n'utilise qu'une socket, je ne peux qu'envoyer, ou recevoir, mais pas les 2, il faut que je crée 2 sockets comme dans l'exemple.


Linux :

Ce code ( avec les 2 sockets ) fonctionne sous linux de la même façon que sous iOS. Logique on va dire.

Sauf que je peux également écouter et recevoir en broadcast avec juste une socket : celle qui va faire un bind sur l'adresse broadcast. Dans ce cas, le recv fonctionne bien, et le send vers 172.17.255.255 aussi. Comportement différent de iOS, donc.

Si par contre j'utilise l'adresse interface pour faire le bind, le recv ne marche plus, normal pour les raisons vues plus haut.

Avec ce code, je ne peux pas recevoir mes propres packets UDP.

Par contre, avec une seule socket ecoute et envoi sur le même port UDP, je reçois mes propres paquets par défaut ( je peux les trier en regardant l'adresse émetteur ).

Win/WinCE :

Ce code ne marche pas du tout. Tout simplement parce que faire un bind sur l'adresse broadcast renvoie une erreur sous win/wince. Il faut donc faire le bind sur l'adresse de l'interface ET positionner la socket en broadcast, pour envoyer et recevoir des broadcast. Comportement différent de toutes les autres plateformes.

je ne peux pas non plus écouter sur 2 sockets ( les 2 sur l'adresse d'interface ) , 1 pour envoyer, et 1 pour recevoir. La 2eme ne fonctionne plus.

Je peux optionnellement ( via un appel système dont je ne me souviens plus la syntaxe ) choisir de recevoir ou non mes propres paquets UDP.


mes questions :

Y-a-t-il des paramètres que j'aurais pu oublier afin de pouvoir envoyer et recevoir en broadcast sur une même socket sous iOS ? J'ai essayé toutes les combinaisons d'appels et de paramètre, je n'obtiens pas de résultat sans passer par 2 sockets. Du coup ca m'embete car je ne sais pas si c'est une façon propre de faire d'une part , et d'autre part , je ne sais pas quelles sont les conséquences d'une telle façon de faire. De plus, j'ai l'impression que je ne vais pas pouvoir recevoir mes propres paquets broadcast , or, je risque d'avoir besoin de la fonctionnalité.


Y-a-t-il une raison autre que "c'est microsoft" sur le fait qu'il faille faire un bind sur l'adresse de l'interface uniquement sous win/wince , et sur l'adresse broadcast sur toutes les autres plateformes, pour recevoir des paquets provenant d'une adresse broadcast ?


Merci de m'avoir lu jusque là, en espérant des réponses