Servidor TCP en C: socket, bind, listen y accept con ejemplo de fecha y hora

InformáticaActualizado el 4,72 KB

Resumen

El documento explica paso a paso cómo implementar un servidor TCP en lenguaje C que acepta conexiones de clientes, recibe un código de operación y responde con la fecha y hora actuales en UTC. Recorre la creación del socket, la configuración de la dirección, las llamadas bind, listen y accept, y el envío de la respuesta. También señala algunas limitaciones del código original, como la falta de comprobación de errores en recv y send.

Puntos clave

  • socket(PF_INET, SOCK_STREAM, 0) crea un socket TCP sobre IPv4.
  • La estructura sockaddr_in se inicializa a cero con memset y se configura con INADDR_ANY y htons para el puerto.
  • bind asocia el socket al puerto y listen lo pone en modo pasivo con hasta 5 clientes en cola.
  • accept acepta una conexión y devuelve un nuevo descriptor para comunicarse con el cliente.
  • gmtime convierte time_t a struct tm; el mes va de 0 a 11 y el año se cuenta desde 1900.
  • El servidor atiende las conexiones de forma secuencial, una a la vez.
  • Los errores de recv y send no se comprueban en el código original.

Ponte a prueba

Servidor TCP en C: implementación de un servidor de fecha y hora

Descripción general

El siguiente código implementa un servidor TCP en lenguaje C que espera conexiones de clientes, recibe un código de operación y, si corresponde a una solicitud de fecha, responde con la fecha y hora actuales en formato UTC.

Código fuente

int main() {
  struct sockaddr_in serv_addr; /* Dirección del propio servidor */
  struct sockaddr_in cli_addr; /* Dirección del cliente conectado */
  int sd, n_sd; /* Descriptores de los sockets pasivo y activo */
  socklen_t addr_len; /* Longitud de la dirección del cliente */
  int codigo_operacion; /* Código de operación enviado y recibido */

  /* Creamos el socket TCP */
  sd = socket(PF_INET, SOCK_STREAM, 0);
  if (sd < 0) {
     perror("no se ha podido abrir el socket");
     exit(1);
  }

  /* Rellenamos la estructura con la dirección del servidor */
  memset((char*) &serv_addr, 0, sizeof(serv_addr));
  serv_addr.sin_family = AF_INET;
  serv_addr.sin_addr.s_addr = INADDR_ANY;
  serv_addr.sin_port = htons(atoi(PUERTO_TCP));

  /* Vinculamos el socket al puerto del servidor */
  if (bind(sd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) {
    perror("error al vincular la dirección local");
    exit(1);
  }

  /* Pone el socket en modo pasivo (ahora puede aceptar conexiones) */
  listen(sd, 5); /* 5 clientes pueden esperar su turno en cola */

  /* Bucle del servidor */
  while(1){
    printf("Servidor esperando conexión...\n");

    /* Espera una conexión de un cliente */
    addr_len = sizeof(cli_addr);
    n_sd = accept(sd, (struct sockaddr *) &cli_addr, &addr_len);
    if(n_sd < 0) {
      perror("error al aceptar nueva conexión");
      exit(1);
    }

    /* Recibimos el código de operación que envía el cliente */
    recv(n_sd, &codigo_operacion, sizeof(codigo_operacion), 0);

    /* Comprobamos el código de operación recibido */
    switch(codigo_operacion) {
      case SOLICITUD_FECHA: {
        time_t fecha_actual = time(0); /* Obtiene la fecha actual */

        /* Convertimos la fecha a una estructura tm */
        struct tm * fecha_tm = gmtime(&fecha_actual);
        struct fecha_y_hora fecha;
        fecha.dia = fecha_tm->tm_mday;
        fecha.mes = fecha_tm->tm_mon + 1; /* Porque devuelve de 0 a 11 */
        fecha.anno = fecha_tm->tm_year + 1900; /* Devuelve desde 1900 */
        fecha.hora = fecha_tm->tm_hour;
        fecha.minuto = fecha_tm->tm_min;
        fecha.segundo = fecha_tm->tm_sec;

        /* Enviamos la respuesta */
        /* Primero enviamos el código de operación */
        codigo_operacion = RESPUESTA_FECHA;
        send(n_sd, &codigo_operacion, sizeof(codigo_operacion), 0);

        /* Enviamos la fecha */
        send(n_sd, &fecha, sizeof(fecha), 0);
        break;
      }
      default: {
         perror("mensaje no válido");
         exit(1);
      }
    }
  }
}

Explicación de los pasos principales

1. Creación del socket

Se utiliza socket(PF_INET, SOCK_STREAM, 0) para crear un socket TCP sobre IPv4.

2. Configuración de la dirección del servidor

La estructura sockaddr_in se inicializa a cero con memset y se configura la familia de direcciones, la dirección IP (INADDR_ANY) y el puerto en formato de red mediante htons.

3. Vinculación y escucha

La función bind asocia el socket al puerto indicado y listen lo pone en modo pasivo, permitiendo hasta 5 clientes en cola.

4. Bucle de atención a clientes

El servidor acepta conexiones con accept, recibe el código de operación y responde según corresponda. Actualmente, atiende de forma secuencial una conexión a la vez.

5. Respuesta con fecha y hora

Obtención de la fecha

Se obtiene el tiempo actual con time(0) y se convierte a una estructura struct tm con gmtime, ajustando los valores de mes (0 a 11) y año (desde 1900).

Envío de la respuesta

Primero se envía el código RESPUESTA_FECHA y después la estructura con la fecha y hora.

Consideraciones

  • Las constantes PUERTO_TCP, SOLICITUD_FECHA, RESPUESTA_FECHA y la estructura fecha_y_hora deben estar definidas previamente.
  • Los errores de recv y send no se comprueban en el código original.
  • Un mensaje no válido termina el servidor con exit(1); en aplicaciones reales podría optarse por cerrar únicamente la conexión afectada.

Descargar

PDF Word HTML

Preguntas frecuentes

¿Qué hace la función listen en un servidor TCP?

Pone el socket en modo pasivo, de forma que puede aceptar conexiones entrantes. En este código el segundo argumento, 5, indica cuántos clientes pueden esperar en cola.

¿Por qué se ajustan el mes y el año al convertir la fecha?

Porque struct tm devuelve el mes de 0 a 11 y el año desde 1900, así que hay que sumar 1 al mes y 1900 al año para obtener los valores habituales.

¿Qué función convierte el puerto al formato de red?

La función htons convierte el número de puerto al orden de bytes de red antes de asignarlo a sin_port.

Ponte a prueba

Tarjetas de repaso

Pulsa una tarjeta para ver la respuesta.

Test

1. ¿Qué función crea el socket TCP en el código?
2. ¿Qué valor de INADDR_ANY se asigna a la dirección del servidor?
3. ¿Cuántos clientes pueden esperar en cola según la llamada a listen?
4. ¿Para qué se usa la función gmtime en el código?
5. ¿Qué ocurre con el mes devuelto por gmtime?
6. ¿Qué limitación del código original se menciona respecto a los errores?

Preguntas generadas con IA a partir del documento: comprueba las respuestas con el texto.