この記事の概要
サーバーサイドを勉強していると、socket()
やらbind()
というような難しそうな用語がよく登場します。今まではこれらの概念から目を背けてきましたが、一人前のエンジニアになるためには理解しておく必要がありそうです。
しかし、フロントエンドと違い、バックエンド関係の記事はとにかく小難しいです。古い記事が多いですし、難しい単語を羅列して攻撃してきます。
なので、落ちている記事を自分なりに噛み砕いて、ここに置いておこうと思います。
ここからは、コネクション型通信:サーバプログラムの作成という分かりやすい記事を主軸として、細かい部分をLinuxのマニュアルページで補完しながら書いていきます。
プログラムの流れ
サーバのプログラムは、6つのフェーズから構成されます。
はじめ | ||
---|---|---|
↓ | ||
socket() | (1) ソケット生成 | |
↓ | ||
bind() | (2) ソケット登録 | |
↓ | ||
listen() | (3) ソケット接続準備 | |
↓ | ||
accept() | (4) ソケット接続待機 | ← 接続要求 |
↓ | ||
recv() / send() | (5) 受信/送信 | ← データ → クライアント |
↓ | ||
close() | (6) ソケット切断 | |
↓ | ||
終わり |
以下、
- socket()
- bind()
- listen()
- accept()
- read() / write()
- close()
の6つのフェーズについて、それぞれ説明していきます。
注意
TCPとUDPでは、同じソケットの概念でも、手順等が大きく異なります。
このページに分かりやすく比較した表があったので、引用させていただきます。
コメントで教えていただきました。ありがとうございます。
(1) Socket()
概要
ソケットを生成する。引数にはTCP通信に利用するプロトコルの種類を与える。プロトコルの種類とは、インターネット用であることや、TCPとUDPの種別も含まれる。
書式
int socket(int domain, int type, int protocol);
参考 : Socket
詳細
引数
-
domain(第1引数)
どのプロトコルファミリーを通信に使うかを指定する。
例)- Unix
- IP
- IPv6
-
type(第2引数)
通信方式を指定する。
例)- 順序性と信頼性があり、双方向に接続する。
- 信頼性はあるが、順序は保証しない。
-
protocol (第3引数)
ソケットが使用するプロトコルを指定する。通常、プロトコルファミリーの種類ごとに一つのプロトコルのみをサポートする。その場合は0を指定する。
返り値
- 各ソケットの識別子となるファイルディスクリプターを返す。
(2) Bind()
概要
socket()
を実行してもソケットが作られただけであり、ポート番号などは未確定。なので、bind()
を用いてポート番号などをソケットに割り当てる。
書式
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参考 : Bind
詳細
引数
返り値
成功した場合はゼロを返す。エラー時には-1を返す。
(3) Listen()
概要
ソケットを接続待ちの状態にする。
書式
int listen(int sockfd, int backlog);
参考 : Listen
詳細
引数
-
sockfd(第1引数)
任意のソケットを示すファイルディスクリプター。 -
backlog(第2引数)
保留中のキューの最大長。キューがいっぱいの状態で接続要求が到着すると、クライアントはエラーを受け取る。
返り値
成功した場合はゼロを返す。エラー時には-1を返す。
(4) Accept()
概要
accept()
を実行すると、クライアントから通信接続要求が来るまでプログラムを停止し、接続後にプログラムを再開する。
書式
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参考 : Accept
詳細
引数
-
sockfd(第1引数)
任意のソケットを示すファイルディスクリプター。 -
addr(第2引数)
接続した相手のIPアドレスやポート番号などの情報を含む。 -
addrlen(第3引数)
addrの指す構造体のサイズ。
返り値
成功した場合は、受け付けたソケットのファイルディスクリプターを返す。
エラー時には-1を返す。
(5) Read() / Write()
ここでは
read()
とwrite()
としていますが、一般にはrecv()
とsend()
が使われるそうです。また、
listen()
するためのsocket()
と、データ読み書き用のsocket()
は別です。データ読み書き用のsocket()
はaccept
できた分だけ作成されるので、マルチプロセス/スレッドな構成にすることで、独立したsocket()
で多重通信ができるそうです。コメントで教えていただきました! ありがとうございます。
概要
クライアントから送られてきたデータの受信read()
と、送信write()
を行う。
書式
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
詳細
引数
-
fd(第1引数)
読み込み/書き込み先を示すファイルディスクリプター -
buf(第2引数)
読み込み/書き込み先を示すバッファ -
count(第3引数)
読み込み/書き込みの最大バイト数
返り値
読み込まれた/書き込まれたバイト数を返す。
エラー時には-1を返す。
(6) Close()
TCPの場合、
close()
の代わりに**shutdown()
**も使えるそうです。コメントより教えていただきました。ありがとうございます。
概要
ソケットを閉じて、通信接続を終了する。クローズされたファイルティスクリプターはどのファイルからも参照されていない状態になり、再利用できるようになる。
書式
int close(int fd);
参考 : Close
詳細
引数
-
fd(第1引数)
任意のソケットを示すファイルディスクリプター。
返り値
成功した場合は0を返す。エラー時には-1を返す。
その他・用語の解説
引数と返り値
プログラムの関数には引数を入れると返り値が帰ってきます。LinuxのシステムはC言語でできているので、この記事で紹介している書式も全てC言語です。
ソケット
システム間の通信を可能にするもの。
TCP
標準的に利用されている通信方法です。信頼性が高い通信をすることができます。
UDP
TCPほど信頼性は高くありませんが、高速です。リアルタイム性を求められる通信に利用されます。
プロトコルファミリー
通信プロトコル(方法)をまとめたもの。
ファイルディスクリプター
ファイルなどを識別するためのもの。よくFDと略される。
ポート
TCP/UDPの概念です。よく「サーバーに空いている穴」と表現されます。ポートごとに担当するアプリケーションが違います。
アドレス
システム上での特定の場所を示す番地のことです。
構造体
いろんなデータを格納できる便利な箱です。
まとめ
socket()
とかbind()
という用語は難しそうですが、サーバーのシステムの流れの一部として捉えると、概念自体はそこまで難しくないのではないかと思います。
おかしなところや誤字脱字にお気付きの方がおられましたら、お気軽に編集リクエスト / コメントしてください。