¿Cómo uso setsockopt (SO_REUSEADDR)?

5 minutos de lectura

avatar de usuario
usuario3735849

Estoy ejecutando mi propio servidor http en una frambuesa pi. El problema es que cuando detengo el programa y lo reinicio, el puerto ya no está disponible. A veces tengo el mismo problema cuando recibo muchas solicitudes.
Quiero usar SO_REUSEADDR para poder seguir usando el puerto incluso cuando se produce el error, pero no he tenido suerte al configurarlo. A continuación se muestra mi código.
El error que recibo es “ERROR en el enlace: la dirección ya está en uso”.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h> 
#include <sys/socket.h>
#include <netinet/in.h>

void error(const char *msg)
{
    perror(msg);
    exit(1);
}

int main(int argc, char *argv[])
{
    printf("Starting Listener\n");
     int sockfd, newsockfd, portno;
     socklen_t clilen;
     char buffer[256];
     struct sockaddr_in serv_addr, cli_addr;
     int n;
     if (argc < 2) {
         fprintf(stderr,"ERROR, no port provided\n");
         exit(1);
     }
     sockfd = socket(AF_INET, SOCK_STREAM, 0);
     if (sockfd < 0) 
        error("ERROR opening socket");
     bzero((char *) &serv_addr, sizeof(serv_addr));
     portno = atoi(argv[1]);
     serv_addr.sin_family = AF_INET;
     serv_addr.sin_addr.s_addr = INADDR_ANY;
     serv_addr.sin_port = htons(portno);
     if (bind(sockfd, (struct sockaddr *) &serv_addr,
              sizeof(serv_addr)) < 0) 
              error("ERROR on binding");

     printf("about to listen\n");
     listen(sockfd,5);
     printf("finished listening\n");
     clilen = sizeof(cli_addr);
     printf("About to accept\n");

     int i;
     for(i=0; i<100; i++){
         newsockfd = accept(sockfd, 
                 (struct sockaddr *) &cli_addr, 
                 &clilen);

         if (newsockfd < 0) 
             error("ERROR on accept");
         bzero(buffer,256);
         n = read(newsockfd,buffer,255);
         if (n < 0) error("ERROR reading from socket");
         printf("Here is the message: %s\n",buffer);
         n = write(newsockfd,"I got your message",18);
         if (n < 0) error("ERROR writing to socket");
         close(newsockfd);
     }
     close(sockfd);
     return 0; 
}

avatar de usuario
Chnossos

Después :

sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) 
    error("ERROR opening socket");

Puede agregar (con estándar Literal compuesto C99 apoyo) :

if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int)) < 0)
    error("setsockopt(SO_REUSEADDR) failed");

O :

int enable = 1;
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0)
    error("setsockopt(SO_REUSEADDR) failed");

  • Probé ambos y obtuve el mismo resultado después de matar el programa y volver a iniciarlo. Este es el error: Iniciando Listener ERROR en el enlace: Dirección ya en uso a punto de escuchar terminado de escuchar A punto de aceptar

    – usuario3735849

    14 de junio de 2014 a las 2:00


  • No funcionará correctamente si mata el programa de la manera difícil. Tienes que realizar una salida limpia.

    – Chnossos

    16 de junio de 2014 a las 21:17

  • El propósito de SO_REUSEADDR/SO_REUSEPORT es permitir la reutilización del puerto incluso si el proceso falla o se cancela.

    – promomonet

    8 de agosto de 2014 a las 22:07

  • @mpromonet Tal vez no esté implementado en todas partes, porque en mi sistema no funciona si elimino el programa con un SIGKILL, por ejemplo.

    – Chnossos

    8 de agosto de 2014 a las 22:13

  • @Chnossos Acabo de probar en una raspberry pi con kernel 3.12.25, el enlace funciona bien incluso cuando los sockets TIME_WAIT todavía usan el puerto (lo que ocurre con un SIGKILL de eliminación). ¿Insertó el setsockopt antes de la llamada de enlace?

    – promomonet

    9 de agosto de 2014 a las 17:16


avatar de usuario
mpromonet

Dependiendo de la versión de libc, podría ser necesario configurar las opciones de socket SO_REUSEADDR y SO_REUSEPORT como se explica en enchufe(7) documentación:

   SO_REUSEPORT (since Linux 3.9)
          Permits multiple AF_INET or AF_INET6 sockets to be bound to an
          identical socket address.  This option must be set on each
          socket (including the first socket) prior to calling bind(2)
          on the socket.  To prevent port hijacking, all of the
          processes binding to the same address must have the same
          effective UID.  This option can be employed with both TCP and
          UDP sockets.

Como esta opción de socket aparece con kernel 3.9 y raspberry use 3.12.x, será necesario configurar SO_REUSEPORT.

Puede configurar estas dos opciones antes de llamar a bind de esta manera:

    int reuse = 1;
    if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse, sizeof(reuse)) < 0)
        perror("setsockopt(SO_REUSEADDR) failed");

#ifdef SO_REUSEPORT
    if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, (const char*)&reuse, sizeof(reuse)) < 0) 
        perror("setsockopt(SO_REUSEPORT) failed");
#endif

Creo que deberías usar las opciones SO_LINGER (con tiempo de espera 0). En este caso, su conexión se cerrará inmediatamente después de cerrar su programa; y el próximo reinicio podrá vincularse nuevamente.

ejemplo:

linger lin;
lin.l_onoff = 0;
lin.l_linger = 0;
setsockopt(fd, SOL_SOCKET, SO_LINGER, (const char *)&lin, sizeof(int));

ver definición: http://man7.org/linux/man-pages/man7/socket.7.html

SO_LINGER
          Sets or gets the SO_LINGER option.  The argument is a linger
          structure.

              struct linger {
                  int l_onoff;    /* linger active */
                  int l_linger;   /* how many seconds to linger for */
              };

          When enabled, a close(2) or shutdown(2) will not return until
          all queued messages for the socket have been successfully sent
          or the linger timeout has been reached.  Otherwise, the call
          returns immediately and the closing is done in the background.
          When the socket is closed as part of exit(2), it always
          lingers in the background.

Más sobre SO_LINGER: Opción TCP SO_LINGER (cero) – cuando se requiere

  • creo que deberia decir lin.l_onoff = 1;. De lo contrario, está deshabilitando la opción por completo, lo que significa que permanecerá en segundo plano.

    – raíz de dragón

    08/03/2019 a las 22:00

  • con la forma en que está en la respuesta o en el comentario, no puedo vincularme si me detengo e inmediatamente inicio mi programa. Si pongo SO_REUSEADDR, entonces funciona muy bien.

    – xaxxon

    22 de mayo de 2019 a las 23:06


¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad