/** * @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); }