Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: CI

on:
push:
pull_request:

jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Build
run: make all

- name: Test
run: make test
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
bin/
*.o
*.log
*.swp
15 changes: 15 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
FROM debian:bookworm-slim

RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
make \
&& rm -rf /var/lib/apt/lists/*

WORKDIR /app
COPY . .

RUN make all

EXPOSE 5555

CMD ["./bin/pcc_server", "5555"]
37 changes: 37 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
CC := gcc
CFLAGS := -Wall -Werror -O3 -std=c11 -D_POSIX_C_SOURCE=200809
SRC_DIR := src
BIN_DIR := bin

SERVER := $(BIN_DIR)/pcc_server
CLIENT := $(BIN_DIR)/pcc_client

.PHONY: all clean test

all: $(SERVER) $(CLIENT)

$(BIN_DIR):
@mkdir -p $(BIN_DIR)

$(SERVER): $(SRC_DIR)/pcc_server.c | $(BIN_DIR)
$(CC) $(CFLAGS) $< -o $@

$(CLIENT): $(SRC_DIR)/pcc_client.c | $(BIN_DIR)
$(CC) $(CFLAGS) $< -o $@

clean:
rm -rf $(BIN_DIR)

test: all
@set -e; \
port=5555; \
tmpfile=$$(mktemp); \
echo "abc123" > $$tmpfile; \
timeout 5s $(SERVER) $$port > /tmp/pcc_server.log 2>&1 & \
srv_pid=$$!; \
sleep 1; \
$(CLIENT) 127.0.0.1 $$port $$tmpfile > /tmp/pcc_client.log; \
kill -INT $$srv_pid; \
wait $$srv_pid || true; \
grep "# of printable characters: 6" /tmp/pcc_client.log; \
rm -f $$tmpfile
49 changes: 49 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Printable Characters Counter (PCC)

## Read This First
- The authoritative project compass is [`docs/instructions_network.txt`](docs/instructions_network.txt). All development choices, error semantics, and protocol details align with that document.

## Architecture Overview
- **Role:** Implements a TCP printable-characters counter with deterministic client/server protocol flow.
- **Server (`pcc_server`)**: Accepts TCP sessions, counts printable bytes (ASCII 32–126) from each byte stream, persists aggregated frequency metrics, and emits statistics on atomic SIGINT handling.
- **Client (`pcc_client`)**: Streams a file over TCP using the mandated protocol, prints the printable-count returned by the server, and exits with zero on success.
- **Concurrency Model:** Iterative single-threaded loop with signal-aware acceptance; SIGINT processing is atomic relative to in-flight client handling as required by the assignment text.

## Protocol Specification
1. Client sends **N** (uint32, network byte order) representing the byte-stream length.
2. Client streams **N** bytes of payload.
3. Server responds with **C** (uint32, network byte order) representing printable-byte count.
4. Server maintains global printable-frequency counters and, on SIGINT, prints only characters with non-zero frequency in ascending ASCII order plus the number of successfully served clients. These outputs match the exact format strings in `instructions_network.txt`.

## Core Networking Concepts
- **TCP Handshake:** The server binds to `INADDR_ANY`, listens with backlog `10`, and completes the three-way handshake before payload exchange. This guarantees reliable, ordered byte streams prior to protocol framing.
- **Network Byte Order (Endianness):** Length and count fields are serialized with `htonl`/`ntohl` to enforce big-endian wire representation across heterogeneous hosts.
- **PCC Algorithm:** Byte streams are ingested in bounded buffers (1 KiB) to avoid oversized allocations. Each byte is classified as printable or non-printable; printable bytes increment both per-connection and global frequency vectors. Only successful sessions contribute to `pcc_total`, ensuring TCP errors or premature disconnects do not contaminate statistics.
- **Robust Error Handling:** All critical system calls (`socket`, `bind`, `listen`, `accept`, `read`, `write`, `connect`) are validated. TCP-specific disconnect conditions (`ETIMEDOUT`, `ECONNRESET`, `EPIPE`) log and continue without mutating global counters; other failures are fatal with descriptive diagnostics.

## Build, Test, and Run
- Toolchain: `gcc`, `make` (flags: `-Wall -Werror -O3 -std=c11 -D_POSIX_C_SOURCE=200809`).
- Build artifacts: `bin/pcc_server`, `bin/pcc_client`.
- Commands:
- `make all` — compile server and client.
- `make test` — loopback integration smoke test using a temporary file and SIGINT-driven shutdown.
- `make clean` — remove build outputs.
- Manual execution examples:
- `./bin/pcc_server 5555`
- `./bin/pcc_client 127.0.0.1 5555 ./path/to/file`

## Dockerized Execution
1. Build: `docker build -t pcc .`
2. Run server: `docker run --rm -p 5555:5555 pcc ./bin/pcc_server 5555`
3. Run client (loopback to host or container IP): `docker run --rm --network host -v $(pwd):/data pcc ./bin/pcc_client 127.0.0.1 5555 /data/yourfile`

## CI/CD
- GitHub Actions workflow `ci.yml` performs `make all` and `make test` on every push and pull request to enforce compilation hygiene and protocol smoke coverage.

## Repository Layout
- `src/` — C sources (`pcc_server.c`, `pcc_client.c`).
- `docs/` — assignment and protocol instructions (`instructions_network.txt`).
- `bin/` — build outputs (generated).

## Academic Policy
Educational Use Only. The logic here is unique and easily detectable by automated plagiarism tools. Use of this code in academic assignments is strictly prohibited.
File renamed without changes.
90 changes: 0 additions & 90 deletions pcc_client.c

This file was deleted.

151 changes: 151 additions & 0 deletions src/pcc_client.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
#define _POSIX_C_SOURCE 200809
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <netinet/in.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <unistd.h>

#define BUF_SIZE 1024

static ssize_t read_exact(int fd, void *buf, size_t len) {
size_t offset = 0;
while (offset < len) {
ssize_t r = read(fd, (char *)buf + offset, len - offset);
if (r < 0) {
if (errno == EINTR) {
continue;
}
return -1;
}
if (r == 0) {
return (ssize_t)offset;
}
offset += (size_t)r;
}
return (ssize_t)offset;
}

static int write_exact(int fd, const void *buf, size_t len) {
size_t offset = 0;
while (offset < len) {
ssize_t w = write(fd, (const char *)buf + offset, len - offset);
if (w < 0) {
if (errno == EINTR) {
continue;
}
return -1;
}
if (w == 0) {
continue;
}
offset += (size_t)w;
}
return 0;
}

int main(int argc, char *argv[]) {
if (argc != 4) {
fprintf(stderr, "Usage: %s <server_ip> <port> <file>\n", argv[0]);
exit(1);
}

int fd = open(argv[3], O_RDONLY);
if (fd < 0) {
perror("failed to open file");
exit(1);
}
struct stat st;
if (fstat(fd, &st) < 0) {
perror("failed to get file size");
close(fd);
exit(1);
}

if (st.st_size > UINT32_MAX) {
fprintf(stderr, "File too large for protocol limits\n");
close(fd);
exit(1);
}

uint32_t file_size = (uint32_t)st.st_size;
uint32_t net_size = htonl(file_size);

int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
perror("socket creation failed");
close(fd);
exit(1);
}

struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons((uint16_t)atoi(argv[2]));

if (inet_pton(AF_INET, argv[1], &serv_addr.sin_addr) != 1) {
fprintf(stderr, "Invalid server IP address\n");
close(fd);
close(sockfd);
exit(1);
}

if (connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
perror("failed to connect to server");
close(fd);
close(sockfd);
exit(1);
}

if (write_exact(sockfd, &net_size, sizeof(net_size)) != 0) {
perror("failed to send file size");
close(fd);
close(sockfd);
exit(1);
}

char buf[BUF_SIZE];
while (1) {
ssize_t r = read(fd, buf, BUF_SIZE);
if (r < 0) {
if (errno == EINTR) {
continue;
}
perror("failed to read from file");
close(fd);
close(sockfd);
exit(1);
}
if (r == 0) {
break;
}

if (write_exact(sockfd, buf, (size_t)r) != 0) {
perror("failed to send file data to server");
close(fd);
close(sockfd);
exit(1);
}
}
uint32_t net_count;
if (read_exact(sockfd, &net_count, sizeof(net_count)) != (ssize_t)sizeof(net_count)) {
perror("failed to receive response from server");
close(fd);
close(sockfd);
exit(1);
}

close(fd);
close(sockfd);

printf("# of printable characters: %u\n", ntohl(net_count));
return 0;
}

// AI chat: https://github.com/features/copilot
Loading
Loading