Skip to content

エコーサーバー・クライアントの実装(最小限)#1

Merged
thonda28 merged 11 commits intomainfrom
feature/minimal-implementation
Jan 6, 2025
Merged

エコーサーバー・クライアントの実装(最小限)#1
thonda28 merged 11 commits intomainfrom
feature/minimal-implementation

Conversation

@thonda28
Copy link
Copy Markdown
Owner

@thonda28 thonda28 commented Jan 3, 2025

Description

クライアント側で標準入力に入力した文字列を送信し、サーバー側で受け取った文字列をそのままクライアント側に送り返し、クライアント側で表示するものを実装
クライアント側で標準入力に入力した文字列を送信し、サーバー側でそのまま表示するものを実装

  • サーバー側は停止するまで無限ループでエコーする
  • クライアント側は一度送信すると終了

エラーハンドリングとしては、エラー発生時にすでに作成済みの socket は close してから exit するようにした

  • 冗長な感じがあるので別途リファクタリングはしたい(goto 文を使って cleanup 処理を書くとシンプルにできそう、goto 文は一般に非推奨ではあるがこのケースでは可読性が上がる利点が大きそう)

Usage

以下コマンドでサーバー、クライアントをそれぞれ実行可能

  • カレントディレクトリの確認
$ pwd
/path/to/c-echo
  • サーバー起動
$ make run-server
  • クライアント起動(サーバーとは別ターミナル)
$ make run-client

Note

ひとまず最小限の実装までなので、大まかな方向性が正しそうであればこの PR をマージして以下の対応に取り組む予定

  • 大量のリクエスト処理
  • 適切な終了
  • IPv6 対応

@shining-ai
Copy link
Copy Markdown

クライアント側で標準入力に入力した文字列を送信し、サーバー側でそのまま表示するものを実装
サーバ側から、入力した文字列をそのまま返送するものを私は想定していました。

Comment thread server.c Outdated
exit(1);
}

for (;;)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

無限ループはwhileのほうが個人的には自然だと思いました。
好みかもしれません。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

たしかにそうですね、直近 Golang に触れていたこともあって引っ張られてました。while にしようと思います。

Comment thread server.c Outdated
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(8080);
result = bind(listen_sock, (struct sockaddr *)&server_addr, sizeof(server_addr));
if (result == -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.

resultの使いまわしだと、なんのチェックか上の行を見直す手間が少しあると思いました。
変数名をそれぞれ変えるか、
if文の中にbind(listen_sock, (struct sockaddr *)&server_addr, sizeof(server_addr))を直接入れてもいいのかなと思いました。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

ここはちょっと個人的にも迷ったところでした。以下3パターンで迷って、他の2つが冗長だったりコードを読みにくいかなと思って、result を繰り返し使うのを採用しました。(直前の行で代入されるので許容範囲かなと)

  • 各 System call の結果を格納する result を繰り返し使用
  • エラーチェックにのみ使用する bind_result のような変数を定義
  • 変数名に入れず、そのまま評価

ですが、ここでコメントもらってわかりにくいのかもと思ったので、修正を検討しようと思います。(エラーハンドリングまわり全般をリファクタリングしたいと思っているので、現状はこのままでリファクタリング時にまとめて修正する想定です)

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

if文の中にbind(listen_sock, (struct sockaddr *)&server_addr, sizeof(server_addr))を直接入れてもいいのかなと思いました。

再度検討して、エラーハンドリング全般を上記対応のように修正することにしました。コメントありがとうございました!(514a8de

Copy link
Copy Markdown

@hayashi-ay hayashi-ay left a comment

Choose a reason for hiding this comment

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

コメントしました。次のスコープで実装予定のものとかぶっているかもしれないですが 

Comment thread server.c
if (result == -1)
{
perror("server: bind()");
close(listen_sock);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

あっても良いですが、プロセス終了時にOS側で解放してくれると思うので、なくても良いですね

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

たしかになくても解放されそうですが、個人的にリソース管理は明示的にすることの利点が大きいと考えているので、このままにしておこうと思います。

Comment thread server.c Outdated
{
unsigned int client_len;
struct sockaddr_in client_addr;
int conn_sock = accept(listen_sock, (struct sockaddr *)&client_addr, &client_len);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

次のスコープかもしれないですが、selectなどで多重化して複数リクエスト受け取れるようにしたいですね。

Comment thread server.c Outdated
}

char buf[256];
result = recv(conn_sock, buf, sizeof(buf) - 1, 0);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

255文字以上受け取れるようにしたいですね。
あとmanも目を通しておくと良いと思います。
https://man7.org/linux/man-pages/man2/recv.2.html

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

255文字以上受け取れるようにしたいですね。

イーサネット上の TCP では1460バイトが最大セグメントサイズなので、それは超えない範囲で1024バイトくらいがよいですかね?(なんとなく 2 のべき乗で設定しておくようなイメージがあったので)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

コメントが良くなかったかもしれないです。バッファサイズ以上の長さの文字列を受け取れるようにしたいです。

あとMSSはTCPレイヤーの話でアプリケーション側からは関係ないと思います。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

バッファサイズ以上の長さの文字列を受け取れるようにしたいです。

あ、なるほどです。f934b8c で修正してみました。recv() が 0 を返すまで buffer の範囲で繰り返しデータを受け取って送り返すループを追加したんですが、イメージ合っていますか?(server 側の buffer を小さくして動作確認してみた感じだと意図通り動いてそうではありました)

あとMSSはTCPレイヤーの話でアプリケーション側からは関係ないと思います。

たしかにそうですね、訂正ありがとうございます!

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

一旦いいんじゃないでしょうか

Comment thread server.c
exit(1);
}

char buf[256];
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

たぶんL52でsizeof(buf) - 1まで読み取るようにしていて、NULL終端を担保しているつもりだと思うんですけど、たぶん0で値が初期化されている保証はなくて、環境によってはゴミ値が入っている可能性があると思います。(参考文献などなくてすいませんが)

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

なるほどです、たしかに担保できていなさそうです。明示的に buf を初期化することで NULL 終端をしておこうと思います。

@thonda28
Copy link
Copy Markdown
Owner Author

thonda28 commented Jan 4, 2025

サーバ側から、入力した文字列をそのまま返送するものを私は想定していました。

エコーサーバーというと、一般にこっちを指すみたいですね。勘違いしてたので修正します、コメントありがとうございました!

@thonda28
Copy link
Copy Markdown
Owner Author

thonda28 commented Jan 6, 2025

レビューありがとうございました!一旦マージして追加実装をしようと思いますが、もし他にもコメントあればマージ後でもぜひお願いします!

@thonda28 thonda28 merged commit c5e0e7b into main Jan 6, 2025
@thonda28 thonda28 deleted the feature/minimal-implementation branch January 6, 2025 13:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants