Skip to content
Open
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
Binary file added .DS_Store
Binary file not shown.
Binary file added socket_programming/.DS_Store
Binary file not shown.
53 changes: 53 additions & 0 deletions socket_programming/README.md
Original file line number Diff line number Diff line change
@@ -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 に書き直す必要あり)
Binary file added socket_programming/client
Binary file not shown.
49 changes: 49 additions & 0 deletions socket_programming/client.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>

#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, *res;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;

int rv = getaddrinfo(HOST, PORT, &hints, &res);
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);
if (sfd < 0) { perror("socket"); return 1; }
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

失敗した場合に res を freeaddrinfo() していない点が気になりました。関数の末尾に、リソースが確保されている場合は解放するコードを書き、途中で失敗した場合は、そこまで goto で飛ばす、という書き方を時々見かけます。賛否はあるものの、参考まで。


if (connect(sfd, res->ai_addr, res->ai_addrlen) < 0) { perror("connect"); return 1; }
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

失敗した場合に sfd を close していない点が気になりました。

freeaddrinfo(res);

/* リクエスト送信 */
char req[512];
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];
ssize_t n;
while ((n = read(sfd, buf, sizeof buf - 1)) > 0) {
buf[n] = '\0';
printf("%s", buf);
}
printf("\n");
close(sfd);
return 0;
}
Binary file added socket_programming/server
Binary file not shown.
214 changes: 214 additions & 0 deletions socket_programming/server.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>

#define PORT "8080"
#define BACKLOG 10
#define BUF_SIZE 4096

/* -------------------------------------------------------
* /calc?query=<expression> を評価する
* 対応演算子: + - * /
* 返り値: 動的確保した結果文字列(呼び出し元が 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); return; }
Copy link
Copy Markdown

@nodchip nodchip May 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

個人的には、生でメモリを確保し、解放する際は、解放したあとにポインターに NULL を代入します。理由は、悪意ある者が、メモリのダンプを覗いたとき、ポインターの値から、解放済みのメモリ領域を覗き、価値のあるデータを盗むのを防ぎたいためです。趣味の範囲かもしれません。

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);
return;
}
free(req);

/* パスとクエリ文字列に分割 */
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);
free(body);
}

/* -------------------------------------------------------
* サーバーソケット作成 (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;
}