diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..45d322a Binary files /dev/null and b/.DS_Store differ diff --git a/socket_programming/.DS_Store b/socket_programming/.DS_Store new file mode 100644 index 0000000..e204da8 Binary files /dev/null and b/socket_programming/.DS_Store differ diff --git a/socket_programming/README.md b/socket_programming/README.md new file mode 100644 index 0000000..84c8a97 --- /dev/null +++ b/socket_programming/README.md @@ -0,0 +1,53 @@ +## ビルド実行方法 +```shell +gcc -Wall -Wextra -o server server.c +gcc -Wall -Wextra -o client client.c + +./server & # サーバー起動 + +# curl でテスト +curl "http://localhost:8080/calc?query=2+10" # → 12 +curl "http://localhost:8080/calc?query=3*4" # → 12 +curl "http://localhost:8080/calc?query=10/0" # → Bad Request + +# 自作クライアントでテスト +➜ socket_programming git:(socket-programming) ✗ ./client "10+2" +HTTP/1.1 200 OK +Content-Length: 2 +Content-Type: text/plain +Connection: close + +12 +➜ socket_programming git:(socket-programming) ✗ ./client "10*2" +HTTP/1.1 200 OK +Content-Length: 2 +Content-Type: text/plain +Connection: close + +20 +➜ socket_programming git:(socket-programming) ✗ ./client "10-2" +HTTP/1.1 200 OK +Content-Length: 1 +Content-Type: text/plain +Connection: close + +8 +➜ socket_programming git:(socket-programming) ✗ ./client "10/0" +HTTP/1.1 400 Bad Request +Content-Length: 11 +Content-Type: text/plain +Connection: close + +Bad Request +➜ socket_programming git:(socket-programming) ✗ ./client "10/2" +HTTP/1.1 200 OK +Content-Length: 1 +Content-Type: text/plain +Connection: close + +5 + + +## 対応OS +- Mac / Linux (Ubuntu 等) +- Windows は非対応 (unistd.h 等の POSIX ヘッダーが使用不可なため。対応するには Winsock に書き直す必要あり) \ No newline at end of file diff --git a/socket_programming/addrinfo_test b/socket_programming/addrinfo_test new file mode 100755 index 0000000..ee6759a Binary files /dev/null and b/socket_programming/addrinfo_test differ diff --git a/socket_programming/addrinfo_test.c b/socket_programming/addrinfo_test.c new file mode 100644 index 0000000..0e1992a --- /dev/null +++ b/socket_programming/addrinfo_test.c @@ -0,0 +1,34 @@ +#include +#include +#include +#include +#include +#include + +int main() { + struct addrinfo hints, *res, *p; + memset(&hints, 0, sizeof hints); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + printf("before: res = %p\n", (void *)res); + getaddrinfo("localhost", "8080", &hints, &res); + printf("after: res = %p\n", (void *)res); + + int count = 0; + for (p = res; p != NULL; p = p->ai_next) { + char ipstr[INET6_ADDRSTRLEN]; + if (p->ai_family == AF_INET) { + struct sockaddr_in *s = (struct sockaddr_in *)p->ai_addr; + inet_ntop(AF_INET, &s->sin_addr, ipstr, sizeof ipstr); + printf("[%d] IPv4: %s\n", count, ipstr); + } else { + struct sockaddr_in6 *s = (struct sockaddr_in6 *)p->ai_addr; + inet_ntop(AF_INET6, &s->sin6_addr, ipstr, sizeof ipstr); + printf("[%d] IPv6: %s\n", count, ipstr); + } + count++; + } + printf("合計: %d個\n", count); + freeaddrinfo(res); +} \ No newline at end of file diff --git a/socket_programming/client b/socket_programming/client new file mode 100755 index 0000000..e796aff Binary files /dev/null and b/socket_programming/client differ diff --git a/socket_programming/client.c b/socket_programming/client.c new file mode 100644 index 0000000..73c39d5 --- /dev/null +++ b/socket_programming/client.c @@ -0,0 +1,98 @@ +#include +#include +#include +#include +#include +#include +#include + +// サーバーのIPアドレスとPORT番号 +// ドメイン名(例: google.com)が指定された場合: +// 1. まず ISP(ソフトバンク、NTTなど)の DNS サーバーに問い合わせる +// 2. ISP が知らなければルート DNS サーバー(世界13台)に問い合わせる +// 3. さらに権威 DNS サーバーに問い合わせて IP アドレスを取得 +// 4. 返ってきた IP アドレスの HOST:PORT に connect() しに行く +// ※ DNSはサーバーのポートは返さないが、PORTは http: 80, https: 443 なので特別指定しなくても問題ない + +// 自分のPCが契約しているプロバイダのDNSをちゃんと使っているのか調べてみた +// ➜ / cat etc/resolv.conf : 自分のルーターが使っているIPアドレスがわかる +// ➜ / scutil --dns: +// → / nslookup xxxx:xxxx:xx:xxx....:xxx +// → / whois xxxx:xxxx:xx:xxx....:xxx: descr: BBIX IPv6 Network SoftbankBB ABUSE e-mail: abuse@e.softbank.co.jp +// うん、ちゃんと使ってた +#define HOST "127.0.0.1" +#define PORT "8080" + +int main(int argc, char *argv[]) { + const char *expr = (argc > 1) ? argv[1] : "2+10"; + + struct addrinfo hints; + struct addrinfo *res; + + memset(&hints, 0, sizeof hints); + // hints を全て0埋めしてくれる + // addrinfo の構造体は以下 + // struct addrinfo { + // int ai_flags; /* input flags */ + // int ai_family; /* protocol family for socket */ --> AF-INET(IPv4) or AF_INET6(IPv6) + // int ai_socktype; /* socket type */ --> TCP or UDP + // int ai_protocol; /* protocol for socket */ --> IPPROTO_TCP or IPPROTO_UDP + // socklen_t ai_addrlen; /* length of socket-address */ --> ai_addr のサイズ + // struct sockaddr *ai_addr; /* socket-address for socket */ --> 実際のアドレス+ポート番号 + // char *ai_canonname; /* canonical name for service location */ --> ホスト名 + // struct addrinfo *ai_next; /* pointer to next in list */ --> 次の addrinfo ノードへのポインタ + // }; + // *ai_next が LinkedList みたいにポインタで次の要素を指してくれるので、server.c 側で for (p = res; p != NULL; p->ai_next)とかけた + // なぜLinkedListのような構造なのか? + // 1つのホストに対して、複数アドレスが返ってくる可能性があるため + // 検証コードを addrinfo_text.c において、実行結果を見ると + // ➜ socket_programming git:(socket-programming) ✗ gcc -o addrinfo_test addrinfo_test.c && ./addrinfo_test + // [0] IPv6: ::1 + // [1] IPv4: 127.0.0.1 + // 合計: 2個 + // IPv4, IPv6 の2つのアドレスが返ってくる、server.c では、この2つのアドレスに対して bind を試みる + + hints.ai_family = AF_UNSPEC; // IPv4, IPv6 どちらも対応 + hints.ai_socktype = SOCK_STREAM; + // man 2 socket で確認。 + // TCP のこと、順序保証、信頼性、双方向接続 + // A SOCK_STREAM type provides sequenced, reliable, two-way connection based byte streams. + // SOCK_DGRAM: UDP, connectionless, unreliable message + // SOCK_RAW: 内部ネットワークプロトコルへの直接アクセス、root権限が必要 + + int rv = getaddrinfo(HOST, PORT, &hints, &res); // HOST: 接続先のIPアドレス, PORT: 接続先のポート + if (rv != 0) { fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv)); return 1; } + + int sfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); + // アロー演算子、ポインタ経由でアクセスしている + // Java で書くところの res.ai_family と同じ + // man 2 socket + // RETURN VALUES + // A -1 is returned if an error occurs, otherwise the return value is a descriptor referencing the socket. + + if (sfd < 0) { perror("socket"); freeaddrinfo(res); return 1; } + + if (connect(sfd, res->ai_addr, res->ai_addrlen) < 0) { perror("connect"); close(sfd); freeaddrinfo(res); return 1; } + freeaddrinfo(res); + + /* リクエスト送信 */ + char req[512]; // スタック領域に512byte確保 + int reqlen = snprintf(req, sizeof req, + "GET /calc?query=%s HTTP/1.1\r\n" + "Host: localhost\r\n" + "Connection: close\r\n" + "\r\n", + expr); + write(sfd, req, reqlen); + + /* レスポンス受信・表示 */ + char buf[4096]; // スタック領域に4096byte確保、スタックはメモリ領域の名前で、取り出されると自動でその分のメモリは解放される特徴がある。free()不要 + ssize_t n; + while ((n = read(sfd, buf, sizeof buf - 1)) > 0) { + buf[n] = '\0'; + printf("%s", buf); + } + printf("\n"); + close(sfd); + return 0; +} diff --git a/socket_programming/server b/socket_programming/server new file mode 100755 index 0000000..3a84cbc Binary files /dev/null and b/socket_programming/server differ diff --git a/socket_programming/server.c b/socket_programming/server.c new file mode 100644 index 0000000..4f7d0ee --- /dev/null +++ b/socket_programming/server.c @@ -0,0 +1,218 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PORT "8080" +#define BACKLOG 10 +#define BUF_SIZE 4096 + +/* ------------------------------------------------------- + * /calc?query= を評価する + * 対応演算子: + - * / + * 返り値: 動的確保した結果文字列(呼び出し元が free する) + * エラー時は NULL + * ------------------------------------------------------- */ +static char *calc(const char *query_string) { + /* query_string は "query=2+10" など */ + const char *eq = strchr(query_string, '='); + if (!eq) return NULL; + + const char *expr = eq + 1; // const char* + printf("DEBUG expr: [%s]\n", expr); + + double left, right; + char op; + if (sscanf(expr, " %lf %c %lf", &left, &op, &right) != 3) { + return NULL; // free(expr) を削除 + } + + double result; + switch (op) { + case '+': result = left + right; break; + case '-': result = left - right; break; + case '*': result = left * right; break; + case '/': + if (right == 0) return NULL; + result = left / right; + break; + default: return NULL; + } + + /* 整数なら整数表示、そうでなければ小数表示 */ + char *buf = malloc(64); + if (!buf) { perror("malloc"); exit(1); } + if (result == (long long)result) + snprintf(buf, 64, "%lld", (long long)result); + else + snprintf(buf, 64, "%g", result); + return buf; +} + +/* ------------------------------------------------------- + * 1リクエストの処理 + * ------------------------------------------------------- */ +static void handle_request(int fd) { + /* リクエスト全体を読む */ + char *req = malloc(BUF_SIZE); + if (!req) { perror("malloc"); exit(1); } + ssize_t total = 0; + while (total < BUF_SIZE - 1) { + ssize_t n = read(fd, req + total, BUF_SIZE - 1 - total); + if (n < 0) { perror("read"); free(req); req = NULL; return; } + if (n == 0) break; + total += n; + /* ヘッダー終端 \r\n\r\n が来たら終了 */ + req[total] = '\0'; + if (strstr(req, "\r\n\r\n")) break; + } + req[total] = '\0'; + printf("--- request ---\n%s\n", req); + + /* リクエストライン解析: "GET /path?query HTTP/1.x" */ + char method[16], path[1024], version[16]; + if (sscanf(req, "%15s %1023s %15s", method, path, version) != 3) { + free(req); + req = NULL; + return; + } + free(req); + req = NULL; + + /* パスとクエリ文字列に分割 */ + char *qmark = strchr(path, '?'); + char *query_string = NULL; + if (qmark) { + *qmark = '\0'; + query_string = qmark + 1; + } + + /* レスポンスボディ生成 */ + char *body = NULL; + int status = 200; + const char *status_msg = "OK"; + + if (strcmp(method, "GET") == 0 && strcmp(path, "/calc") == 0 && query_string) { + body = calc(query_string); + } + + if (!body) { + status = 400; + status_msg = "Bad Request"; + body = strdup("Bad Request"); + if (!body) { perror("strdup"); exit(1); } + } + + /* レスポンス送信 */ + size_t body_len = strlen(body); + char *header = malloc(256); + if (!header) { perror("malloc"); exit(1); } + int hlen = snprintf(header, 256, + "HTTP/1.1 %d %s\r\n" + "Content-Length: %zu\r\n" + "Content-Type: text/plain\r\n" + "Connection: close\r\n" + "\r\n", + status, status_msg, body_len); + + /* ヘッダー */ + ssize_t written = 0; + while (written < hlen) { + ssize_t n = write(fd, header + written, hlen - written); + if (n < 0) { perror("write"); break; } + written += n; + } + /* ボディ */ + written = 0; + while ((size_t)written < body_len) { + ssize_t n = write(fd, body + written, body_len - written); + if (n < 0) { perror("write"); break; } + written += n; + } + + free(header); + header = NULL; + free(body); + body = NULL; +} + +/* ------------------------------------------------------- + * サーバーソケット作成 (IPv4/IPv6 両対応) + * ------------------------------------------------------- */ +static int create_server_socket(void) { + struct addrinfo hints, *res, *p; + memset(&hints, 0, sizeof hints); + hints.ai_family = AF_UNSPEC; /* IPv4 / IPv6 どちらでも */ + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE; /* INADDR_ANY */ + + int rv = getaddrinfo(NULL, PORT, &hints, &res); + if (rv != 0) { + fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv)); + exit(1); + } + + int sfd = -1; + for (p = res; p != NULL; p = p->ai_next) { + sfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol); + if (sfd < 0) { perror("socket"); continue; } + + int yes = 1; + if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes) < 0) { + perror("setsockopt"); close(sfd); continue; + } + + if (bind(sfd, p->ai_addr, p->ai_addrlen) < 0) { + perror("bind"); close(sfd); continue; + } + break; /* 成功 */ + } + freeaddrinfo(res); + + if (sfd < 0) { + fprintf(stderr, "failed to bind\n"); + exit(1); + } + + if (listen(sfd, BACKLOG) < 0) { + perror("listen"); exit(1); + } + return sfd; +} + +/* ------------------------------------------------------- + * main + * ------------------------------------------------------- */ +int main(void) { + int sfd = create_server_socket(); + printf("Listening on port %s ...\n", PORT); + + while (1) { + struct sockaddr_storage client_addr; + socklen_t addrlen = sizeof client_addr; + int cfd = accept(sfd, (struct sockaddr *)&client_addr, &addrlen); + if (cfd < 0) { perror("accept"); continue; } + + /* クライアントの IP アドレスを表示 */ + char ipstr[INET6_ADDRSTRLEN]; + if (client_addr.ss_family == AF_INET) { + struct sockaddr_in *s = (struct sockaddr_in *)&client_addr; + inet_ntop(AF_INET, &s->sin_addr, ipstr, sizeof ipstr); + } else { + struct sockaddr_in6 *s = (struct sockaddr_in6 *)&client_addr; + inet_ntop(AF_INET6, &s->sin6_addr, ipstr, sizeof ipstr); + } + printf("Connection from %s\n", ipstr); + + handle_request(cfd); + close(cfd); + } + + close(sfd); + return 0; +}