-
Notifications
You must be signed in to change notification settings - Fork 0
feat: サーバ・クライアントの実装 #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| run_* |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,109 @@ | ||
| #include <arpa/inet.h> | ||
| #include <stdio.h> | ||
| #include <stdlib.h> | ||
| #include <string.h> | ||
| #include <sys/time.h> | ||
| #include <unistd.h> | ||
| #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; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. address familyなので変数名は |
||
| server_address.sin_family = domain; | ||
| // ホストバイトオーダーからネットワークバイトオーダーへ変換 | ||
| server_address.sin_port = htons(PORT_NUMBER); | ||
| inet_pton(domain, SERVER_IP, &server_address.sin_addr); | ||
Satorien marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| // サーバへの接続 | ||
| 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); | ||
Satorien marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } else { | ||
| handle_error("Unsupported function"); | ||
Satorien marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| 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); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 今だとサーバーのソケットが閉じている時にこのループでwriteが二回以上呼び出されると、SIGPIPEを受け取ってクライアントのプロセスが落ちると思います。 |
||
| if (n < 0) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| handle_error("Write to server failed"); | ||
| } | ||
| sent_len += n; | ||
| } | ||
|
Comment on lines
+59
to
+65
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. short count対策しているのが良いと思いました! (nits) サーバー側でも同様の処理をしているのでラッパー関数を書いても良いかもしれません。 |
||
| 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); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. readは読み込み要求のサイズ以下を返す可能性があるのでwriteでやっているようにループした方が良いと思います。 https://linuxjm.sourceforge.io/html/LDP_man-pages/man2/read.2.html |
||
| 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 <function> <query>\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); | ||
Satorien marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| log_msg("Request sent and response received.\n"); | ||
|
|
||
| // ソケットのクローズ | ||
| close_socket(client_socket); | ||
| log_msg("Client socket closed.\n"); | ||
| free(request); | ||
| return 0; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,193 @@ | ||
| #define _POSIX_C_SOURCE 200809L | ||
| #include <errno.h> | ||
| #include <limits.h> | ||
| #include <netinet/in.h> | ||
| #include <signal.h> | ||
| #include <stdio.h> | ||
| #include <stdlib.h> | ||
| #include <string.h> | ||
| #include <unistd.h> | ||
| #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); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. writeと同様readもshort countの可能性があるのでループして要求した文字数分読めたかチェックした方が良さそうです。 |
||
| if (bytes_read < 0) { | ||
| log_msg("Read from client failed\n"); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ログを出して後続の処理に行ってしまうのでreturnした方が良いと思います。 |
||
| } 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); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. https://github.com/Satorien/Socket-Programming/pull/1/changes#r2658611560 |
||
| 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"); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. エラーを見る限り致命的なものしかないので、ここで失敗した場合は処理を終了した方が良い気がします。 |
||
| } | ||
|
|
||
| // ソケットのバインド | ||
| bind_socket(server_socket); | ||
|
Comment on lines
+148
to
+158
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. https://github.com/Satorien/Socket-Programming/pull/1/changes#r2658599149 |
||
| 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) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. シグナルハンドラでフラグを変更してgraceful shutdownに対応しているは良いですね。参考になりました。 |
||
| 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; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| #include <stdarg.h> | ||
| #include <stdio.h> | ||
| #include <stdlib.h> | ||
| #include <string.h> | ||
| #include <sys/socket.h> | ||
| #include <unistd.h> | ||
| #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); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 今の書き方だとIPv4 or IPv6対応になっていると思うのですが、setsockoptで |
||
| 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"); | ||
| } | ||
| } | ||
|
Comment on lines
+30
to
+35
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ソケットクローズ時のエラー対応しているの良いですね! |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IPv6の場合
sockaddr_inではなくsockaddr_in6を利用する必要があるので、ここの場合わけも必要だと思います。ref: https://www.coins.tsukuba.ac.jp/~syspro/2019/2019-06-05/sockaddr-struct.html
getaddrinfoを利用するとサーバーがIPv4・IPv6どちらに対応しているかを気にせず扱えるのでこ
ちらを利用しても良いかもしれません。
https://linuxjm.sourceforge.io/html/LDP_man-pages/man3/getaddrinfo.3.html