jade-raw-send.c

/**
 * @file jade-raw-send.c
 * @date October 14, 2010
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

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

typedef struct sockaddr    sockaddr;
typedef struct sockaddr_in sockaddr_in;
typedef struct timespec    timespec;

/**
 * Information passed to a background thread that communicates with a client.
 */
typedef struct thread_info
{
  /**
   * The file descriptor for the client socket.  This is opened by the main
   * thread but processed, shutdown, and closed by the background thread.
   */
  int fd;
  
  /**
   * The time interval between sends to the client.  This is passed as a
   * command-line argument.
   */
  timespec interval;
  
  /** The number of timespec values to sent to the client. */
  unsigned int iterations;
  
  /** The thread identification value (used for debugging purposes only). */
  pthread_t thread_id;

} thread_info;

/**
 * Normalizes a timespec so the 'tv_nsec' field ranges from 0 (inclusive) to
 * 1e9 (exclusive).
 *
 * @param t The timespec to normalize.
 *
 * @return The 't' parameter.
 */
static timespec * normalize_timespec(timespec * t)
{
  /* Eliminate overflows. */
  while (t->tv_nsec > 1e9L) {
    t->tv_nsec -= 1e9L;
    t->tv_sec++;
  }

  /* Eliminate underflows. */
  while (t->tv_nsec < 0L) {
    t->tv_nsec += 1e9L;
    t->tv_sec--;
  }
  
  /* Return the normalized parameter. */
  return t;
}

/**
 * Adds two timespec values and stores the result into a third data structure.
 * The output may be one of the specified parameters.  The output is normalized.
 *
 * @param t1 The first timespec to add.
 * @param t2 The second timespec to add.
 * @param sum The result of the operation.
 *
 * @return The 'sum' parameter.
 */
static timespec * add_timespec(
  const timespec * t1,
  const timespec * t2,
  timespec       * sum)
{
  /* Add and two values. */
  sum->tv_sec  = t1->tv_sec  + t2->tv_sec;
  sum->tv_nsec = t1->tv_nsec + t2->tv_nsec;

  /* Return the normalized sum. */
  return normalize_timespec(sum);
}

/**
 * Sends a buffer of text to the client.  If the message is fragmented, this
 * function loops until the entire buffer is sent.  The functions returns
 * EXIT_SUCCESS or EXIT_FAILURE.
 *
 * @param fd The file descriptor for the client socket.
 * @param buffer The buffer of text to send to the client.
 *
 * @return EXIT_SUCCESS if successfull; otherwise, EXIT_FAILURE.
 */
static int send_to_client(int fd, const char * text) {
  size_t count;

  /* Determine the size of the NULL-terminated text. */
  count = strlen(text) + 1;

  /* Loop while there are bytes remaining to send. */
  while (count > 0) {
    ssize_t result;

    /* Send as many bytes as possible, and check for errors. */
    result = send(fd, text, count, 0);
    if (result <= 0 || result > count) {
      perror("send");
      return EXIT_FAILURE;
    }

    /* Adjust the remaining text to send, if any. */
    count -= (size_t)result;
    text  += (size_t)result;
  }

  /* Return successfully. */
  return EXIT_SUCCESS;
}

/**
 * The main processing loop of the background thread.  This function loops over
 * the number of iterations specified on the command line and for each iteration
 * sends the client a line of text containing the timespec of the real-time
 * clock.
 *
 * @param info The thread information.
 *
 * @return EXIT_SUCCESS if successful; otherwise, EXIT_FAILURE.
 */
static int thread_loop(thread_info * info)
{
  unsigned int i;
  timespec     now;
  timespec     timeout;

  /* Get the absolute time for the next send to the client.  Initially, this is
   * the current time. */
  if (0 != clock_gettime(CLOCK_REALTIME, &timeout)) {
    perror("clock_gettime");
    return EXIT_FAILURE;
  }

  /* Loop over each iteration, which was specified on the command line. */
  for (i = 0; i < info->iterations; i++) {
    char msg[100];

    /* Get the current time. */
    if (0 != clock_gettime(CLOCK_REALTIME, &now)) {
      perror("clock_gettime");
      return EXIT_FAILURE;
    }

    /* Format the current time as a message to send to the client. */
    sprintf(
      msg,
      "%lu.%09lu\r\n",
      (unsigned long)now.tv_sec,
      (unsigned long)now.tv_nsec);

    /* Send the message to the client and check for errors. */
    if (EXIT_SUCCESS != send_to_client(info->fd, msg))
      return EXIT_FAILURE;

    /* Determine the next timeout based on the interval specified on the
     * command line. */
    add_timespec(&timeout, &info->interval, &timeout);

    /* Sleep until the timeout occurs or an error occurs. */
    if (0 != clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME, &timeout, NULL)) {
      perror("clock_nanosleep");
      return EXIT_FAILURE;
    }
  }

  /* Return successfully. */
  return EXIT_SUCCESS;
}

/**
 * The main entry point for the background thread.  This thread processes input
 * from the main thread.  It sends timespecs to a client and then shuts down
 * and closes the socket.  It also frees memory allocated for the thread's
 * data.
 *
 * @param arg The thread input, which is a 'thread_info' structure.
 *
 * @return EXIT_SUCCESS if successful; otherwise, EXIT_FAILURE.
 */
static void * thread_main(void * arg)
{
  thread_info * info;
  int           result;

  info = (thread_info *)arg;

  printf("Thread %lu has started.\n", info->thread_id);
  result = thread_loop(info);

  printf("Shutting down thread %lu...\n", info->thread_id);
  shutdown(info->fd, SHUT_RDWR);
  close(info->fd);
  free(info);

  printf("Thread %lu has stopped.\n", info->thread_id);
  return (void *)result;
}

/**
 * A function that binds to a socket, listens for connections, and creates
 * threads for each client.  This function does not exit uless there is an
 * error.
 *
 * @param port The port number to bind.
 * @param iterations The number of timespecs to send to the clients.
 * @param interval The interval between sends to the client.
 *
 * @return EXIT_FAILURE.
 */
static int socket_pump(
  unsigned short   port,
  unsigned int     iterations,
  const timespec * interval)
{
  int         opt;
  int         sfd;
  sockaddr_in sin;

  /* Create the socket for TCP/IP communications. */
  if (-1 == (sfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP))) {
    perror("socket");
    return EXIT_FAILURE;
  }

  /* Set the socket option to allow Control-C to end the program without
   * causing the program to hold onto the binding after the program has
   * terminated. */
  opt = 1;
  if (0 > setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(int))) {
    perror("setsockopt");
    return EXIT_FAILURE;
  }

  /* Prepare the socket address to bind. */
  memset(&sin, 0, sizeof(sin));
  sin.sin_family      = AF_INET;
  sin.sin_addr.s_addr = INADDR_ANY;
  sin.sin_port        = htons(port);

  /* Bind to the port. */
  printf("Binding to port %hu...\n", port);
  if (-1 == bind(sfd, (sockaddr *)&sin, sizeof(sin))) {
    perror("bind");
    close(sfd);
    return EXIT_FAILURE;
  }

  /* Listen for connections with a backlog of three. */
  if (-1 == listen(sfd, 3)) {
    perror("listen");
    close(sfd);
    return EXIT_FAILURE;
  }

  /* Loop until there is an unexpected error. */
  for (;;)
  {
    int           cfd;
    thread_info * info;

    /* Accept the next connection. */
    puts("Waiting for a connection...");
    if (-1 == (cfd = accept(sfd, NULL, NULL))) {
      perror("accept");
      close(sfd);
      return EXIT_FAILURE;
    }

    puts("Connected.");

    /* Allocate and prepare the input to the thread. */
    info = (thread_info *)malloc(sizeof(thread_info));
    info->interval   = *interval;
    info->iterations = iterations;
    info->fd         = cfd;

    /* Create and start the background thread that will process the client. */
    if (0 != pthread_create(&info->thread_id, NULL, &thread_main, info)) {
      perror("pthread_create");
      free(info);
      close(sfd);
      return EXIT_FAILURE;
    }
  }
}

/**
 * The main entry point of the program.  This function checks for syntax
 * errors, displays usage if requested, and then starts to listen for clients.
 *
 * @param argc The number of command-line arguments.
 * @param argv The command-line arguments.
 *
 * @return EXIT_SUCCESS if successful; otherwise, EXIT_FAILURE.
 */
int main(int argc, char * argv[])
{
  timespec        interval;
  unsigned int    iterations;
  unsigned short  port;

  /* Check for the "--help" option. */
  if (argc == 2 && 0 == strcmp("--help", argv[1])) {
    puts("usage: jade-raw-send port iterations interval");
    puts("Implements Jade's raw send program.");
    puts("");
    puts("  port        The port number used when listening for connections");
    puts("  iterations  The number of lines of text to send to each client");
    puts("  interval    The number of nanoseconds to wait between lines sent to each");
    puts("              client");
    puts("");
    return EXIT_SUCCESS;
  }

  /* Check for the correct number of arguments. */
  if (argc != 4) {
    fputs("The syntax of the command is incorrect.\n", stderr);
    return EXIT_FAILURE;
  }

  /* Parse the port number. */
  if (1 != sscanf(argv[1], "%hu", &port)) {
    fprintf(stderr, "Invalid port '%s'.\n", argv[1]);
    return EXIT_FAILURE;
  }

  /* Parse the number of iterations. */
  if (1 != sscanf(argv[2], "%u", &iterations)) {
    fprintf(stderr, "Invalid number of iterations '%s'.\n", argv[2]);
    return EXIT_FAILURE;
  }
  
  /* Parse the interval. */
  interval.tv_sec  = 0;
  interval.tv_nsec = 0;
  if (1 != sscanf(argv[3], "%lu", &interval.tv_nsec)) {
    fprintf(stderr, "Invalid nanosecond interval '%s'.\n", argv[3]);
    return EXIT_FAILURE;
  }

  /* Listen for connections. */
  return socket_pump(port, iterations, &interval);
}
Valid HTML 4.01 Valid CSS