はじめに
RailsやNext.jsなどのアプリケーションフレームワークを使えば、簡単にネットワークを介したアプリケーションを作成することができますが、その背後にはOSが提供する基本的なネットワーク操作が存在しています。
アプリケーションはユーザランドに属しているため、カーネルランドに属しているTCP/UDP等のトランスポート層の機能を使うためにはシステムコールを呼び出す必要があります。
今回はTCP
でネットワークプログラミングをする際に必要なシステムコールについて調べていきます。
socket()
まずはsocket()
を呼び出すことによって、アプリケーション層とトランスポート層の間に口を作ります。
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
呼び出しの際の引数はそれぞれ以下を意味します。
- domain
- どのプロトコルファミリーで通信するかを指定します
- ex
-
AF_INET
: IPv4 Internet protocols -
AF_INET6
: IPv6 Internet protocols -
AF_BLUETOOTH
: Bluetooth low-level socket protocol
-
- type
- ソケットのタイプを指定します
- ex
-
SOCK_STREAM
: TCPコネクションで使われるsocketタイプ -
SOCK_DGRAM
: UDP通信で使われるsocketタイプ
-
- protocol
- ソケットが使用するプロトコルを指定します
- ここで指定できるプロトコルは
/etc/protocols
で管理されています
戻り値は、この呼び出しによって作成されたsocketを参照するfile descriptorです。
bind()
socket()
で作成された、socketに対してアドレスを割り当てます。
ここで言うアドレスとはIPアドレスとport番号のことです。
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
- sockfd
- bindするsocketのfile descirptorを指定します
-
socket()
の戻り値がここに入ります
- addr
- 割り当てるアドレスを指定します
- addrlen
- 第2引数の
const struct sockaddr *addr
で表される構造体のサイズを指定します
- 第2引数の
Serverの場合は特定のportに対してbindすることがほとんどですが、Clientからリクエストを送る際にもこのbind()
は呼び出されています。
Clientからリクエストを送る際にはbind先を指定しないことがほとんどなので、kernelが暗黙に指定していますが、これらのportのことをephemeral portと呼びます。
ephemeral portとして使うことができる範囲は決められていて、/proc/sys/net/ipv4/ip_local_port_range
で確認することができます。
listen()
listen()
によって作成されたsocketがClient用ではなくてServer用であることを明示します。
この時点でServerにおけるClientからの接続要求の準備が整います。
#include <sys/socket.h>
int listen(int sockfd, int backlog);
- sockfd
- 対象のsocketのfile descriptorを指定します
- backlog
- queueに入れられる処理待ちのconnectionの最大サイズを指定します
- このサイズに到達した場合、Clientはエラーを受け取ります
accept(), accept4()
accept()
で、接続待ちコネクションが入っているqueueからコネクションを取り出して、そのコネクションに対応するためのread/write用のsocketを作成します。
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *_Nullable restrict addr,
socklen_t *_Nullable restrict addrlen);
- sockfd
- listening用のsocketのファイルディスクリプタを指定します
- socket()の戻り値に等しいです
- addr
- 自socketのアドレスではなく、接続要求をしてきたClientのアドレスを指定します
- addrlen
- 第2引数の構造体のサイズを指定します
戻り値は、コネクションに対応するために作成したread/write用のsocketのfile descriptorです。
recv()
recv()
で、対象のsocketからmessageを受け取ります。
#include <sys/socket.h>
ssize_t recv(int sockfd, void buf[.len], size_t len, int flags);
- sockfd
- Server:
accpet()
で作成したsocketのfile descriptを指定します - Client:
socket()
で作成したsocketのfile descriptを指定します
- Server:
- buf
- messageを受け取るためのbufferのポインタを指定します
- len
- 第2引数のbufferの最大サイズを指定します
- flags
- 受信時の動作を指定するオプションフラグです
戻り値は受信したmessageのサイズになります。
send()
send()
によって、接続しているsocketに対してmessageを送信します。
#include <sys/socket.h>
ssize_t send(int sockfd, const void buf[.len], size_t len, int flags);
- sockfd
- Server:
accpet()
で作成したsocketのfile descriptを指定します - Client:
socket()
で作成したsocketのfile descriptを指定します
- Server:
- buf
- 送信したいデータが乗っているbufferのポインタを指定します
- len
- bufferのサイズを指定します
- flags
- 送信時の動作を指定するオプションフラグです
参考