Refactored project structure, added multithreaded echo server and prime checking server, updated tests.

This commit is contained in:
2025-07-28 17:31:05 +01:00
parent 4926cfe0d3
commit fec3f555e9
14 changed files with 621 additions and 110 deletions

View File

@@ -1,2 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<module classpath="CMake" type="CPP_MODULE" version="4" /> <module classpath="CMake" type="CPP_MODULE" version="4">
<component name="FacetManager">
<facet type="Python" name="Python facet">
<configuration sdkName="Python 3.13" />
</facet>
</component>
</module>

3
.idea/misc.xml generated
View File

@@ -1,5 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="Black">
<option name="sdkName" value="Python 3.13" />
</component>
<component name="CMakePythonSetting"> <component name="CMakePythonSetting">
<option name="pythonIntegrationState" value="YES" /> <option name="pythonIntegrationState" value="YES" />
</component> </component>

View File

@@ -1,10 +1,24 @@
cmake_minimum_required(VERSION 3.31) cmake_minimum_required(VERSION 3.31)
project(cprotohackers C) project(server00 C)
set(CMAKE_C_STANDARD 23) set(CMAKE_C_STANDARD 23)
add_executable(cprotohackers main.c
main.h set(COMMON_SOURCES
data.c data.c
data.h) data.h)
target_link_libraries(cprotohackers wsock32 ws2_32)
add_executable(server00 server00.c
server00.h
${COMMON_SOURCES})
target_link_libraries(server00 wsock32 ws2_32)
find_package(json-c CONFIG REQUIRED)
add_executable(server01 server01.c
server01.h
${COMMON_SOURCES})
target_link_libraries(server01 PRIVATE wsock32 ws2_32 json-c::json-c)

58
data.c
View File

@@ -6,6 +6,25 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#define PORT 40000
SOCKET get_listen_socket() {
WSADATA WSAData;
SOCKET client;
SOCKADDR_IN serverAddr, clientAddr;
WSAStartup(MAKEWORD(2,0), &WSAData);
SOCKET server = socket(AF_INET, SOCK_STREAM, 0);
serverAddr.sin_addr.s_addr = INADDR_ANY;
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(PORT);
bind(server, (SOCKADDR *)&serverAddr, sizeof(serverAddr));
listen(server, 0);
return server;
}
char_array_t *char_array_create(int capacity) { char_array_t *char_array_create(int capacity) {
char_array_t *array = malloc(sizeof(char_array_t)); char_array_t *array = malloc(sizeof(char_array_t));
@@ -22,10 +41,11 @@ void char_array_append(char_array_t *array, char *value, size_t length) {
if (array->data == NULL) { if (array->data == NULL) {
exit(1); exit(1);
} }
printf("old size: %llu\n", array->size); // char *temp = calloc(length+1, sizeof(char));
printf("length: %llu\n", length); // memcpy(temp, value, length);
// printf("Appending '%s' bytes\n", temp);
// free(temp);
size_t new_size = array->size + length; size_t new_size = array->size + length;
printf("New size: %llu\n", new_size);
if (new_size > array->capacity) { if (new_size > array->capacity) {
array->capacity = new_size; array->capacity = new_size;
char *new_array = realloc(array->data, array->capacity); char *new_array = realloc(array->data, array->capacity);
@@ -47,3 +67,35 @@ void char_array_wipe(char_array_t *array) {
array->size = 0; array->size = 0;
memset(array->data, 0, array->capacity); memset(array->data, 0, array->capacity);
}; };
bool char_array_has_char(char_array_t *array, char c) {
const char *ret = memchr(array->data, c, array->size);
if (ret == NULL) {
return false;
}
return true;
};
char *char_array_get_until_char(char_array_t *array, char c) {
size_t idx = 0;
for (size_t i = 0; i < array->size; i++) {
if (array->data[i] == c) {
idx = i;
break;
}
}
if (idx == 0) {
return NULL;
}
char *ret = malloc(idx+1);
memcpy(ret, array->data, idx);
ret[idx] = '\0';
char *temp = calloc(array->capacity, sizeof(char));
size_t new_i = 0;
for (size_t i = idx+1; i < array->size; i++) {
temp[new_i++] = array->data[i];
}
free(array->data);
array->data = temp;
array->size = new_i;
return ret;
};

6
data.h
View File

@@ -1,6 +1,6 @@
#pragma once #pragma once
#include <stdio.h> #include <stdio.h>
#include <winsock2.h>
typedef struct CharArray { typedef struct CharArray {
size_t size; size_t size;
size_t capacity; size_t capacity;
@@ -12,3 +12,7 @@ void char_array_destroy(char_array_t *array);
void char_array_append(char_array_t *array, char *value, size_t length); void char_array_append(char_array_t *array, char *value, size_t length);
void char_array_print(const char_array_t *array); void char_array_print(const char_array_t *array);
void char_array_wipe(char_array_t *array); void char_array_wipe(char_array_t *array);
bool char_array_has_char(char_array_t *array, char c);
char *char_array_get_until_char(char_array_t *array, char c);
SOCKET get_listen_socket();

55
echo_server_test.py Normal file
View File

@@ -0,0 +1,55 @@
import socket
import time
def test_echo_server(host='localhost', port=40000, message="Hello, Echo Server!"):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
try:
s.connect((host, port))
print(f"Connected to {host}:{port}")
# Send the message
print(f"Sending message of length: {len(message)}")
s.sendall(message.encode())
s.shutdown(socket.SHUT_WR)
# Receive the response in chunks
chunks = []
bytes_received = 0
while bytes_received < len(message):
chunk = s.recv(1024)
if not chunk:
break
chunks.append(chunk)
bytes_received += len(chunk)
time.sleep(0.1) # Match the server's Sleep(100)
response = b''.join(chunks).decode()
print(f"Received {len(response)} bytes")
# Check if it's a true echo
if response == message:
print("SUCCESS: Received message matches sent message!")
else:
print("FAILURE: Received message differs from sent message!")
if len(response) != len(message):
print(f"Length mismatch: sent {len(message)}, received {len(response)}")
except ConnectionRefusedError:
print("Connection failed! Make sure the server is running.")
except Exception as e:
print(f"An error occurred: {e}")
if __name__ == "__main__":
# Test with a simple message
test_echo_server()
# Test with a longer message
test_echo_server(message="This is a longer message to test the echo server's ability to handle larger strings!")
# Test with special characters
test_echo_server(message="Special chars: !@#$%^&*()")
# Test with a large message
large_message = "X" * 10000 # 10KB of data
test_echo_server(message=large_message)

79
main.c
View File

@@ -1,79 +0,0 @@
#include <stdio.h>
#include <winsock2.h>
#include <pthread.h>
#include "main.h"
#include "data.h"
#define PORT 40000
#define BUFFER_SIZE 1024
int main()
{
// load_file();
WSADATA WSAData;
SOCKET client;
SOCKADDR_IN serverAddr, clientAddr;
WSAStartup(MAKEWORD(2,0), &WSAData);
SOCKET server = socket(AF_INET, SOCK_STREAM, 0);
serverAddr.sin_addr.s_addr = INADDR_ANY;
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(PORT);
bind(server, (SOCKADDR *)&serverAddr, sizeof(serverAddr));
listen(server, 0);
printf("Listening for incoming connections...\n");
char buffer[BUFFER_SIZE] = {0};
int clientAddrSize = sizeof(clientAddr);
int recieved;
char_array_t *reply = char_array_create(1024);
while((client = accept(server, (SOCKADDR *)&clientAddr, &clientAddrSize)) != INVALID_SOCKET)
{
printf(" Client connected!\n");
while ((recieved = recv(client, buffer, sizeof(buffer), 0)) > 0) {
printf(" Client says: |%.1024s|\n",buffer);
char_array_append(reply, buffer, recieved);
if (buffer[recieved] == '\0') {
break;
}
memset(buffer, 0, sizeof(buffer));
Sleep(10);
}
printf(" Sending back...\n");
printf(" Sending back: |%d| \n", reply->size);
send(client, reply->data,reply->size,0);
char_array_wipe(reply);
closesocket(client);
printf("Client disconnected.\n");
}
}
void echo(SOCKET client) {
char buffer[BUFFER_SIZE] = {0};
char_array_t *reply = char_array_create(BUFFER_SIZE);
int bytesReceived;
while ((bytesReceived = recv(client, buffer, sizeof(buffer), 0)) > 0) {
printf(" Client says: |%.1024s|\n",buffer);
char_array_append(reply, buffer, bytesReceived);
if (buffer[bytesReceived] == '\0') {
break;
}
memset(buffer, 0, sizeof(buffer));
Sleep(10);
}
printf(" Sending back...\n");
printf(" Sending back: |%llu| \n", reply->size);
send(client, reply->data,reply->size,0);
closesocket(client);
}

21
main.h
View File

@@ -1,21 +0,0 @@
//
// Created by Ajurna on 27/07/2025.
//
#ifndef MAIN_H
#define MAIN_H
#endif //MAIN_H
typedef struct ReplyArray {
size_t size;
size_t capacity;
char *data;
} array_t;
array_t *array_create(int size);
void array_destroy(array_t *array);
void array_append(array_t *array, char *value, size_t length);
void array_print(const array_t *array);
void array_wipe(array_t *array);

View File

@@ -0,0 +1,84 @@
import socket
import threading
import time
import random
import string
def random_string(length):
"""Generate a random string of fixed length"""
return ''.join(random.choices(string.ascii_letters + string.digits, k=length))
def echo_client(client_id, message):
"""Individual client function that connects and sends/receives data"""
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
try:
s.connect(('localhost', 40000))
print(f"Client {client_id}: Connected")
# Send the message
print(f"Client {client_id}: Sending message of length {len(message)}")
s.sendall(message.encode())
# Receive the response in chunks
chunks = []
bytes_received = 0
while bytes_received < len(message):
chunk = s.recv(1024)
if not chunk:
break
chunks.append(chunk)
bytes_received += len(chunk)
time.sleep(0.1)
response = b''.join(chunks).decode()
# Verify response
if response == message:
print(f"Client {client_id}: SUCCESS - Message verified ({len(response)} bytes)")
else:
print(f"Client {client_id}: FAILURE - Message mismatch!")
print(f"Client {client_id}: Expected length: {len(message)}, Got: {len(response)}")
except Exception as e:
print(f"Client {client_id}: Error - {str(e)}")
def run_parallel_test(num_clients=5, message_size_range=(100, 5000)):
"""Run multiple clients in parallel"""
threads = []
print(f"Starting parallel test with {num_clients} clients...")
# Create and start threads
for i in range(num_clients):
# Generate random message size between min and max range
msg_size = random.randint(*message_size_range)
message = random_string(msg_size)
thread = threading.Thread(target=echo_client, args=(i, message))
threads.append(thread)
thread.start()
# Small delay between starting threads
time.sleep(0.1)
# Wait for all threads to complete
for thread in threads:
thread.join()
print("All clients finished")
if __name__ == "__main__":
# Test 1: Few clients with small messages
print("\nTest 1: 3 parallel clients with small messages")
run_parallel_test(num_clients=3, message_size_range=(100, 500))
time.sleep(1) # Wait between tests
# Test 2: More clients with varying message sizes
print("\nTest 2: 5 parallel clients with varying message sizes")
run_parallel_test(num_clients=5, message_size_range=(1000, 5000))
time.sleep(1) # Wait between tests
# Test 3: Stress test with many clients
print("\nTest 3: Stress test with 10 parallel clients")
run_parallel_test(num_clients=10, message_size_range=(500, 10000))

163
prime_time_server_tests.py Normal file
View File

@@ -0,0 +1,163 @@
import socket
import json
import threading
import time
import random
def send_request(sock, request):
"""Send a request and get response"""
request_str = json.dumps(request) + '\n'
sock.sendall(request_str.encode())
response = sock.recv(1024).decode()
try:
return json.loads(response.strip())
except json.JSONDecodeError:
return response.strip()
def batch_test_client():
"""Run a test with 50 problems in quick succession"""
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
try:
sock.connect(('localhost', 40000))
print("Batch Test: Connected")
# Generate 50 test cases with known prime and non-prime numbers
test_numbers = (
# Known primes
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47] +
# Non-primes
[4, 6, 8, 9, 10, 12, 14, 15, 16, 18, 20, 21, 22, 24, 25] +
# Edge cases
[0, 1, -1, 1.5, 2.0] +
# Larger numbers
[97, 100, 121, 169, 289, 361, 529, 841, 961, 1001] +
# Random numbers to complete 50
[random.randint(1000, 10000) for _ in range(5)]
)
success_count = 0
start_time = time.time()
for i, number in enumerate(test_numbers):
request = {"method": "isPrime", "number": number}
try:
response = send_request(sock, request)
expected_prime = (
isinstance(number, int) and
number > 1 and
all(number % i != 0 for i in range(2, int(number ** 0.5) + 1))
)
expected = {"method": "isPrime", "prime": expected_prime}
if response == expected:
success_count += 1
print(f"✓ Test {i+1}/50: {number} -> {response['prime']}")
else:
print(f"✗ Test {i+1}/50: {number} Expected: {expected}, Got: {response}")
except Exception as e:
print(f"Error on test {i+1}/50 with number {number}: {e}")
break
end_time = time.time()
duration = end_time - start_time
print(f"\nBatch Test Results:")
print(f"Total tests: 50")
print(f"Successful: {success_count}")
print(f"Failed: {50 - success_count}")
print(f"Time taken: {duration:.2f} seconds")
print(f"Average time per request: {(duration/50)*1000:.2f} ms")
except ConnectionRefusedError:
print("Batch Test: Connection failed! Is the server running?")
def test_client(test_cases, client_id):
"""Run a series of test cases"""
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
try:
sock.connect(('localhost', 40000))
print(f"Client {client_id}: Connected")
for i, test in enumerate(test_cases):
request = test['request']
expected = test['expected']
desc = test.get('desc', 'No description')
print(f"\nClient {client_id}: Test {i + 1} - {desc}")
print(f"Request: {request}")
try:
response = send_request(sock, request)
print(f"Response: {response}")
if test.get('should_disconnect', False):
try:
send_request(sock, {"method": "isPrime", "number": 2})
print("ERROR: Server didn't disconnect when it should have")
except:
print("SUCCESS: Server correctly disconnected")
break
if response == expected:
print("SUCCESS: Response matches expected")
else:
print(f"FAILURE: Expected {expected}, got {response}")
except Exception as e:
print(f"Error during test: {e}")
if test.get('should_disconnect', False):
print("SUCCESS: Expected disconnect occurred")
else:
print("FAILURE: Unexpected error")
break
except ConnectionRefusedError:
print(f"Client {client_id}: Connection failed! Is the server running?")
def run_all_tests():
print("Starting Prime Time Server tests...")
# Test 1: Basic functionality
print("\nTest 1: Single client with valid requests")
test_client([{
'desc': "Basic prime test",
'request': {"method": "isPrime", "number": 7},
'expected': {"method": "isPrime", "prime": True}
}], 0)
time.sleep(1)
# Test 2: Malformed request
print("\nTest 2: Single client with malformed request")
test_client([{
'desc': "Malformed request test",
'request': {"method": "wrong", "number": 7},
'expected': "malformed",
'should_disconnect': True
}], 0)
time.sleep(1)
# Test 3: Batch test with 50 problems
print("\nTest 3: Batch test with 50 problems")
batch_test_client()
time.sleep(1)
# Test 4: Multiple parallel clients
print("\nTest 4: Multiple parallel clients")
threads = []
for i in range(5):
thread = threading.Thread(target=batch_test_client)
threads.append(thread)
thread.start()
time.sleep(0.1)
for thread in threads:
thread.join()
if __name__ == "__main__":
run_all_tests()

68
server00.c Normal file
View File

@@ -0,0 +1,68 @@
#include <stdio.h>
#include <winsock2.h>
#include <pthread.h>
#include "server00.h"
#include "data.h"
#define PORT 40000
#define BUFFER_SIZE 1024
int main()
{
// load_file();
WSADATA WSAData;
SOCKET client;
SOCKADDR_IN serverAddr, clientAddr;
WSAStartup(MAKEWORD(2,0), &WSAData);
SOCKET server = socket(AF_INET, SOCK_STREAM, 0);
serverAddr.sin_addr.s_addr = INADDR_ANY;
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(PORT);
bind(server, (SOCKADDR *)&serverAddr, sizeof(serverAddr));
listen(server, 0);
printf("Listening for incoming connections...\n");
int clientAddrSize = sizeof(clientAddr);
int connection_number = 0;
char_array_t *reply = char_array_create(1024);
while((client = accept(server, (SOCKADDR *)&clientAddr, &clientAddrSize)) != INVALID_SOCKET)
{
struct EchoArgs *args = malloc(sizeof(struct EchoArgs));
args->client = client;
args->connection = connection_number++;
pthread_t thread;
pthread_create(&thread, nullptr, echo,(void *) args);
}
}
void *echo(void *args) {
const auto echoArgs = (struct EchoArgs *) args;
char buffer[BUFFER_SIZE] = {0};
char_array_t *reply = char_array_create(BUFFER_SIZE);
int bytesReceived;
int bytesTotal = 0;
while ((bytesReceived = recv(echoArgs->client, buffer, sizeof(buffer), 0)) > 0) {
// printf(" Client {%d} says: |%.1024s|\n", echoArgs->connection ,buffer);
printf("Client sent {%d}: |%d| \n", echoArgs->connection, bytesReceived);
char_array_append(reply, buffer, bytesReceived);
bytesTotal += bytesReceived;
memset(buffer, 0, sizeof(buffer));
Sleep(20);
}
printf("Total bytes received {%d}: %d\n", echoArgs->connection, bytesTotal);
printf("Sending back {%d}: |%llu| \n", echoArgs->connection, reply->size);
send(echoArgs->client, reply->data,reply->size,0);
closesocket(echoArgs->client);
free(echoArgs);
return NULL;
}

17
server00.h Normal file
View File

@@ -0,0 +1,17 @@
//
// Created by Ajurna on 27/07/2025.
//
#ifndef MAIN_H
#define MAIN_H
#endif //MAIN_H
#include <winsock2.h>
#pragma once
struct EchoArgs {
int connection;
SOCKET client;
};
void *echo(void *args);

124
server01.c Normal file
View File

@@ -0,0 +1,124 @@
//
// Created by PeterDwyer on 28/07/2025.
//
#include "server01.h"
#include <math.h>
#include <stdio.h>
#include <json-c/json_tokener.h>
#include <json-c/json_object.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>
#include "data.h"
#define IS_PRIME "{\"method\":\"isPrime\",\"prime\":true}\n"
#define IS_NOT_PRIME "{\"method\":\"isPrime\",\"prime\":false}\n"
#define IS_PRIME_ERROR "{\"method\":\"isPrime\",\"error\":true}\n"
int main() {
SOCKET server = get_listen_socket();
SOCKADDR_IN serverAddr, clientAddr;
SOCKET client;
int clientAddrSize = sizeof(clientAddr);
int connection_number = 1;
while((client = accept(server, (SOCKADDR *)&clientAddr, &clientAddrSize)) != INVALID_SOCKET)
{
handle_args_t *args = malloc(sizeof(handle_args_t));
args->client = client;
args->connection = connection_number++;
pthread_t thread;
pthread_create(&thread, nullptr, handle_connection, args);
}
return 0;
}
void *handle_connection(void *args) {
handle_args_t *handleArgs = args;
char buffer[1024] = {0};
int bytesReceived;
char_array_t *data = char_array_create(1024);
while ((bytesReceived = recv(handleArgs->client, buffer, sizeof(buffer), 0)) > 0) {
printf("Client sent {%d}: |%d| \n", handleArgs->connection, bytesReceived);
char_array_append(data, buffer, bytesReceived);
char *request;
while ((request = char_array_get_until_char(data, '\n')) != NULL) {
parse_request(handleArgs, request, data);
}
memset(buffer, 0, sizeof(buffer));
}
pthread_exit(NULL);
}
void parse_request(handle_args_t *handleArgs, const char *request, char_array_t *data) {
if (request != NULL) {
const json_object *obj = json_tokener_parse(request);
if (obj == NULL) {
printf("{%d} Failed to parse json\n", handleArgs->connection);
send(handleArgs->client, IS_PRIME_ERROR, strlen(IS_PRIME_ERROR), 0);
char_array_destroy(data);
closesocket(handleArgs->client);
pthread_exit(NULL);
}
json_object *method_obj = json_object_object_get(obj, "method");
json_object *number_obj = json_object_object_get(obj, "number");
if (method_obj == NULL || number_obj == NULL) {
printf("{%d} Missing Fields\n", handleArgs->connection);
send(handleArgs->client, IS_PRIME_ERROR, strlen(IS_PRIME_ERROR), 0);
char_array_destroy(data);
closesocket(handleArgs->client);
pthread_exit(NULL);
}
const int number_type = json_object_get_type(number_obj);
if (!(json_object_is_type(method_obj, json_type_string) && (number_type== json_type_double || number_type == json_type_int))) {
printf("{%d} Fields Wrong Type\n", handleArgs->connection);
send(handleArgs->client, IS_PRIME_ERROR, strlen(IS_PRIME_ERROR), 0);
char_array_destroy(data);
closesocket(handleArgs->client);
pthread_exit(NULL);
}
const char *method = json_object_get_string(method_obj);
if (strcmp(method, "isPrime") != 0) {
printf("{%d} Invalid method\n", handleArgs->connection);
send(handleArgs->client, IS_PRIME_ERROR, strlen(IS_PRIME_ERROR), 0);
char_array_destroy(data);
closesocket(handleArgs->client);
}
if (number_type == json_type_double) {
printf("{%d} Floats cannot be prime\n", handleArgs->connection);
send(handleArgs->client, IS_NOT_PRIME, strlen(IS_NOT_PRIME), 0);
}
const int number = json_object_get_int(number_obj);
bool number_is_prime;
if (number == INT32_MAX) {
number_is_prime = false;
} else {
number_is_prime = is_prime(number);
}
printf("{%d} '%d' is prime: %d\n", handleArgs->connection, number, number_is_prime);
char *response = number_is_prime ? IS_PRIME : IS_NOT_PRIME;
send(handleArgs->client, response, strlen(response), 0);
}
}
bool is_prime(const int number) {
if (number == 2) {
return true;
}
if (number < 2 || number % 2 == 0) {
return false;
}
const int limit = (int)sqrt(number);
for (int i = 3; i <= limit; i+=2) {
if (number % i == 0) {
return false;
}
}
return true;
}

21
server01.h Normal file
View File

@@ -0,0 +1,21 @@
//
// Created by PeterDwyer on 28/07/2025.
//
#ifndef SERVER01_H
#define SERVER01_H
#include <winsock2.h>
#include "data.h"
#pragma once
typedef struct HandleArgs {
int connection;
SOCKET client;
} handle_args_t;
#endif //SERVER01_H
bool is_prime(const int number);
void *handle_connection(void *args);
void parse_request(handle_args_t *handleArgs, const char *request, char_array_t *data);