CS 105

Networking Source Code

Local Version

/*
 * wisdom_local.c — The wisdom service, minus the network.
 *
 * This is what you get when you strip the networking out of
 * wisdom_server.c.  The handle_session() function is identical —
 * we just call it with stdin and stdout instead of a socket.
 *
 * Build: gcc -Wall -o wisdom_local wisdom_local.c
 */

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

#define BUFSIZE 256

/*
 * Identical to handle_session() in wisdom_server.c.
 * It never knew it was talking over a network.
 */
void handle_session(FILE *in, FILE *out)
{
    char buf[BUFSIZE];

    while (fgets(buf, sizeof(buf), in) != NULL) {
        buf[strcspn(buf, "\r\n")] = '\0';

        if (strcmp(buf, "TIME") == 0) {
            time_t now = time(NULL);
            char *timestr = ctime(&now);
            fprintf(out, "%s", timestr);
        } else if (strcmp(buf, "MEANING") == 0) {
            fprintf(out, "42\n");
        } else if (strcmp(buf, "STOP") == 0) {
            fprintf(out, "Goodbye!\n");
            fflush(out);
            return;
        } else {
            fprintf(out, "Unknown command: %s\n", buf);
        }
        fflush(out);
    }
}

int main(void)
{
    printf("Wisdom (local mode)\n");
    printf("Commands: TIME, MEANING, STOP\n\n");

    handle_session(stdin, stdout);

    return 0;
}

Basic Client-Server Version

Client

/*
 * wisdom_client.c — Connects to a wisdom server and sends commands.
 *
 * Usage: ./wisdom_client [host [port]]
 *   Defaults: host=127.0.0.1, port=4242
 *
 * Build: gcc -Wall -o wisdom_client wisdom_client.c
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define DEFAULT_PORT 4242
#define BUFSIZE 256

int main(int argc, char *argv[])
{
    const char *host = (argc > 1) ? argv[1] : "127.0.0.1";
    int port = (argc > 2) ? atoi(argv[2]) : DEFAULT_PORT;

    /* ---- Create socket ---- */
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd < 0) { perror("socket"); exit(1); }

    /* ---- Connect to server ---- */
    struct sockaddr_in addr = {
        .sin_family = AF_INET,
        .sin_port   = htons(port)
    };
    if (inet_pton(AF_INET, host, &addr.sin_addr) <= 0) {
        fprintf(stderr, "Bad address: %s\n", host);
        exit(1);
    }
    if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
        perror("connect");
        exit(1);
    }

    /* Wrap socket in FILE* streams, same as the server does */
    FILE *srv_in  = fdopen(fd, "r");
    FILE *srv_out = fdopen(dup(fd), "w");

    printf("Connected to %s:%d\n", host, port);
    printf("Commands: TIME, MEANING, STOP\n\n");

    char buf[BUFSIZE];
    char response[BUFSIZE];

    // while (printf("> "), fflush(stdout),
    //        fgets(buf, sizeof(buf), stdin) != NULL) {

    while (1) {
        printf("> ");
        fflush(stdout);
        if (fgets(buf, sizeof(buf), stdin) == NULL) {
            /* Interrupted by signal? Try again! */
            if (errno == EINTR) continue;
            fprintf(stderr, "Input error: %s\n", strerror(errno));
            break;
        }

        /* Send the command (including newline) to the server */
        fprintf(srv_out, "%s", buf);
        fflush(srv_out);

        /* Read the server's response */
        if (fgets(response, sizeof(response), srv_in) == NULL) {
            printf("Server disconnected\n");
            break;
        }
        printf("%s", response);

        /* If we sent STOP, we're done */
        buf[strcspn(buf, "\r\n")] = '\0';
        if (strcmp(buf, "STOP") == 0)
            break;
    }

    fclose(srv_in);
    fclose(srv_out);
    return 0;
}

Server

/*
 * wisdom_server.c — A tiny TCP server that dispenses wisdom.
 *
 * Protocol:
 *   Client sends a line, server responds with a line.
 *     TIME     →  current date/time
 *     MEANING  →  42
 *     STOP     →  server closes the connection
 *     (other)  →  error message
 *
 * Usage: ./wisdom_server [port]
 *   Default port is 4242.
 *
 * Build: gcc -Wall -o wisdom_server wisdom_server.c
 */

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

#define DEFAULT_PORT 4242
#define BUFSIZE 256

/*
 * Handle one client session: read commands, send responses.
 * Returns when the client disconnects or sends STOP.
 *
 * Note: this function only uses FILE* streams — it doesn't know
 * or care whether they're connected to a socket or to a terminal.
 */
void handle_session(FILE *in, FILE *out)
{
    char buf[BUFSIZE];
    bool stop = false;

    while (!stop && fgets(buf, sizeof(buf), in) != NULL) {
        buf[strcspn(buf, "\r\n")] = '\0';     /* strip newline */
        /* Debug output to show client behavior */
        fprintf(stderr, "-> Received: %s\n", buf);

        if (strcmp(buf, "TIME") == 0) {
            time_t now = time(NULL);
            char *timestr = ctime(&now);       /* includes \n */
            fprintf(out, "%s", timestr);
        } else if (strcmp(buf, "MEANING") == 0) {
            fprintf(out, "42\n");
        } else if (strcmp(buf, "STOP") == 0) {
            fprintf(out, "Goodbye!\n");
            stop = true;
        } else {
            fprintf(out, "Unknown command: %s\n", buf);
        }
        fflush(out);
        /* Debug output to show server response */
        fprintf(stderr, "<- Reply sent for: %s\n", buf);
    }
}

int main(int argc, char *argv[])
{
    int port = (argc > 1) ? atoi(argv[1]) : DEFAULT_PORT;

    /* ---- Create socket ---- */
    int server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd < 0) { perror("socket"); exit(1); }

    /* Allow quick restart without "address already in use" */
    int opt = 1;
    setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    /* ---- Bind to port ---- */
    struct sockaddr_in addr = {
        .sin_family      = AF_INET,
        .sin_addr.s_addr = INADDR_ANY,
        .sin_port        = htons(port)
    };
    if (bind(server_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
        perror("bind");
        exit(1);
    }

    /* ---- Listen for connections ---- */
    if (listen(server_fd, 1) < 0) { perror("listen"); exit(1); }
    printf("Wisdom server listening on port %d\n", port);

    /* ---- Accept loop: one client at a time ---- */
    while (1) {
        int client_fd = accept(server_fd, NULL, NULL);
        if (client_fd < 0) { perror("accept"); continue; }
        printf("Client connected\n");

        /*
         * Wrap the socket fd in FILE* streams so we can use
         * fgets/fprintf — same interface as stdin/stdout.
         * We dup() for the write stream so each fclose()
         * closes its own fd independently.
         */
        FILE *client_in  = fdopen(client_fd, "r");
        FILE *client_out = fdopen(dup(client_fd), "w");

        handle_session(client_in, client_out);

        fclose(client_in);
        fclose(client_out);
        printf("Client disconnected\n");
    }
}

Forking Server Version

/*
 * wisdom_server_fork.c — Forking version: handles multiple clients.
 *
 * Protocol:
 *   Client sends a line, server responds with a line.
 *     TIME     →  current date/time
 *     MEANING  →  42
 *     STOP     →  server closes the connection
 *     (other)  →  error message
 *
 * Each client gets its own child process.  A SIGCHLD handler
 * reaps children so they don't become zombies.
 *
 * Usage: ./wisdom_server_fork [port]
 *   Default port is 4242.
 *
 * Build: gcc -Wall -o wisdom_server_fork wisdom_server_fork.c
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <time.h>
#include <signal.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <netinet/in.h>

#define DEFAULT_PORT 4242
#define BUFSIZE 256

/*
 * Handle one client session: read commands, send responses.
 * Returns when the client disconnects or sends STOP.
 *
 * Note: this function only uses FILE* streams — it doesn't know
 * or care whether they're connected to a socket or to a terminal.
 */
void handle_session(FILE *in, FILE *out)
{
    char buf[BUFSIZE];
    bool stop = false;

    while (!stop && fgets(buf, sizeof(buf), in) != NULL) {
        buf[strcspn(buf, "\r\n")] = '\0';     /* strip newline */
        /* Debug output to show client behavior */
        fprintf(stderr, "[child %d] -> Received: %s\n", getpid(), buf);

        if (strcmp(buf, "TIME") == 0) {
            time_t now = time(NULL);
            char *timestr = ctime(&now);       /* includes \n */
            fprintf(out, "%s", timestr);
        } else if (strcmp(buf, "MEANING") == 0) {
            fprintf(out, "42\n");
        } else if (strcmp(buf, "STOP") == 0) {
            fprintf(out, "Goodbye!\n");
            stop = true;
        } else {
            fprintf(out, "Unknown command: %s\n", buf);
        }
        fflush(out);
        /* Debug output to show server response */
        fprintf(stderr, "[child %d] <- Reply sent for: %s\n", getpid(), buf);
    }
}

/*
 * SIGCHLD handler: reap all finished children.
 * Uses a loop because multiple children might exit between
 * two deliveries of SIGCHLD.
 */
void sigchld_handler(int sig)
{
    (void)sig;
    while (waitpid(-1, NULL, WNOHANG) > 0)
        ;
}

int main(int argc, char *argv[])
{
    int port = (argc > 1) ? atoi(argv[1]) : DEFAULT_PORT;

    /* ---- Install SIGCHLD handler to reap children ---- */
    struct sigaction sa = {
        .sa_handler = sigchld_handler,
        .sa_flags   = SA_RESTART      /* so accept() doesn't fail with EINTR */
    };
    sigemptyset(&sa.sa_mask);
    sigaction(SIGCHLD, &sa, NULL);

    /* ---- Create socket ---- */
    int server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd < 0) { perror("socket"); exit(1); }

    /* Allow quick restart without "address already in use" */
    int opt = 1;
    setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    /* ---- Bind to port ---- */
    struct sockaddr_in addr = {
        .sin_family      = AF_INET,
        .sin_addr.s_addr = INADDR_ANY,
        .sin_port        = htons(port)
    };
    if (bind(server_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
        perror("bind");
        exit(1);
    }

    /* ---- Listen for connections ---- */
    if (listen(server_fd, 5) < 0) { perror("listen"); exit(1); }
    printf("Wisdom server (fork) listening on port %d\n", port);

    /* ---- Accept loop: fork a child for each client ---- */
    while (1) {
        int client_fd = accept(server_fd, NULL, NULL);
        if (client_fd < 0) { perror("accept"); continue; }

        pid_t pid = fork();
        if (pid < 0) {
            perror("fork");
            close(client_fd);
            continue;
        }

        if (pid == 0) {
            /* ---- Child process ---- */
            close(server_fd);          /* child doesn't need the listener */
            fprintf(stderr, "[child %d] Client connected\n", getpid());

            FILE *client_in  = fdopen(client_fd, "r");
            FILE *client_out = fdopen(dup(client_fd), "w");
            handle_session(client_in, client_out);
            fclose(client_in);
            fclose(client_out);

            fprintf(stderr, "[child %d] Client disconnected\n", getpid());
            exit(0);
        }

        /* ---- Parent process ---- */
        close(client_fd);              /* parent doesn't talk to client */
    }
}

Forking Server with Limit on Number of Children

/*
 * wisdom_server_fork_limit.c — Forking version with connection limit.
 *
 * Protocol:
 *   Client sends a line, server responds with a line.
 *     TIME     →  current date/time
 *     MEANING  →  42
 *     STOP     →  server closes the connection
 *     (other)  →  error message
 *
 * Each client gets its own child process.  A SIGCHLD handler
 * reaps children and tracks the count.  The server won't accept
 * new connections when MAX_CHILDREN are active — it blocks until
 * a slot opens up.
 *
 * Usage: ./wisdom_server_fork_limit [port]
 *   Default port is 4242.
 *
 * Build: gcc -Wall -o wisdom_server_fork_limit wisdom_server_fork_limit.c
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <time.h>
#include <signal.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <netinet/in.h>

#define DEFAULT_PORT 4242
#define BUFSIZE 256
#define MAX_CHILDREN 5

volatile sig_atomic_t num_children = 0;

/*
 * Handle one client session: read commands, send responses.
 * Returns when the client disconnects or sends STOP.
 *
 * Note: this function only uses FILE* streams — it doesn't know
 * or care whether they're connected to a socket or to a terminal.
 */
void handle_session(FILE *in, FILE *out)
{
    char buf[BUFSIZE];
    bool stop = false;

    while (!stop && fgets(buf, sizeof(buf), in) != NULL) {
        buf[strcspn(buf, "\r\n")] = '\0';     /* strip newline */
        /* Debug output to show client behavior */
        fprintf(stderr, "[child %d] -> Received: %s\n", getpid(), buf);

        if (strcmp(buf, "TIME") == 0) {
            time_t now = time(NULL);
            char *timestr = ctime(&now);       /* includes \n */
            fprintf(out, "%s", timestr);
        } else if (strcmp(buf, "MEANING") == 0) {
            fprintf(out, "42\n");
        } else if (strcmp(buf, "STOP") == 0) {
            fprintf(out, "Goodbye!\n");
            stop = true;
        } else {
            fprintf(out, "Unknown command: %s\n", buf);
        }
        fflush(out);
        /* Debug output to show server response */
        fprintf(stderr, "[child %d] <- Reply sent for: %s\n", getpid(), buf);
    }
}

/*
 * SIGCHLD handler: reap all finished children and update the count.
 * Uses a loop because multiple children might exit between
 * two deliveries of SIGCHLD.
 */
void sigchld_handler(int sig)
{
    (void)sig;
    while (waitpid(-1, NULL, WNOHANG) > 0) {
        num_children--;
        fprintf(stderr, "[parent] Child exited, %d/%d slots used\n",
                (int)num_children, MAX_CHILDREN);
    }
}

int main(int argc, char *argv[])
{
    int port = (argc > 1) ? atoi(argv[1]) : DEFAULT_PORT;

    /* ---- Install SIGCHLD handler to reap children ---- */
    struct sigaction sa = {
        .sa_handler = sigchld_handler,
        .sa_flags   = SA_RESTART      /* so accept() doesn't fail with EINTR */
    };
    sigemptyset(&sa.sa_mask);
    sigaction(SIGCHLD, &sa, NULL);

    /* ---- Create socket ---- */
    int server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd < 0) { perror("socket"); exit(1); }

    /* Allow quick restart without "address already in use" */
    int opt = 1;
    setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    /* ---- Bind to port ---- */
    struct sockaddr_in addr = {
        .sin_family      = AF_INET,
        .sin_addr.s_addr = INADDR_ANY,
        .sin_port        = htons(port)
    };
    if (bind(server_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
        perror("bind");
        exit(1);
    }

    /* ---- Listen for connections ---- */
    if (listen(server_fd, 5) < 0) { perror("listen"); exit(1); }
    printf("Wisdom server (fork, max %d clients) listening on port %d\n",
           MAX_CHILDREN, port);

    /* ---- Accept loop: fork a child for each client ---- */
    while (1) {
        /* Wait for a slot to open if we're at capacity */
        while (num_children >= MAX_CHILDREN) {
            fprintf(stderr, "[parent] At capacity (%d children), waiting...\n",
                    (int)num_children);
            pause();    /* sleep until a signal (SIGCHLD) arrives */
        }

        int client_fd = accept(server_fd, NULL, NULL);
        if (client_fd < 0) { perror("accept"); continue; }

        pid_t pid = fork();
        if (pid < 0) {
            perror("fork");
            close(client_fd);
            continue;
        }

        if (pid == 0) {
            /* ---- Child process ---- */
            close(server_fd);
            fprintf(stderr, "[child %d] Client connected\n", getpid());

            FILE *client_in  = fdopen(client_fd, "r");
            FILE *client_out = fdopen(dup(client_fd), "w");
            handle_session(client_in, client_out);
            fclose(client_in);
            fclose(client_out);

            fprintf(stderr, "[child %d] Client disconnected\n", getpid());
            exit(0);
        }

        /* ---- Parent process ---- */
        close(client_fd);
        num_children++;
        fprintf(stderr, "[parent] Forked child %d (%d/%d slots used)\n",
                pid, (int)num_children, MAX_CHILDREN);
    }
}

(When logged in, completion status appears here.)