2012-04-24 16 views
11

El sistema operativo es Linux. Tengo un proceso de servidor que puede cambiar su puerto en tiempo real. Sin embargo, me gustaría saber con anticipación si un puerto es libre antes de la vinculación.¿cómo encuentro en C que un puerto es libre de usar?

Escenario: El servidor enlaza localhost: 5000 y recibe una solicitud para enlazar en localhost: 6000. El servidor debe verificar si el puerto es libre. Esta pregunta busca respuestas que proporcionen una rutina que verifique si un puerto es libre o no.

Para el registro, estoy editando mi pregunta con un fragmento de código que comprueba si un puerto es libre de usar. Esto no significa que será usado. El siguiente código responde a la pregunta "si el puerto está disponible ahora", no lo usa. Abre un socket, verifique si bind devuelve EADDRINUSE y cierra el socket.

#include <iostream> 
#include <sys/socket.h> 
#include <stdlib.h> 
#include <string.h> 
#include <unistd.h> 
#include <arpa/inet.h> 
#include <errno.h> 

int main(int argc, char **argv) { 

    struct sockaddr_in serv_addr; 
    if(argc < 2) 
     return 0; 
    int port = atoi(argv[1]); 

    int sockfd = socket(AF_INET, SOCK_STREAM, 0); 
    if(sockfd < 0) { 
     printf("socket error\n"); 
     return 0; 
    } else { 
     printf("Opened fd %d\n", sockfd); 
    } 

    bzero((char *) &serv_addr, sizeof(serv_addr)); 
    serv_addr.sin_family = AF_INET; 
    serv_addr.sin_addr.s_addr = INADDR_ANY; 
    serv_addr.sin_port = htons(port); 
    if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) { 

     if(errno == EADDRINUSE) 
     { 
      printf("the port is not available. already to other process\n"); 
     } else { 
      printf("could not bind to process (%d) %s\n", errno, strerror(errno)); 
     } 
    } 

    if (close (sockfd) < 0) { 
     printf("did not close fd: %s\n", strerror(errno)); 
     return errno; 
    } 

    return 0; 


} 

Estas son algunas tandas de muestras (resultados parciales)

[bash{1051}{51}]:[~/some_sources/checkbind]::./a.out 41067 
the port is not available. already to other process 
[bash{1052}{52}]:[~/some_sources/checkbind]::./a.out 22 
could not bind to process (13) Permission denied 
[bash{1053}{53}]:[~/some_sources/checkbind]::./a.out 22000 
Opened fd 3 
+10

Pruebe unirse a él, si no funciona, no es gratis ... – Nick

+1

Nicks sugerencia es probablemente la solución más portátil, pero puede haber formas específicas del OS que hacer también. ¿Qué sistema operativo usarás? –

+0

@Nick ve que bind devuelve EADDRINUSE (errno) si el puerto no es libre. – cateof

Respuesta

12

Ésta es una obvia race condition, ya que otros procesos en el sistema podrían ser de cumplimiento obligatorio para los puertos en paralelo. Por lo tanto, cualquier solución que encuentre será imperfecta, y tendrá que todavía escribirla de acuerdo con el "intento de bind(), si falla, elija un nuevo número de puerto y vuelva a intentarlo".

+0

Acepto que puede haber una condición de carrera, pero en mi sistema este escenario no es posible. Además, el enfoque de "elegir el próximo puerto" no es aceptable. – cateof

+0

@cateof: ¿Qué no es posible?¿La condición de carrera no es posible o no es posible probar la unión y luego ver si falla? –

+0

La condición de carrera es posible si tiene al menos dos procesos compitiendo por un recurso. En mi sistema, solo un proceso intenta vincularse. – cateof

7

Si su servidor fue dijo qué puerto usar, solo bind(). Seriamente.

Claro, podría analizar /proc/net/tcp y ver si el puerto está en uso. ¿Pero entonces, qué? Todavía necesita llamar al bind() ahora que sabe que su puerto es gratis, y le dirá si el puerto estaba libre de todos modos, por lo que no tenía sentido arrastrarse por /proc/net/tcp y hacer todo eso (¡lento!) Producción de cuerdas y el análisis sintáctico y el kernel extra viajan a través de rutas de diagnóstico no muy bien optimizadas (léase: súper lento en comparación con bind()), solo para obtener información que podría estar desactualizada incluso antes de terminar de analizarla. Así que solo llame al bind() y sea feliz.

4

He luchado con esto yo mismo y he modificado ligeramente su código.

La solución es establecer serv_addr.sin_port = 0 (puerto de asignación automática).

Nota En los bind() y los getsockname() líneas no son impuros moldes de sockaddr_in a sockaddr. Lo he visto en muchos lugares y estoy buscando una solución más segura.

#include <sys/types.h> 
#include <sys/socket.h> 
#include <netinet/in.h> 

// ... snip ... 

int sock = socket(AF_INET, SOCK_STREAM, 0); 
if(sock < 0) { 
    printf("socket error\n"); 
    return; 
} 
printf("Opened %d\n", sock); 

struct sockaddr_in serv_addr; 
bzero((char *) &serv_addr, sizeof(serv_addr)); 
serv_addr.sin_family = AF_INET; 
serv_addr.sin_addr.s_addr = INADDR_ANY; 
serv_addr.sin_port = 0; 
if (bind(sock, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) { 
    if(errno == EADDRINUSE) { 
     printf("the port is not available. already to other process\n"); 
     return; 
    } else { 
     printf("could not bind to process (%d) %s\n", errno, strerror(errno)); 
     return; 
    } 
} 

socklen_t len = sizeof(serv_addr); 
if (getsockname(sock, (struct sockaddr *)&serv_addr, &len) == -1) { 
    perror("getsockname"); 
    return; 
} 

printf("port number %d\n", ntohs(serv_addr.sin_port)); 


if (close (sock) < 0) { 
    printf("did not close: %s\n", strerror(errno)); 
    return; 
} 

La salida del programa

Opened 4 
port number 59081 
+0

"En la línea' getsockname (...) 'hay un lanzamiento peligroso de' sockaddr_in' a 'sockaddr'." ¿De qué manera crees que es peligroso? Tal vez quieras usar 'sockaddr_storage' para estar realmente seguro. – glglgl

+0

Tienen el mismo tamaño 16 bytes. El 'sin_len' y' sin_family' están ubicados en el mismo lugar en ambas estructuras. Simplemente no me gustan los reinterpret_cast cast. – neoneye

Cuestiones relacionadas