...y quién no utiliza el #ifdef tendrá que tratar conmigo... |
Entonces, el fragmento de código (extracto de ese post allí) que hay que cambiar es el siguiente:
...
// acepta conexiones de un client entrante printf("%s: espera connexiones entrantes...\n", argv[0]); socklen_t socksize = sizeof(struct sockaddr_in); struct sockaddr_in client; // (remote) client socket info int client_sock; if ((client_sock = accept(my_socket, (struct sockaddr *)&client, &socksize)) < 0) { // error accept() printf("%s: accept failed (%s)\n", argv[0], strerror(errno)); return EXIT_FAILURE; } ...Y la nueva versión con la compilación condicional a través de #ifdef será la siguiente:
... // acepta conexiones de un client entrante (en non blocking mode: my_socket es SOCK_NONBLOCK) printf("%s: espera connexiones entrantes...\n", argv[0]); socklen_t socksize = sizeof(struct sockaddr_in); struct sockaddr_in client; // (remote) client socket info int client_sock; #ifdef OLD_LINUX if ((client_sock = accept(my_socket, (struct sockaddr *)&client, &socksize)) < 0) { #else if ((client_sock = accept4(my_socket, (struct sockaddr *)&client, &socksize, SOCK_NONBLOCK)) < 0) { #endif // error accept() printf("%s: accept failed (%s)\n", argv[0], strerror(errno)); return EXIT_FAILURE; } #ifdef OLD_LINUX else { // accept ejecutada: set socket a non-blocking int flags; if ((flags = fcntl(client_sock, F_GETFL, 0)) >= 0) { if (fcntl(client_sock, F_SETFL, flags | O_NONBLOCK) < 0) { // error accept() printf("%s: fcntl failed (%s)\n", argv[0], strerror(errno)); return EXIT_FAILURE; } } } #endif ...Ok, como se nota es ampliamente comentado y así se auto-explica, por lo cual no voy a detenerme sobre las instrucciones y/o grupos de instrucciones (¡leer los comentarios! ¡Están ahí para eso!), pero voy a añadir, solamente, algunos detalles estructurales.
En las partes incluidas en el #ifdef OLD_LINUX hay la versión del código que NO utiliza la accept4(), y que es, obviamente, un poco más complicada que la otra, porque hay que utilizar fcntl() para transformar en no-bloqueante el socket creado, mientras que la accept4() lo crea directamente usando, como se ha mencionado, el flag SOCK_NONBLOCK. Sin embargo, como se puede ver, el código resultante es bastante claro y legible y, después de todo, no se ve tan mal (¡el estilo primero!).
Alguien podría decir: la versión bajo #ifdef también trabaja con un Linux reciente, así que ¿por qué no dejar solo esa y quitar las #ifdef? ¡NO! ¡NO! y otra vez ¡NO! No hay que escribir código old-style para que sea retro-compatible: siempre hay que tratar de escribir de una manera moderna, y si es necesario (como en el ejemplo) hay que poner el material antiguo bajo #ifdef. Y cuando llegue el momento (cuando, por ejemplo, ya no vamos a usar un entorno operativo dual) limpiaremos y dejaremos sólo un bonito código moderno.
Obviamente la compilación condicional la efectuaremos mediante la introducción (o no introducción) de una instrucción -D OLD_LINUX en la línea del nuestro makefile que genera lo files objeto, o directamente en la línea de comandos si no se utiliza un makefile. Bueno, por fin hemos escribito un código único para dos entornos operativos diferentes: ¡misión cumplida!
Sólo una última aclaración sobre el nuestro socket server modificado (y que no tiene nada que ver con las #ifdef): si el non-blocking socket nos necesita para ejecutar unas recv() no bloqueantes en la siguiente fase a la de accept, es mucho más fácil modificar adecuadamente el loop de recepción y pasar el flag MSG_DONTWAIT a la recv() en el argumento flags (el cuarto). Por lo que la recv() no bloquea en ambos entornos operativos, y todo ello sin el uso de #ifdef. Pero esa es otra historia...
¡Hasta el próximo post!