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