-
Notifications
You must be signed in to change notification settings - Fork 0
クライアント・サーバーの実装課題 #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
d32a997
8db084b
f554a06
b735988
752536b
ee4e10c
0b9dca6
4423a0b
2cf0ad8
fc9b5be
3dc20fa
6dcf304
213f588
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 |
|---|---|---|
| @@ -1,2 +1,20 @@ | ||
| # socket-programming | ||
| CSZAP2 | ||
| ## 概要 | ||
| echoサーバーを実装しています。 | ||
|
|
||
| ## 要件 | ||
| - C言語で実装 | ||
| - TCPで通信 | ||
| - ちゃんとエラーハンドリングをする | ||
| - 余裕があれば、 | ||
| - スレッドを使って高速化 | ||
| - シグナルでサーバ停止 | ||
|
|
||
| ## 使い方 | ||
| サーバーの起動: | ||
| ```terminal | ||
| ./server <port> | ||
| ``` | ||
| クライアントの起動: | ||
| ```terminal | ||
| ./client <ip> <port> | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| #include <stdio.h> | ||
| #include <sys/socket.h> | ||
| #include <arpa/inet.h> | ||
| #include <stdlib.h> | ||
| #include <string.h> | ||
| #include <unistd.h> | ||
| #include <netdb.h> | ||
|
|
||
| #include "common.h" | ||
|
|
||
| #define BUFFER_SIZE 1024 | ||
|
|
||
| int main(int argc, char *argv[]) { | ||
| int s; | ||
| int status; | ||
| char *node_name; | ||
| char *service_name; | ||
| struct addrinfo hints; | ||
| struct addrinfo *ai0; | ||
| struct addrinfo *ai; | ||
| int receive_message_size; | ||
| char receive_buffer[BUFFER_SIZE]; | ||
| char send_buffer[BUFFER_SIZE]; | ||
|
|
||
| if (argc != 3) { | ||
| fprintf(stderr, "usage: ./client <hostname> <port>\n"); | ||
| exit(EXIT_FAILURE); | ||
| } | ||
| node_name = argv[1]; | ||
| service_name = argv[2]; | ||
|
|
||
| memset(&hints, 0, sizeof(hints)); | ||
| hints.ai_family = PF_UNSPEC; | ||
| hints.ai_socktype = SOCK_STREAM; | ||
|
|
||
| status = getaddrinfo(node_name, service_name, &hints, &ai0); | ||
| if (status) { | ||
| fprintf(stderr, "%s", gai_strerror(status)); | ||
| exit(EXIT_FAILURE); | ||
| } | ||
|
|
||
| for (ai = ai0; ai; ai = ai->ai_next) { | ||
| // socket | ||
| s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); | ||
| if (s < 0) { | ||
| continue; | ||
| } | ||
| // connect | ||
| if (connect(s, ai->ai_addr, ai->ai_addrlen)) { | ||
| close(s); | ||
| s = -1; | ||
| continue; | ||
| } | ||
| break; | ||
| } | ||
| if (s < 0) { | ||
| fprintf(stderr, "cannot connect %s.\n", node_name); | ||
| exit(EXIT_FAILURE); | ||
| } | ||
| printf("connect to %s.\n", node_name); | ||
| freeaddrinfo(ai0); | ||
|
|
||
| while(1) { | ||
| printf("please enter the characters: "); | ||
| if (fgets(send_buffer, BUFFER_SIZE, stdin) == NULL) { | ||
|
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. 入力の終了に対応してください (EOF -- 一般的には、端末で Ctrl-D を押した場合)
Owner
Author
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. 以下で修正しました。 |
||
| if (feof(stdin)) { | ||
| printf("end of input detected (EOF).\n"); | ||
| break; | ||
| } else { | ||
| perror("error reading from stdin"); | ||
| exit(EXIT_FAILURE); | ||
| } | ||
| } | ||
| // send | ||
| send_all(s, send_buffer, strlen(send_buffer)); | ||
| // receive | ||
| receive_message_size = receive_all(s, receive_buffer, BUFFER_SIZE); | ||
| if (receive_message_size == 0) { | ||
| printf("server disconnected.\n"); | ||
| break; | ||
| } | ||
| receive_buffer[receive_message_size] = '\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. 受け取ったデータがバッファ全部に詰まっていた際に、これはバッファの次のメモリ (out of bounds) になってしまう。 |
||
| printf("received from server: %s\n", receive_buffer); | ||
| } | ||
| close(s); | ||
|
|
||
| return EXIT_SUCCESS; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| #include <stdio.h> | ||
| #include <stdlib.h> | ||
| #include <string.h> | ||
| #include <sys/socket.h> | ||
| #include <sys/types.h> | ||
|
|
||
| ssize_t send_all(int s, char *buf, size_t len) { | ||
| int sent_total = 0; | ||
| int n; | ||
|
|
||
| while (sent_total < len) { | ||
| n = send(s, buf + sent_total, len - sent_total, 0); | ||
| if (n == -1) { | ||
| perror("send() failed.\n"); | ||
| exit(EXIT_FAILURE); | ||
|
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. もし失敗時に exit するなら、 send_all_or_die みたいな名前にしたい。 |
||
| } | ||
| if (n == 0) { | ||
| printf("end of input detected (EOF).\n"); | ||
| return sent_total; | ||
|
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. send_all なので、 paritial に終わらせたくない。 |
||
| } | ||
| sent_total += n; | ||
| } | ||
| return sent_total; | ||
| } | ||
|
|
||
| ssize_t receive_all(int s, char *buf, size_t len) { | ||
| int received_total = 0; | ||
| int n; | ||
|
|
||
| while (received_total < len) { | ||
| n = recv(s, buf + received_total, len - received_total, 0); | ||
| if (n == -1) { | ||
| perror("recv() failed.\n"); | ||
| exit(EXIT_FAILURE); | ||
| } | ||
| if (n == 0) { | ||
| printf("end of input detected (EOF).\n"); | ||
| return received_total; | ||
| } | ||
| received_total += n; | ||
| if (strchr(buf, '\n') != NULL) { | ||
|
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. receive_all と書いてあるので、データの内容については触れたくない。 |
||
| break; | ||
| } | ||
| } | ||
| return received_total; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| #pragma once | ||
|
|
||
| #include <sys/types.h> | ||
|
|
||
| ssize_t send_all(int s, char *buf, size_t len); | ||
| ssize_t receive_all(int s, char *buf, size_t len); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,130 @@ | ||
| 調べたことを適宜メモする | ||
|
|
||
| ## 参考資料 | ||
| - https://beej.us/guide/bgnet/html/#sendrecv | ||
|
|
||
| # Tips | ||
| ## atoiとstrtol | ||
| - atoi | ||
| - https://man7.org/linux/man-pages/man3/atol.3.html | ||
| - strtol | ||
| - https://man7.org/linux/man-pages/man3/strtol.3.html | ||
| - 以下のような理由で、strtolを利用した方が良い | ||
| - https://www.gavo.t.u-tokyo.ac.jp/~dsk_saito/lecture/software2/misc/misc02.html | ||
| - 整数以外の文字列が入力された時にエラーを検知できない | ||
| - intの範囲を大きく超える整数の検出などもできない | ||
| - atoiに与える文字列が完全に管理できるなら、strtolより軽量(エラーチェックなし、10進数しか扱えない)なのでメリットもある | ||
|
|
||
| ## 3ハンドシェイクとソケット関数との対応 | ||
| | 3ウェイハンドシェイクのステップ | ソケット関数 | 説明 | | ||
| | ---- | ---- | ---- | | ||
| | 1. SYN(接続要求) | connect() | サーバーにSYNを送り、3ウェイハンドシェイクを開始 | | ||
| | 2. SYN-ACK(応答) | accept() | サーバーが接続要求に応答(SYN-ACK)し、接続を確立 | | ||
| | 3. ACK(確認応答、接続完了) | accept() | クライアントからACKを受信し、通信準備が整う | | ||
|
|
||
| ## 主要なシステムコール | ||
| ### getaddrinfo | ||
| ```c | ||
| #include <sys/types.h> | ||
| #include <sys/socket.h> | ||
| #include <netdb.h> | ||
|
|
||
| int getaddrinfo(const char *node, // e.g. "www.example.com" or IP | ||
| const char *service, // e.g. "http" or port number | ||
| const struct addrinfo *hints, | ||
| struct addrinfo **res); | ||
| ``` | ||
| - 関連情報を記載したaddrinfo構造体(hints)へのポインタを渡すことで、それを補完した完全な状態のaddrinfoのリンクドリストを作成してくれる | ||
| - エラーは```gai_strerror()```で拾う | ||
| - 使い終わったリンクドリストは```freeaddrinfo()```で解放する | ||
| - ```AI_PASSIVE```は、ローカルホストのアドレスを割り当てる設定 | ||
|
|
||
| ### socket | ||
| ```c | ||
| #include <sys/types.h> | ||
| #include <sys/socket.h> | ||
|
|
||
| int socket(int domain, int type, int protocol); | ||
| ``` | ||
| - ソケットディスクリプタを返す | ||
| - domain = PF_INET / PF_INET6 | ||
| - type = SOCK_STREAM / SOCK_DGRAM | ||
| - 以下のように```getaddrinfo```で取得した情報を使って呼び出す | ||
| ```c | ||
| getaddrinfo("www.example.com", "http", &hints, &res); | ||
| s = socket(res->ai_family, res->ai_socktype, res->ai_protocol); | ||
| ``` | ||
|
|
||
| ### bind | ||
| ```c | ||
| #include <sys/types.h> | ||
| #include <sys/socket.h> | ||
|
|
||
| int bind(int sockfd, struct sockaddr *my_addr, int addrlen); | ||
| ``` | ||
| - ソケットをポートに関連付けする | ||
| - 1024以下のポートは予約されているので使わないこと | ||
| - ポートの再利用を許可するよう、以下のように設定できる | ||
| ```c | ||
| int yes=1; | ||
| //char yes='1'; // Solaris people use this | ||
|
|
||
| // lose the pesky "Address already in use" error message | ||
| if (setsockopt(listener,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof yes) == -1) { | ||
| perror("setsockopt"); | ||
| exit(1); | ||
| } | ||
| ``` | ||
|
|
||
| ### connect | ||
| ```c | ||
| #include <sys/types.h> | ||
| #include <sys/socket.h> | ||
|
|
||
| int connect(int sockfd, struct sockaddr *serv_addr, int addrlen); | ||
| ``` | ||
| - 特定のIP、ポートに接続する | ||
| - ローカルのポートは気にしないので```bind```は呼ばない(カーネルが設定してくれる) | ||
|
|
||
| ### listen | ||
| ```c | ||
| int listen(int sockfd, int backlog); | ||
| ``` | ||
| - 接続を待つ | ||
| - backlogは、着信キューで許可されるコネクション数 | ||
| - 着信してきたコネクションは、```accept()```するまでこのキューで待機することになる | ||
|
|
||
| ### accept | ||
| ```c | ||
| #include <sys/types.h> | ||
| #include <sys/socket.h> | ||
|
|
||
| int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); | ||
| ``` | ||
| - 接続を許可する | ||
| - 新しいソケットファイルディスクリプタが返される(こちらで```send/recv```する) | ||
|
|
||
| ### send | ||
| ```c | ||
| int send(int sockfd, const void *msg, int len, int flags); | ||
| ``` | ||
| - 送信したいデータへのポインタとデータのバイト長を渡すことで、データを送信できる | ||
| - 実際に送信されたバイト長を返す | ||
| - ```len```と一致しない場合は一部しか送信できていないことを意味する | ||
|
|
||
| ### recv | ||
| ```c | ||
| int recv(int sockfd, void *buf, int len, int flags); | ||
| ``` | ||
| - ```buf```に送られてきたデータを読み込む | ||
| - 0が返ってきた場合は、リモート側が接続を閉じたことを意味する | ||
|
|
||
| ### close | ||
| ```c | ||
| close(sockfd); | ||
| ``` | ||
| - ソケットディスクリプタの接続を閉じる | ||
| - ```shutdown```を使うとより細かい制御(受信、送信、送受信の拒否)ができる | ||
| - ```close```と異なり、ソケットディスクリプタを閉じるわけではないことに注意 | ||
|
|
||
| # 用語集 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,97 @@ | ||
| #include <stdio.h> | ||
| #include <sys/socket.h> | ||
| #include <arpa/inet.h> | ||
| #include <stdlib.h> | ||
| #include <string.h> | ||
| #include <unistd.h> | ||
| #include <netdb.h> | ||
|
|
||
| #include "common.h" | ||
|
|
||
| #define QUEUE_LIMIT 5 | ||
| #define BUFFER_SIZE 1024 | ||
|
|
||
| int main(int argc, char *argv[]) { | ||
| int client_sd; | ||
| int server_sd; | ||
| int status; | ||
| char *service_name; | ||
| struct addrinfo hints; | ||
| struct addrinfo *ai0; | ||
| struct addrinfo *ai; | ||
| struct sockaddr_storage ss; | ||
| unsigned int ss_len; | ||
| int send_message_size; | ||
| int receive_message_size; | ||
| char receive_buffer[BUFFER_SIZE]; | ||
|
|
||
| if (argc != 2) { | ||
| fprintf(stderr, "usage: ./server <port>\n"); | ||
| exit(EXIT_FAILURE); | ||
| } | ||
| service_name = argv[1]; | ||
|
|
||
| memset(&hints, 0, sizeof(hints)); | ||
| hints.ai_family = PF_UNSPEC; | ||
| hints.ai_socktype = SOCK_STREAM; | ||
| hints.ai_flags = AI_PASSIVE; | ||
|
|
||
| status = getaddrinfo(NULL, service_name, &hints, &ai0); | ||
| if (status) { | ||
| fprintf(stderr, "%s", gai_strerror(status)); | ||
| exit(EXIT_FAILURE); | ||
| } | ||
|
|
||
| for (ai = ai0; ai; ai = ai->ai_next) { | ||
| // socket | ||
| server_sd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); | ||
| if (server_sd < 0) { | ||
| continue; | ||
| } | ||
| // bind | ||
| if (bind(server_sd, ai->ai_addr, ai->ai_addrlen) < 0) { | ||
| perror("bind() failed.\n"); | ||
| close(server_sd); | ||
| continue; | ||
| } | ||
| //listen | ||
| if (listen(server_sd, QUEUE_LIMIT) < 0) { | ||
| perror("listen() failed.\n"); | ||
| close(server_sd); | ||
| continue; | ||
| } | ||
| break; | ||
| } | ||
| if (server_sd < 0) { | ||
| fprintf(stderr, "cannot create server socket.\n"); | ||
| exit(EXIT_FAILURE); | ||
| } | ||
| freeaddrinfo(ai0); | ||
|
|
||
| while(1) { | ||
| // accept | ||
| if ((client_sd = accept(server_sd, (struct sockaddr*) &ss, &ss_len)) < 0) { | ||
| perror("accept() failed.\n"); | ||
| exit(EXIT_FAILURE); | ||
| } | ||
|
|
||
| while(1) { | ||
| //recv | ||
| receive_message_size = receive_all(client_sd, receive_buffer, BUFFER_SIZE); | ||
|
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. receive_all はエラー時に exit するけれど、 |
||
| if (receive_message_size == 0) { | ||
| printf("connection has already closed.\n"); | ||
| break; | ||
| } | ||
| //send | ||
| send_message_size = send_all(client_sd, receive_buffer, receive_message_size); | ||
| if(send_message_size == 0) { | ||
| printf("connection has already closed.\n"); | ||
| break; | ||
| } | ||
| } | ||
| close(client_sd); | ||
| } | ||
| close(server_sd); | ||
|
|
||
| return EXIT_SUCCESS; | ||
| } | ||
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.
#include の順番に何か規則を持たせたい。 (<> -> "" の順番で、アルファベット順など)