Citation:
Envoyé par ram-0000
Au fait, si tu veux être portable, utilise recv() et non read() pour tes sockets...
Version imprimable
Citation:
Envoyé par ram-0000
Au fait, si tu veux être portable, utilise recv() et non read() pour tes sockets...
La norme POSIX permet d'utiliser read() sur une socket. Les sockets Berkeley reposent sur recv() et send(), garanties marcher sur des sockets.
read() ne marche que sur les systèmes où les sockets sont des descripteurs. En clair, pas sous Windows, alors que Windows respecte l'interface des sockets Berkeley.
Pourquoi ?
Il y a une API portable (sockets BSD), pourquoi ne pas l'utiliser ? De plus il y a un paramètre de plus qui pourrait avoir son rôle à jouer un jour ou l'autre... Utiliser read() et write() est un détournement de l'utilisation des sockets. Ce n'est pas la manière normale de procéder telle qu'elle a été conçue par BSD.
Une fois de plus, ce n'est pas parce que quelque chose est possible qu'elle est souhaitable.
P.S. Es-tu bien sûr que POSIX.1 définisse l'usage de read() et write() dans le cadre des sockets ?
Ici : http://www.opengroup.org/onlinepubs/.../socket.h.html
je ne vois pas de read() / write() ...
de même ici http://opengroup.org/onlinepubs/007908775/xsh/read.html
je ne vois pas de sockets.
Tu fais fausse route, les sockets Berkeley n'ont jamais été normalisé, ce n'est qu'une implémentation (reprise par Microsoft soit) mais pas une norme.
POSIX utilise le "fichier" comme abstraction, tout est fichier, si on regarde dans la norme on trouve :
alors que :Citation:
pread, read - read from a file
...
If fildes refers to a socket, read() shall be equivalent to recv() with no flags set.
Donc sauf rare cas, pour être le plus générique possible, mieux vaut utiliser read() et write().Citation:
recv - receive a message from a connected socket
Cela est tout à fait normalisé, les fonctions que tu cites sont celles qui date des premiers BSD, POSIX ne fait que les reprendre, et ajoute la notion de généricité via les "fichiers".
Citation:
2.10.7 Socket I/O Mode
The I/O mode of a socket is described by the O_NONBLOCK file status flag which pertains to the open file description for the socket. This flag is initially off when a socket is created, but may be set and cleared by the use of the F_SETFL command of the fcntl() function.
When the O_NONBLOCK flag is set, functions that would normally block until they are complete shall either return immediately with an error, or shall complete asynchronously to the execution of the calling process. Data transfer operations (the read(), write(), send(), and recv() functions) shall complete immediately, transfer only as much as is available, and then return without blocking, or return an error indicating that no transfer could be made without blocking. The connect() function initiates a connection and shall return without blocking when O_NONBLOCK is set; it shall return the error [EINPROGRESS] to indicate that the connection was initiated successfully, but that it has not yet completed.
C'est malheureusement une idée reçue mais fausse. Le X de POSIX n'est là que pour rappeler le fait que la plupart des fonctions viennent du monde UNIX, mais il ne veut pas dire "pour UNIX", le plus important est Portable Operating system Interface, d'ailleurs, le commité de normalisation fait le maximum pour retirer ces ambiguïtés des noms, comme pour AF_UNIX qui est devenu AF_LOCAL, mais ce n'est pas toujours aussi simple.
Le lien que tu as donné est aussi un argument contre toi, il est par définition spécifique aux sockets, alors que read() et write ne le sont pas, elles peuvent être utilisé génériquement sur tout type de file descriptor. Si demain, pour une raison x ou y, ton file descriptor ne pointe plus sur une socket mais un pipe ou un fichier sur disque, tu es bon pour modifier ton code.
et toi du code non générique!
Dans mon code, je remplace le socket() par un open() ou encore un pipe() peu importe, je passe le descripteur à une fonction qui va s'occuper des entrées sorties. Avec ta proposition, tu es bon pour réécrire la fonction qui s'occupe des I/O, avec la mienne, aucun problème. C'est d'ailleurs le genre de problématique que je dois souvent gérer au boulot, et heureusement que je reste générique (et portable).
Tu n'es pas un habitué d'UNIX, il n'y a pas besoin d'invoquer socket() pour avoir un descripteur qui pointe sur une socket, l'exemple le plus souvent rencontré est STDIN_FILENO, suivant comment ton programme est invoqué, ça peut être un fichier, un pipe, une socket, bref pratiquement tout. A partir de là, si on doit s'amuser à déterminer le type sous jacent puis invoquer un fonction d'entrée sortie différente en fonction de celui ci, on n'est pas sortie de l'auberge.
@Emmanuel: Le débat ici, c'est entre la "généricité sous POSIX" et la "portabilité avec les systèmes non-POSIX respectant Berkeley" (en clair: Windows, et peut-être certains systèmes embarqués).
Sous POSIX, tu peux passer un socket à une fonction déjà existante qui ferait quelque chose sur un descripteur (ou à fdopen()).
Code:
1
2
3
4
5
6
7
8 void MaFonction(int fd) { write(fd, "toto", 4); } ... MaFonction(connectedSocket);
Je comprends cet argument et reconnais sa validité, il s'agit ensuite de peser son importance face à celui de la compatibilité Windows.
Disons que si tu fais déjà quelque chose de spécifique à POSIX (du genre un select() avec un descripteur autre qu'un socket), tu n'es plus à ça près...
PS: Il serait intéressant de voir si la fonction Microsoft _open_osfhandle() marche sur les sockets. Car un socket n'est pas exactement un handle non plus (DuplicateHandle() marchotte dessus, mais pose des problèmes et son usage sur un socket est désormais déprécié).
Je remonte ce thread pour conclure que non, ça ne marche pas:
WriteFile() sur un socket échoue avec ERROR_INVALID_PARAMETER (87), et write() utilise WriteFile()...
Un socket n'est donc pas assez "handle" pour être utilisé avec _open_osfhandle() & co...
De toute façon, il y aurait eu des problème à la fermeture, vu que close() appelle CloseHandle() et que CloseHandle() ne doit pas être utilisée sur les sockets...
Merci pour l'info en tout cas.
Pour participer un peu au débat, et pour faire une sorte de synthèse (ou de résumé) :
- send et recv ont des arguments en plus par rapport à read et write. C'est clair qu'ils sont plus adaptés au sockets et permettent des choses que read et write ne permettent pas.
- send et recv sont utilisables sur un socket sous les systèmes respectant la norme POSIX comme sous certains systèmes qui ne la respectent pas (un exemple bien connu : Windows). read et write sont utilisables sur un socket que sous les systèmes qui respectent la norme POSIX (en pratique : UNIX et ses clones). La encore, je trouve donc plus intéressant d'utiliser send et recv plutôt que read et write sur uns ocket.
- Sur un système respectant la norme POSIX, read et write, qui sont les fonctions d'E/S de plus bas niveau au niveau de l'API, peuvent être utilisées sur tout ce qui peut être lu ou écrit (fichier, tube, socket, mémoire, etc.). send et recv, POSIX ou pas POSIX, ne peuvent être utilisées que sur un socket. Lorsqu'on veut avoir du code générique pour un système POSIX, il faut donc en effet utiliser read et write.
En ce qui concerne les sockets sous Windows, Microsoft dit clairement qu'un handle de socket n'est pas forcément un handle de fichier. Il n'est donc pas garanti qu'on peut utiliser _open_osfhandle sur un socket dans le but de pouvoir utiliser les fonctions _read et _write.
Je pense qu'hormis le fait que le système soit POSIX ou pas les fonctions revc/send ont un paramètre en plus contrairement à read/write, ce dernier d'après ce que j'ai compris est souvent mis à zéro, donc j'aurais tendance à utiliser read/write et réserver recv/send pour les usages nécessitant ce fameux paramètres, pour la portabilité j'ajoute ceci:
Code:
1
2
3
4 #ifdef _WIN32 # define write(fd, buf, len) send(fd, buf, len, 0) # define read(fd, buf, len) recv(fd, buf, len, 0) #endif /* _WIN32 */
En ce qui concerne le dernier paramètre souvent mis à 0, c'est vrai pour send(). Pour recv, Le flag MSG_PEEK est au moins aussi utilisé que 0. Enfin, faire usage des deux en même temps c'est pire qu'utiliser des fonctions non portables, non génériques, mais homogènes (les WSASend/WSARecv par exemple).Citation:
les fonctions revc/send ont un paramètre en plus contrairement à read/write, ce dernier d'après ce que j'ai compris est souvent mis à zéro, donc j'aurais tendance à utiliser read/write et réserver recv/send pour les usages nécessitant ce fameux paramètres
est-on certain que "derrière le rideau" read/write n'appelent pas recv/send quand le fd est un socket ?
(sous autre chose que Windows… évidemment…)
Oui. read et write ce sont les fonctions d'E/S de plus bas niveau de l'API UNIX. Toutes les autres fonctions d'E/S (celles de la lib standard, celles de l'API Socket, etc.) y font appel (ou devraient pouvoir être implémentés en y faisant appel).
tel que vous exprimez les choses, cela sous-entendrait que recv/send appellent read/write… ou autrement dit qu'une fonction spécialisée sur les sockets appelle une fonction générale qui prend n'importe quel type de FD…
ce qui laisse planer un sérieux doute…
en fait recv/send appellent directement recvfrom/sendto qui sont aussi des syscall tout comme read/write…
Tu n'as pas compris. read et write, je rappelle, ce sont les fonctions d'E/S génériques de plus bas niveau du système (UNIX). Elles ne font que lire/écrire des octets de manière la plus directe qui existe depuis/sur le fichier (qui peut être un fichier sur disque, un périphérique, un tube, un socket, etc.). send et recv ont un paramètre en plus par rapport à read et write ce qui prouve qu'elles sont de plus haut niveau et éventuellement utilisables que sur un type particulier de fichier (ici : les sockets seulement). Pour comprendre le lien qu'il y a entre send/recv et read/write sous UNIX, supposons un peu que write soit implémentée comme ceci :
En général, send utilise tout simplement write mais qu'est-ce qui empêchera un autre implémenteur d'utiliser directement __socket_write par exemple ?Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 int write(int fd, const void * buf, size_t count) { /* vars ... */ switch(__file_type(fd)) { case __DISK_FILE: __disk_file_write(fd, buf, count); break; case __SOCKET: __socket_write(fd, buf, count); break; /* etc. */ } /* return */ }
j'ai très bien compris et je ne suppose rien du tout, ci-dessous exemple de code source d'un Unix que j'ai ici sous la main :
recv.c
recvfrom.sCode:
1
2
3
4
5
6
7
8
9 ssize_t recv(s, buf, len, flags) int s, flags; size_t len; void *buf; { return (_recvfrom(s, buf, len, flags, NULL, 0)); }
ici read/write et recvfrom/sendto sont des SYSCALL, recv/send sont des glues.Code:
1
2 SYSCALL(recvfrom, 6)
mais que tous les U*x ne fassent pas exactement de la même façon… c'est plus que probable…
Une implémentation où recv/send appellent directement read/write… : le code source please !
appeler la même sous-fonction (votre _socket_XXX) qui traite le cas du socket : oui c'est plausible…
appeler read/write qui doivent tester le type de FD alors qu'on le connaît déjà : çà mérite un (très) gros "?" …
Tu argumentes en te basant sur un cas particulier. Et j'appellerai ça toujours cas particulier même s'il y a des centaines d'UNIX qui le font. D'après ton code, recvfrom est donc tout simplement le __socket_read du système mais rien n'oblige les autres systèmes à en faire autant. Donc ici, effectivement, read appelle recvfrom lorsque fd est un socket, parce qu'on est dans un cas particulier où __socket_read == recvfrom.
Je t'en fais une :mrgreen: ? Non franchement, un code source ne prouvera rien. C'est juste performances vs simplicité de codage et c'est peut-être performances qui est la plus souvent privilégiée contrairement à ce que je pensais mais bref, le fait est qu'on a read/write qui sont génériques mais sous POSIX seulement et send/recv qui sont portables et mieux adaptés aux sockets mais qui ne marchent que sur des sockets. Les implémentations, ça change d'un système à un autre.Citation:
Une implémentation où recv/send appellent directement read/write… : le code source please !
Le problème est pourtant simple: Si recv() appelle read(), comment le flag MSG_PEEK est-il interprété?
Chui bête en effet. J'étais tellement convaincu qu'il n'y avait pas plus bas niveau que read/write que je ne réfléchissais plus avant de poster. Mes excuses à JeitEmgie. Pas étonnant que MS ont décidé de ne pas mêler sockets et file handles ensemble.
Petit remontage de thread pour les dernières trouvailles: Au moins, il est possible, sous Windows, de savoir si un HANDLE donné est un fichier/pipe ou un socket:
Cela marchera sans problème, vu que les sockets restent quand même des handles particuliers (GetFileType() marche dessus, retournant FILE_TYPE_PIPE) et qu'il n'y a donc pas de collisions possibles.Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 BOOL IsSocket(HANDLE h) { SOCKADDR sa; int len = sizeof sa; if(getsockname((SOCKET)h, &sa, &len)==SOCKET_ERROR) { //It failed: Is it because h is not a socket, or because it's not bound? if(GetLastError()==WSAEINVAL) return TRUE; else return FALSE; //For all other errors, assume it's not a socket. } else return TRUE; //Success! it's a socket. }
Par contre, je ne vois aucun moyen de vérifier à 100% si un int est un socket ou un descripteur. Toutefois, si l'on prend le réflexe d'utiliser _open_osfhandle() sur les sockets, on peut toujours utiliser la fonction _get_osfhandle sur le descripteur obtenu pour tester le handle et savoir s'il pointe vers un fichier/pipe ou un socket... (bien qu'on ait toujours des problèmes lors de la fermeture, problèmes qui peuvent être contournés dans une application mono-threadée).