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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
bin/*
build/*
47 changes: 47 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
CC := gcc
CFLAGS := -std=c11 -Wall -Wextra -O2 -g -I src -MMD -MP
LDFLAGS :=
LDLIBS :=

SRCDIR := src
OBJDIR := build
BINDIR := bin

SRCS_SERVER := $(SRCDIR)/http_server.c $(SRCDIR)/http_request.c
SRCS_CLIENT := $(SRCDIR)/http_client.c

OBJS_SERVER := $(patsubst $(SRCDIR)/%.c,$(OBJDIR)/%.o,$(SRCS_SERVER))
OBJS_CLIENT := $(patsubst $(SRCDIR)/%.c,$(OBJDIR)/%.o,$(SRCS_CLIENT))

TARGET_SERVER := $(BINDIR)/http_server
TARGET_CLIENT := $(BINDIR)/http_client

DEPS := $(OBJS_SERVER:.o=.d) $(OBJS_CLIENT:.o=.d)

.PHONY: all clean

all: $(TARGET_SERVER) $(TARGET_CLIENT)

# create bin and build dirs as order-only prerequisites
$(BINDIR):
mkdir -p $(BINDIR)

$(OBJDIR):
mkdir -p $(OBJDIR)

# pattern rule to compile .c -> build/%.o
$(OBJDIR)/%.o: $(SRCDIR)/%.c | $(OBJDIR)
$(CC) $(CFLAGS) -c $< -o $@

# link targets
$(TARGET_SERVER): $(OBJS_SERVER) | $(BINDIR)
$(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@

$(TARGET_CLIENT): $(OBJS_CLIENT) | $(BINDIR)
$(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@

# include dependency files if present
-include $(DEPS)

clean:
rm -rf $(BINDIR) $(OBJDIR)
103 changes: 103 additions & 0 deletions memo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# 📝課題: C言語 + system callでHTTP Serverを作ってみよう
```
GET /calc?query=2+10 HTTP/1.1
```
に対して
```
HTTP/1.1 200 OK
Content-Length: 2

12
```

を返すようなもの
## ヒント
- manを使う
- system callのエラーは必ず対処する
- メモリは動的に確保しよう
- クライアントとサーバーの処理が別で必要

## 余力があれば挑戦するとよいこと
- IPv4+v6両対応
- CPU性能を最大限活用できるようにする
- non-blocking化
- マルチスレッド化
- 通信タイムアウトの設定
- SSL対応
- signalを受け取ったら全コネクションが正常終了するようにする



### 自分の実装計画

まず1プロセスで単純なHTTPサーバーを作る。
- socket, bind, listen, acceptで通信確立
- request lineの解析
- headerの解析
- bobyから計算

時間があったら...
CI/CDをちゃんと設定する
→次にワーカープロセスを使った実装
→次にマルチスレッド
→次にepollによる実装

## 調べたもの

### bind(2)
addrをsocketのfdに結び付ける
0: success
-1: error

sockaddr_in構造体=ポート番号

### HTTP リクエスト
HTTPリクエストは、以下の3つの要素で構成される。

- リクエスト行
- ヘッダーフィールド
- ボディ
ヘッダーフィールド、ボディは省略可能。
ヘッダーフィールドとボディの間は、空行を1行挟む。

https://qiita.com/gunso/items/94c1ce1e53c282bc8d2f#2http%E3%83%AA%E3%82%AF%E3%82%A8%E3%82%B9%E3%83%88%E3%81%AE%E6%A7%8B%E6%88%90
https://www.rfc-editor.org/rfc/rfc9112.html


### HTTP レスポンス
- HTTPリクエストと同様に、
```
POST /index.html HTTP/1.0
```
みたいにスペースで区切られる

- headerフィールド
フィールド名:valueの形式

- HTTPレスポンスは、以下の3つの要素で構成される。
- ステータス行
- ヘッダーフィールド
- ボディ

### C10k問題
プロセス作れる数の限界UNIX系ではプロセス数が~32767, 16bit=2^16

### bindとTIME_WAITについて
TCPにおいて、切断後、一定時間はソケット状態を維持するため、アプリを再起動したときにすぐに同じポートを再利用できない。`SO_REUSEADDR`を設定するとうまくいく
```
// bindが解放されなくなる対策
const int one = 1;
// 第2引数で設定したいレイヤー、第3引数でそのレイヤーで設定したいオプション、第4引数でそのオプションの値を入れる。
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(int));
```
https://qiita.com/bamchoh/items/1dd44ba1fbef43b5284b
https://bombrary.github.io/blog/posts/socket01-file-tcp/

## 頂いたコメント
- clientからhttp_requestを送るときにconnectまで成功して、いざsend使用する直前に切断されてしまった場合、そのままsendするとSIGPIPEシグナルが飛んできてプロセスが終了してしまう。
>サーバー側のコネクションがcloseしているとここでプロセスが落ちるのでMSG_NOSIGNALをつけてベットエラーハンドリングをした方が良いかもしれないです。

>MSG_NOSIGNAL (Linux 2.2 以降)
ストリーム指向のソケットで相手側が接続を切断した時に、エラーとして SIGPIPE を送信しないように要求する。
https://ja.manpages.org/send/2

82 changes: 82 additions & 0 deletions src/http_client.c
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

サーバー側の名前がhttp_server.cなのでhttp_client.cだとわかりやすいと思いました!

Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#define _GNU_SOURCE
#include <errno.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>

#define SERVER_ADDR "127.0.0.1"
#define SERVER_PORT "8090"

int main() {
struct addrinfo hints, *result, *res_p;
int err, client_fd = -1;

memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_INET; // IPv4
hints.ai_socktype = SOCK_STREAM; // TCP

err = getaddrinfo(SERVER_ADDR, SERVER_PORT, &hints, &result);
if (err != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(err));
exit(EXIT_FAILURE);
}

for (res_p = result; res_p; res_p = res_p->ai_next) {
client_fd = socket(res_p->ai_family, res_p->ai_socktype, res_p->ai_protocol);
if (client_fd < 0) {
fprintf(stderr, "create socket failed: %s (errno=%d)\n", strerror(errno), errno);
continue;
}

if (connect(client_fd, res_p->ai_addr, res_p->ai_addrlen) < 0) {
fprintf(stderr, "create connection failed: %s (errno=%d)\n", strerror(errno), errno);
continue;
}
// connect成功
break;
}
// TODO: ここいらない?
if (client_fd < 0) {
printf("create connection error");
exit(EXIT_FAILURE);
}
freeaddrinfo(result);

// http requestの書き込み
char request[512];
int request_len = snprintf(request, sizeof(request),
"GET /calc?query=12+24 HTTP/1.1\r\n"
"Host: %s:%s\r\n"
"User-Agent: simple-c-http-client/1.0\r\n"
"Connection: close\r\n"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

KeepAlive ではないのでConnection: closeをつけているのが丁寧で良いと思いました!

"\r\n",
SERVER_ADDR, SERVER_PORT);
// send: 送り切るまで
int sent = 0;
while (sent < request_len) {
int n = send(client_fd, request + sent, request_len - sent, MSG_NOSIGNAL);
if (n < 0) {
if (errno == EINTR) continue; // interrupted system call
perror("send");
close(client_fd);
exit(1);
}
sent += n;
}

// http responseの読み取り
// TODO: Status line, header, bodyをちゃんと読み取るようにする
// TODO: 動的にメモリ確保をする
int BUF_SIZE = 500;
char buf[BUF_SIZE];
// TODO: ここもBUFF_SIZE受け取れているとは限らない...
int response_len = recv(client_fd, buf, BUF_SIZE, 0); // BUF_SIZEより大きいと動かない
Comment on lines +75 to +77
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

mallocで動的にメモリを確保してレスポンスを受け取る書き方も試しても良いかもしれません。

buf[response_len] = '\0';

close(client_fd);
printf("%s", buf);
}
Loading