Conversation
| while ((received_bytes = recv(conn_sock, buf, sizeof(buf) - 1, 0)) > 0) | ||
| { | ||
| printf("received_bytes: %ld\n", received_bytes); | ||
| buf[received_bytes] = '\0'; // Null-terminate the string |
There was a problem hiding this comment.
received buffer が文字列である (バイナリで \0 を含んでいない) ことが前提になっている?
| while (1) | ||
| { | ||
| char buf[BUFFER_SIZE]; | ||
| fgets(buf, sizeof(buf), stdin); // Null-terminate the string |
…nected_socket() function
…en_sockets() function
…o_epoll() function
…N or EPOLLOUT event
| run-client:build-client | ||
| ./client | ||
| server: server.o utils.o | ||
| $(CC) $(CFLAGS) -o server server.o utils.o |
| { | ||
| perror("client: connect()"); | ||
| close(sock); | ||
| puts("Failed to create a connected socket\n"); |
There was a problem hiding this comment.
perror は stderr に表示されるので、合わせてこれも stderr に出力した方が良いと思います。
| struct sockaddr_in6 server_addr6; | ||
| memset(&server_addr4, 0, sizeof(server_addr4)); | ||
| memset(&server_addr6, 0, sizeof(server_addr6)); | ||
| int is_ipv4 = inet_pton(PF_INET, ip, &server_addr4.sin_addr); |
There was a problem hiding this comment.
is_xxxxx という名前は true or false を連想させるので違う名前にしたい。 (そのままif 文で評価して -1 を true 扱いしてしまう事故が発生しそう)
| int socket_fd; | ||
| if (is_ipv4 == 1) | ||
| { | ||
| if ((socket_fd = socket(PF_INET, SOCK_STREAM, 0)) == -1) |
|
|
||
| #include "utils.h" | ||
|
|
||
| void init_socket_manager(SocketManager *manager, int max_size) |
There was a problem hiding this comment.
メモリ確保が init_socket_manager で
解放が close_all_sockets で対で呼ぶ必要があるのは、名前からだと読み解くのが難しいです。
SocketManager *new_socket_manager(int max_size)
void free_socket_manager(SocketManager *)
などとするのはいかがでしょうか?
| ssize_t sent_bytes; | ||
| if ((sent_bytes = send(client_socket_data->socket_fd, client_socket_data->buffer, strlen(client_socket_data->buffer), 0)) >= 0) | ||
| { | ||
| memmove(client_socket_data->buffer, client_socket_data->buffer + sent_bytes, strlen(client_socket_data->buffer) - sent_bytes); |
There was a problem hiding this comment.
データのコピーにデータ量に時間がかかるためmemmove は避けたい
(send に バッファの途中のポインタを与えたい)
| } | ||
|
|
||
| // Check if the client socket is ready to write | ||
| if (event.events & EPOLLOUT) |
There was a problem hiding this comment.
送るデータが無かったら? (バッファが0の時に EPOLLOUT を受信しない)
| if ((received_bytes = recv(client_socket_data->socket_fd, client_socket_data->buffer, BUFFER_SIZE - 1, 0)) > 0) | ||
| { | ||
| printf("received_bytes: %ld (fd: %d)\n", received_bytes, client_socket_data->socket_fd); | ||
| client_socket_data->buffer[received_bytes] = '\0'; // Null-terminate the string |
There was a problem hiding this comment.
外からやってくるデータがバイナリデータ (\0を含む)かもしれないので、データがどこまで存在しているかを \0 に依存したくない。
(strlen は O(N) 時間がかかるのもあります)
client_socket_data にバッファに何文字入っているかを保存させては?
| } | ||
|
|
||
| struct epoll_event event; | ||
| event.events = EPOLLIN | EPOLLOUT; |
There was a problem hiding this comment.
送り返すデータが存在しない場合に EPOLLOUT を受け取ってもやることがない (= 無限に空パケットを送ったり CPU を無駄にするループになる可能性)
There was a problem hiding this comment.
ここでおっしゃってるのは、そもそも EPOLLOUT を受け取らないように修正する、ということですかね?(#3 では送り返すデータが存在しなければ処理を終えるように修正しました)
もし上記の意図だとすると、バッファの状況を見ながら epoll_event の監視をやめたり始めたりとかそんな感じでしょうか?
| } | ||
| } | ||
| // Check if the event is for a client socket | ||
| else if ((socket_data = find_socket(&client_socket_manager, events[i].data.fd)) != NULL) |
There was a problem hiding this comment.
find_socket は O(N) かかり、 epoll 自体がソケットの数に比例する時間を必要としないように作られている利点を消してしまっています。
epoll_event->data を epollの設定時にセットすることで、イベントの受信時に受け取ることができるので、ここに(data->ptrなどに) SocketData を保持するのはいかがでしょうか?
(close時も O(1)にすることを考えるとSocketData を少し拡張する必要があるかもしれません)
handle_new_connection の分岐も同様
There was a problem hiding this comment.
提案ありがとうございます。epoll_event->data->ptr の存在に気づいていなかったですが、たしかにここを活用できますね。SocketData のポインタをもたせることで socket_manager を線形探索せずに SocketData を取得できるよう #3 で修正してみました。イメージ合っていそうでしょうか?
ただ削除時の実装まではできていないです。(断念しました)
実装するとすると、SocketManager では現状配列で SocketData を管理していますが、ここをハッシュテーブルにして、キーを fd に、バリューに SocketData のポインタを持たせるとかですかね?
|
@inazz |
| } | ||
| } | ||
| // Check if the event is for a client socket | ||
| else if ((socket_data = find_socket(&client_socket_manager, events[i].data.fd)) != NULL) |
There was a problem hiding this comment.
提案ありがとうございます。epoll_event->data->ptr の存在に気づいていなかったですが、たしかにここを活用できますね。SocketData のポインタをもたせることで socket_manager を線形探索せずに SocketData を取得できるよう #3 で修正してみました。イメージ合っていそうでしょうか?
ただ削除時の実装まではできていないです。(断念しました)
実装するとすると、SocketManager では現状配列で SocketData を管理していますが、ここをハッシュテーブルにして、キーを fd に、バリューに SocketData のポインタを持たせるとかですかね?
| } | ||
|
|
||
| struct epoll_event event; | ||
| event.events = EPOLLIN | EPOLLOUT; |
There was a problem hiding this comment.
ここでおっしゃってるのは、そもそも EPOLLOUT を受け取らないように修正する、ということですかね?(#3 では送り返すデータが存在しなければ処理を終えるように修正しました)
もし上記の意図だとすると、バッファの状況を見ながら epoll_event の監視をやめたり始めたりとかそんな感じでしょうか?
| { | ||
| if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) | ||
| { | ||
| continue; |
There was a problem hiding this comment.
ここちょっとわからなかったです。
EWOULDBLOCK の場合にも書き込み自体は発生していて、パイプのバッファサイズ上限に達するまで書き込み続けてしまう、みたいな感じでしょうか。EWOULDBLOCK が起きるケースのイメージがあまりついておらず、どんな動きになるかがわかっていないです。
Description
#1 の続き
この PR では以下3つの機能追加を行う
epollを使って I/O 多重化listen_socketsおよびclient_socketsはSocketManagerという構造体を定義して管理getaddrinfo()で取得した情報を使って socket を作成127.0.0.1なら IPv4、::1なら IPv6)pipeをepollに登録して、SIGINT がきたら Cleanup 処理に移行Note
対応できてないものは以下(他にも対応できていないものはありそうだが、ひとまず自分で認識しているもの)
fread()で入力できそうだったが、Interactive に入力することはできない?struct SockDataで管理している buffer のサイズが固定