関数の仕様調べ - ryhara/Webserv GitHub Wiki
ネットワークの通信に使用するソケットを作成し、ファイルディスクリプタを返す
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
-
ai_family AF_INET IPv4
専用のインターネットファミリ -
ai_socktype SOCK_STREAM
ストリーム型ソケット -
ai_protocol AI_PASSIVE
AI_PASSIVE ビットが hints 構造体の ai_flags メンバーの中に設定される場合、呼び出し元は、戻されたソケット・アドレス構造体を bind() の呼び出しに使用することを計画します。
使用例
_server_fd = socket(_res->ai_family, _res->ai_socktype, _res->ai_protocol);
ネットワークのアドレスとサービスを変換する
_hints は要求を指定するために使い、_res はその要求に基づいて得られた結果を受け取るために使います。
NULL
: ローカルホストのアドレスを指定
SEVEER_PORT_STR
: ポート番号の文字列を指定
_hints
: アドレス情報の要求
_res
: アドレス情報の格納先
使用例
ft_memset(&_hints, 0, sizeof(_hints));
_hints.ai_family = AF_INET;
_hints.ai_socktype = SOCK_STREAM;
_hints.ai_flags = AI_PASSIVE;
if (getaddrinfo(NULL, SERVER_PORT_STR, &_hints, &_res) != 0)
log_exit("getaddrinfo", __LINE__, __FILE__, errno);
setsockopt()は、ソケットに対応するオプションを操作します。
ほとんどのソケットレベルのオプションは optval 用に int 引数を使用します。 setsockopt() の場合、ブール演算を有効にするためには引数は 0 でない必要があり、 オプションを無効にする場合は 0 である必要があります。
ソケット・レベルでオプションを操作するためには、SOCKET.H
内で定義されているSOL_SOCKET
をlevelパラメーターに設定しなければなりません。
int setsockopt(int s, int level, int optname, const void *optval, socklen_t optlen);
SO_REUSEADDR | ローカルアドレスの再使用を有効にします
使用例
これを指定しないと、確かローカルアドレスの再使用に失敗してbindでエラーがでていた。
int option_value =1;
setsockopt(_server_fd, SOL_SOCKET, SO_REUSEADDR, (const char *)&option_value, sizeof(option_value))
ソケットに名前をつけるような操作である。
bind() システムコールは、ローカルプロトコルアドレスをソケットに割り当てます。 ソケットは、socket()で作成される時にアドレスファミリ空間に存在しますが、 プロトコルアドレスは割り当てられていません。 bind() システムコールはソケットに addr を割り当てることを要求します。
ai_addr
戻されたソケット・アドレス構造体のアドレス。 このフィールドに戻される値は、AI_PASSIVE フラグに従って、 このソケット・タイプとともに connect() コールまたは bind() コールの引数として使用できます。
使用例
bind(_server_fd, _res->ai_addr, _res->ai_addrlen);
↓
freeaddrinfo(_res);
freeaddrinfo() 関数は、 リンクリスト res に対して動的に割り当てられたメモリを解放する。
使用例
freeaddrinfo(_res);
ソケット上の接続をlistenする
socket で最初にソケットが作成され、 着信接続を受け入れる意思および着信接続用の待ち行列限界が listen() で指定された後、接続が acceptで受け入れられます。 listen() システムコールは、タイプが SOCK_STREAM または SOCK_SEQPACKET のソケットにだけ適用されます。
引数は、延期中の接続の待ち行列を伸ばす際の最大長を定義します。 待ち行列が満杯のときに接続要求が到着すると、クライアントは ECONNREFUSED を示すエラーを受信する可能性があります。 TCP の場合は、接続は黙って落とされます。
使用例
listen(_server_fd, QUEUE_LENGTH)
ソケット上の接続を受け入れる。 引数 s は socketで作成され、 bindでアドレスにバインドされ、 listenも済ませて接続を待ち受けているソケットです。 accept()は、待ち行列上の最初の接続要求を取り出し、 新しいソケットを作成し、オリジナルのソケット s から O_NONBLOCK プロパティの状態を継承した そのソケットへ新しいファイル記述子を割り当てます。
引数 addr は結果の引数で、 通信レイヤに既知の接続エンティティのアドレスで埋められます。 addr 引数の正確な形式は通信が行われるドメインで決まります。
使用例
client_fd = accept(_server_fd, (struct sockaddr *)&client_addr, &client_addr_len);
EWOULDBLOCK
ソケットが非ブロッキングとマークされ、さらに受け付けるべき接続要求が存在しません。
EAGAIN
とdefineは同じ
errnoがこれの場合はエラーではない
ソケットからメッセージを受け取るのに使用される。 最後の引数にflagを設定できる。
ソケットに受け取るメッセージが存在しなかった場合、 受信用のコールはメッセージが到着するまで待つ。 ただし、ソケットが非停止 (nonblocking) に設定されていた場合(fcntl) は -1 を返し、外部変数 errno に EAGAIN か EWOULDBLOCK を設定する。 これらの受信用のコールは、受信したデータのサイズが要求したサイズに 達するまで待つのではなく、何らかのデータを受信すると復帰する (受信されるデータの最大サイズは要求したサイズである)。
使用例
ssize_t n = recv(client_fd, _buffer, sizeof(_buffer) - 1, 0);
別のソケットにメッセージを送信する。最後の引数にフラグを設定できる。 非停止モードの場合にはエラー EAGAIN か EWOULDBLOCK で失敗する。 いつデータをさらに送信できるようになるかを知るために、 selectを使用することができる。
使用例
ssize_t send_n = send(client_fd, responseMessage.c_str(), responseMessage.size(), 0);
ファイルディスクリプタの設定を行う
サブジェクト通りの使い方だと設定できないらしい pedagoに投げられている質問を例に正しいと思われる実装をした。
-
F_GETFL
: 記述子の状態フラグを取得します -
F_SETFL
: fd に結び付けられた close-on-exec フラグを arg に設定します。 arg は 0 または前述の FD_CLOEXEC です。 -
O_NON_LOCK
: readで読み取るべきデータがない場合、またはwrite操作がブロックするであろう場合に、読み取りまたは書き込み呼び出しはEGAINで-1を返す。 ブロックをせずにすぐにreturnをするノンブロッキング -
FD_CLOEXEC
: このフラグが 1 であるときに、プロセスが exec 関数呼び出し の 1 つを実行する場合、ファイル記述子はクローズされる。フラグが 0 の場合は、ファイルはオープンしたままである。
使用例
int flag = fcntl(fd, F_GETFL);
if (flag < 0) {
log_exit("fcntl", __LINE__, __FILE__, errno);
} else {
if (fcntl(fd, F_SETFL, flag | O_NONBLOCK) < 0) {
log_exit("fcntl/F_SETFL", __LINE__, __FILE__, errno);
}
if (fcntl(fd, F_SETFD, FD_CLOEXEC) < 0) {
log_exit("fcntl/F_SETFD", __LINE__, __FILE__, errno);
}
}
入出力の多重化
poll() システムコールはファイル記述子の集合を調査して、 それらのいずれかで入出力の準備ができているか否かを調べます。 fds 引数は <poll.h> で定義された pollfd 配列を指すポインタ (後述) です。 nfds 引数は fds 配列のサイズを決定します。
#include <poll.h>
int poll(struct pollfd fds[], nfds_t nfds, int timeout);
struct pollfd {
int fd; /* 調べるファイルディスクリプタ fdが-1ならばreventsはクリア(0に設定)されpollfdはちぇっくされない*/
short events; /* 検索するイベント */
short revents; /* 返された、発生したイベント */
};
-
POLLIN
: 高優先データ以外のデータはブロックせずに読取りできます。 -
POLLERR
: デバイスまたはソケット上に例外状態が起きました。 このフラグは、 events ビットマスクに存在していなくても必ずチェックされます。
timeout が 0 でも INFTIM (-1) でもない場合、この値はいずれかのファイル記述子が 準備完了になるのを待機する最大間隔 (ミリ秒単位) です。 timeout が INFTIM (-1) の場合、 poll() は無期限にブロックします。 timeout が 0 の場合 poll() はブロックせずに戻ります。
- select関数を用いた標準入力の監視【Linux / C言語】
- Ubuntu Manpage: select, pselect, FD_CLR, FD_ISSET, FD_SET, FD_ZERO - 同期 I/O の多重化
- 例: 非ブロッキング入出力および select() - IBM Documentation
selectはアドレスで渡されたfd集合を調べ、それらのfdのいくつが準備完了であるか調べる。
最初のnfds個のfdが各集合でチェックされる。戻り時に記述し集合を、要求された操作の準備ができているfdで構成される集合に置き換える。返り値は準備出来たfdの総数を返す
#include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
これらのマクロは fdの値が0未満、またはFD_SETSIZE = 1024
以上である場合は未定義
-
FD_ZERO(&fdset);
fd集合を空集合で初期化 -
FD_SET(fd, &fdset);
fdをfdsetに加える -
FD_CLR(fd, &fdset);
fdをfdsetから削除 -
FD_ISSET(fd, &fdset);
fdがfdsetのメンバである場合は非ゼロ、それ以外は0を返す。
timoutがNULL出ない場合、selectの完了を待つ最大間隔を指定。NULLの場合selectは無期限にブロック Nob-blockingファイルディスクリプタの場合、selectは直ちに戻る
使用例
ノンブロッキングI/Oはfdの準備が完了していない場合即座にエラーを返す。errno=EAGAIN
ノンブロッキングI/Oは即座にエラーを返し、ブロック状態にさせない。非同期I/Oは処理が終了するまで待機して、終了したら通知を返す。