diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d009987 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +run_* \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..7d99d02 --- /dev/null +++ b/Makefile @@ -0,0 +1,10 @@ +all: server client + +server: server.c utils.c + gcc server.c utils.c -o run_server -Wall -Wextra -pedantic -std=c11 + +client: client.c utils.c + gcc client.c utils.c -o run_client -Wall -Wextra -pedantic -std=c11 + +clean: + rm -f run_server run_client \ No newline at end of file diff --git a/client.c b/client.c new file mode 100644 index 0000000..29a20cf --- /dev/null +++ b/client.c @@ -0,0 +1,109 @@ +#include +#include +#include +#include +#include +#include +#include "utils.h" + +#define PORT_NUMBER 8080 +#define SERVER_IP "127.0.0.1" +#define IP_VERSION "IPv4" +#define BUFFER_SIZE 128 + +void connect_client_to_server(int client_socket_fd) { + // サーバアドレス構造体 + struct sockaddr_in server_address; + int domain = (strcmp(IP_VERSION, "IPv6") == 0) ? AF_INET6 : AF_INET; + server_address.sin_family = domain; + // ホストバイトオーダーからネットワークバイトオーダーへ変換 + server_address.sin_port = htons(PORT_NUMBER); + inet_pton(domain, SERVER_IP, &server_address.sin_addr); + + // サーバへの接続 + if (connect(client_socket_fd, (struct sockaddr *) &server_address, sizeof(server_address)) < 0) { + handle_error("Connection to server failed"); + } +} + +char* generate_request(const char *function, const char *query) { + // リクエストの生成 + char *request = calloc(BUFFER_SIZE, 1); + + if (strcmp(function, "calc") == 0) { + snprintf(request, BUFFER_SIZE, "GET /calc?query=%s HTTP/1.1\r\n", query); + } else { + handle_error("Unsupported function"); + } + return request; +} + +void send_request_and_get_response(int client_socket_fd, char *request) { + // タイムアウトの設定(10秒) + struct timeval timeout; + timeout.tv_sec = 10; + timeout.tv_usec = 0; + + // 送信タイムアウトの設定 + if (setsockopt(client_socket_fd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)) < 0) { + log_msg("Warning: Failed to set send timeout\n"); + } + + // 受信タイムアウトの設定 + if (setsockopt(client_socket_fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) < 0) { + log_msg("Warning: Failed to set receive timeout\n"); + } + + size_t request_len = strlen(request); + size_t sent_len = 0; + while (sent_len < request_len) { + ssize_t n = write(client_socket_fd, request + sent_len, request_len - sent_len); + if (n < 0) { + handle_error("Write to server failed"); + } + sent_len += n; + } + log_msg("Request sent:\n%s\n", request); + char *buffer = calloc(BUFFER_SIZE, 1); + ssize_t bytes_read = read(client_socket_fd, buffer, BUFFER_SIZE - 1); + if (bytes_read < 0) { + free(buffer); + handle_error("Read from server failed"); + } else if (bytes_read == 0) { + free(buffer); + handle_error("Server closed the connection"); + } + buffer[bytes_read] = '\0'; + log_msg("Response from server:\n%s\n\n", buffer); + free(buffer); +} + +int main(int argc, char **argv){ + // 引数の数をチェック + if (argc < 3) { + fprintf(stderr, "Usage: %s \n", argv[0]); + fprintf(stderr, "Example: %s calc 18+20\n", argv[0]); + exit(EXIT_FAILURE); + } + + // ソケットの初期化 + int client_socket = initialize_socket(IP_VERSION); + log_msg("Client socket initialized.\n"); + + // サーバへの接続 + connect_client_to_server(client_socket); + log_msg("Connected to server %s:%d.\n", SERVER_IP, PORT_NUMBER); + + // リクエストの送信とレスポンスの受信 + char *function = argv[1]; + char *query = argv[2]; + char *request = generate_request(function, query); + send_request_and_get_response(client_socket, request); + log_msg("Request sent and response received.\n"); + + // ソケットのクローズ + close_socket(client_socket); + log_msg("Client socket closed.\n"); + free(request); + return 0; +} \ No newline at end of file diff --git a/server.c b/server.c new file mode 100644 index 0000000..de51a15 --- /dev/null +++ b/server.c @@ -0,0 +1,193 @@ +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include +#include +#include +#include +#include +#include "utils.h" + +#define PORT_NUMBER 8080 +#define IP_VERSION "IPv4" +#define LISTEN_BACKLOG 5 +#define BUFFER_SIZE 128 + +volatile sig_atomic_t is_server_running = 1; + +void signal_handler(int signum) { + (void)signum; + is_server_running = 0; +} + +void bind_socket(int socket_fd) { + // サーバアドレス構造体: sin_family, sin_addr, sin_port + struct sockaddr_in address_info; + address_info.sin_family = AF_INET; + address_info.sin_addr.s_addr = INADDR_ANY; + address_info.sin_port = htons(PORT_NUMBER); + + // ソケットのバインド + if (bind(socket_fd, (struct sockaddr *) &address_info, sizeof(address_info)) < 0) { + handle_error("Bind failed"); + } +} + +void listen_for_connections(int socket_fd) { + // 接続待機 + if (listen(socket_fd, LISTEN_BACKLOG) < 0) { + handle_error("Listen failed"); + } +} + +int accept_connection(int server_socket_fd) { + // クライアントからの接続受付 + struct sockaddr_in client_address; + socklen_t client_address_len = sizeof(client_address); + + int client_socket_fd = accept(server_socket_fd, (struct sockaddr *) &client_address, &client_address_len); + if (client_socket_fd < 0) { + // シグナルによる中断の場合は-1を返す + if (errno == EINTR) { + return -1; + } + handle_error("Accept failed"); + } + + return client_socket_fd; +} + +char* calculate_query(const char *query) { + // クエリの計算処理 + // 例: "query=2+10" -> 12 + int operand1, operand2; + char operator; + char *buffer = calloc(BUFFER_SIZE, 1); + sscanf(query, "query=%d%c%d", &operand1, &operator, &operand2); + + char *extracted_query = calloc(BUFFER_SIZE, 1); + snprintf(extracted_query, BUFFER_SIZE, "query=%d%c%d", operand1, operator, operand2); + size_t extracted_len = strlen(extracted_query); + if (extracted_len > 0 && (strncmp(query, extracted_query, extracted_len) != 0)) { + log_msg("Malformed query: %s\n", query); + snprintf(buffer, BUFFER_SIZE, "Invalid query format"); + free(extracted_query); + return buffer; + } + free(extracted_query); + + switch (operator) { + case '+': + if ((operand2 > 0 && operand1 > INT_MAX - operand2) || + (operand2 < 0 && operand1 < INT_MIN - operand2)) { + snprintf(buffer, BUFFER_SIZE, "Overflow error"); + log_msg("Overflow detected: %d + %d\n", operand1, operand2); + } else { + snprintf(buffer, BUFFER_SIZE, "%d", operand1 + operand2); + } + return buffer; + default: + log_msg("Unsupported operator: %c\n", operator); + snprintf(buffer, BUFFER_SIZE, "Unsupported operator"); + return buffer; + } +} + +void handle_client_request(int client_socket_fd) { + // リクエストの処理 + char *buffer = calloc(BUFFER_SIZE, 1); + ssize_t bytes_read = read(client_socket_fd, buffer, BUFFER_SIZE - 1); + if (bytes_read < 0) { + log_msg("Read from client failed\n"); + } else if (bytes_read == 0) { + free(buffer); + log_msg("Client disconnected\n"); + return; + } + buffer[bytes_read] = '\0'; + log_msg("Request received:\n%s\n", buffer); + + // /calc エンドポイントの処理 + if (strncmp(buffer, "GET /calc?", 10) == 0) { + char *calculation = calculate_query(buffer + 10); + char *response = calloc(BUFFER_SIZE, 1); + size_t response_body_length = strlen(calculation); + + if (strcmp(calculation, "Invalid query format") == 0 || + strcmp(calculation, "Unsupported operator") == 0 || + strcmp(calculation, "Overflow error") == 0) { + snprintf(response, BUFFER_SIZE, "HTTP/1.1 400 Bad Request\r\nContent-Length: %zu\r\n\r\n%s", + response_body_length, calculation); + } else { + snprintf(response, BUFFER_SIZE, "HTTP/1.1 200 OK\r\nContent-Length: %zu\r\n\r\n%s", + response_body_length, calculation); + } + + size_t response_len = strlen(response); + size_t sent_len = 0; + while (sent_len < response_len) { + ssize_t n = write(client_socket_fd, response + sent_len, response_len - sent_len); + if (n < 0) { + log_msg("Write to client failed\n"); + break; + } + sent_len += n; + } + log_msg("Response sent:\n%s\n", response); + free(response); + free(calculation); + } else { + log_msg("Unsupported request method\n"); + } + free(buffer); +} + +int main(){ + // ソケットの初期化 + int server_socket = initialize_socket(IP_VERSION); + log_msg("Server socket initialized.\n"); + + // アドレスの再利用を有効化 + int optval = 1; + if (setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) == -1) { + log_msg("setsockopt SO_REUSEADDR failed"); + } + + // ソケットのバインド + bind_socket(server_socket); + log_msg("Server socket bound to port %d.\n", PORT_NUMBER); + + // クライアントからの接続を待機 + listen_for_connections(server_socket); + log_msg("Server is listening on port %d...\n----------\n", PORT_NUMBER); + + // シグナルハンドラの設定 + struct sigaction sa; + sa.sa_handler = signal_handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; // SA_RESTARTを設定しない(システムコールを自動再開しない) + if (sigaction(SIGINT, &sa, NULL) == -1) { + handle_error("sigaction failed"); + } + + // リクエストの受信と処理 + while (is_server_running) { + int client_socket = accept_connection(server_socket); + + if (client_socket < 0) continue; + + log_msg("Client connected.\n"); + + handle_client_request(client_socket); + log_msg("Client request handled.\n"); + + close_socket(client_socket); + log_msg("Client socket closed.\n----------\n"); + } + + // サーバソケットのクローズ + close_socket(server_socket); + log_msg("\r\nServer shutdown gracefully.\n"); + return 0; +} \ No newline at end of file diff --git a/utils.c b/utils.c new file mode 100644 index 0000000..6889bd5 --- /dev/null +++ b/utils.c @@ -0,0 +1,35 @@ +#include +#include +#include +#include +#include +#include +#include "utils.h" + +void handle_error(const char *msg) { + perror(msg); + exit(EXIT_FAILURE); +} + +void log_msg(const char *format, ...) { + va_list args; + va_start(args, format); + vprintf(format, args); + va_end(args); +} + +int initialize_socket(const char* ip_version) { + // ソケットの初期化 (IPv4: AF_INET, IPv6: AF_INET6) + int domain = (strcmp(ip_version, "IPv6") == 0) ? AF_INET6 : AF_INET; + int tcp_socket = socket(domain, SOCK_STREAM, 0); + if (tcp_socket < 0) handle_error("Socket creation failed"); + + return tcp_socket; +} + +void close_socket(int socket_fd) { + // ソケットのクローズ + if (close(socket_fd) < 0) { + handle_error("Close failed"); + } +} \ No newline at end of file diff --git a/utils.h b/utils.h new file mode 100644 index 0000000..cffb1ab --- /dev/null +++ b/utils.h @@ -0,0 +1,9 @@ +#ifndef UTILS_H +#define UTILS_H + +void handle_error(const char *msg); +void log_msg(const char *format, ...); +int initialize_socket(const char *ip_version); +void close_socket(int socket_fd); + +#endif // UTILS_H \ No newline at end of file