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.)