ソケット通信
ソケットとは?
コンピュータネットワーク上で、ノードがデータを送受信するための内部的なエンドポイントのこと。具体的には、ネットワークソフトにおけるエンドポイントの表現のことを指す。
よく、ソケットがメスコネクタ、チャンネルを介したノード間の通信が2つのオスコネクタをもったケーブルで表現されることがある。
利用方法
プロセスはソケットを参照するのにソケットディスクリプタを利用できる。プロセスは、まずはじめにプロトコルスタックにソケットの作成をリクエストし、スタックはディスクリプタを返す。ディスクリプタによりソケットを一意に識別できる。
ポートとは異なり、ソケットは特定の1つのノードとのみ紐づく。ソケットはあくまでローカルのリソースであり、他のノードから直接は参照されない。ポートは外部エンドポイント、ソケットは内部エンドポイントと表現できる。
ソケットアドレス
実際には、ソケット は TCP 通信のための IP ネットワーク内のソケット (インターネットソケット と呼ばれる) を参照する。この文脈では、ソケットは特定の ソケットアドレス に紐づけられると想定される。ソケットアドレスは、ローカルノードのポート番号および IP アドレスにちなんで定められる。そして、通信先の外部ノードにも同様にそちらのプロセスで利用する関連したソケットが存在する。 ローカルプロセスが外部プロセスと通信するときは、外部の ソケットアドレス とデータを送受信するのであり、外部の ソケット や ソケットディスクリプタ そのものと通信するのではない。後者のいずれも、外部プロセスのローカルに存在する。
ローカルIP:ローカルポートの 10.20.30.40:4444 とグローバルIP:グローバルポートの 50.60.70.80:8888 間のソケット通信を考える。各々が関連するソケットをローカルに持ち、採番されたソケットディスクリプタによって参照される。前者のを 317、後者のを 922 とする。
まず、10.20.30.40 上のプロセスは、50.60.70.80 ノードのポート 8888 に対して通信のリクエストを送信できる。具体的には、プロトコルスタックに 50.60.70.80:8888 を目的地としたソケットを作成させる。そして、ソケットを作成しソケットディスクリプタ (317) を受け取ったら、そのソケットディスクリプタにを使用することでソケットを介して通信が行える。通信時、プロトコルスタックは 50.60.70.80 のポート 8888 から/へ データを伝送する。しかし、10.20.30.40 上のプロセスは、「ソケット 922 との通信」や「50.60.70.80 上のソケット922との通信」といったような要求は行えない。922 という数字は 10.20.30.40 では意味をなさない。
実装
プロトコルスタック は今日日 OS によって提供される。これはスタックを実装したプロトコルであり、ネットワークを介した通信を処理可能なプログラムの集合である。プロトコルスタックとプログラムがやりとりしネットワークソケットを利用するための API は socket API と呼ばれる。これを利用したアプリケーションプログラミングはソケットプログラミング、もしくはネットワークプログラミングと呼ばれる。
インターネットソケットAPIは大抵 バークレーソケット をベースとしている。これは UNIX 哲学に則り、ソケットはファイルディスクリプタの形式をとっている。 TCP/UDP の標準では、ソケットアドレスは IP アドレスとポート番号の組み合わせと定義されている。 ユニックスドメイン
とは
インターネットドメインにおける、ネットワークを介したマシン同士の通信ではなく、カーネル上のローカルプロセス間の通信を扱う
具体的には、プログラム/プロセス間で通信が行えるようになる
socket API をそのまま利用できる
使い方
ユニックスドメインを利用する
AF_LOCAL, AF_UNIX
構造体 sockaddr_un を利用する
歴史
ソケット という単語自体は、1971 年に RFC 147 で言及されている。この仕様発表の経緯には ARPA network が関連している。この RFC のタイトルは The Definition of a Socket という。 A socket is defined to be the unique identification to or from which information is transmitted in the network. The socket is specified as a 32 bit number with even sockets identifying receiving sockets and odd sockets identifying sending sockets. A socket is also identified by the host in which the sending or receiving processer is located.
これによると、ソケットとは、
ネットワーク上で伝送される情報の伝送元/先の一意な識別子
32bit の数値で定義され、偶数番号は受信、奇数番後は送信を表す
である。ただ、現在のソケットは双方向で通信を行うため、後者は現状に即してない。この仕様内では、ソケットは ARPA network 上のマシン間で通信するためのポートの識別子を定義する ものと説明されている (ここでいうポートが今でいう一般的なポートと合致しているのかはわからない)。
今日日最も利用されているソケットの実装は Berkeley socket API をベースにしている。これは 1983 年に 4.2BSD で実装&公開されている。ただし、AT&T による Unix の権利主張によって、その一年しかフリーで公開はできなかった。 その後、1987 年に AT&T よりの TLI (Transport Layer Interface) が SVR3 に実装&公開された。これは SVR4 まで続いた。
この時点では socket API の明確な仕様は存在せず、上記のように実装も複数存在していた。最終的には、POSIX socket API としてまとめられた。
モダンな OS であれば、大抵 Berkeley をベースとした socket API もしくは POSIX socket API を実装しており、主にはインターネット通信のための標準的なインタフェースとして利用されている。
man から読み取れるソケットのコンセプト
socket の man の DESCRIPTION に色々と書かれている。Ubuntu 16.04.5 LTS で man を読んでみた (POSIX.1-2001, POSIX.1-2008, 4.4BSD.)。
socket 関数は、通信のためのエンドポイントを作成し、ディスクリプタを返す と説明されている。その概要は以下。
code:c
int socket(int domain, int type, int protocol);
ドメイン
domain には通信のドメインが入る。通信におけるドメインとは、利用するプロトコルファミリーを指すようだ。これは sys/socket.h に定義されている。
table:通信ドメイン
名前 目的 man
AF_UNIX, AF_LOCAL Local communication unix(7)
AF_INET IPv4 Internet protocols ip(7)
AF_INET6 IPv6 Internet protocols ipv6(7)
AF_IPX IPX - Novell protocols
AF_NETLINK Kernel user interface device netlink(7)
AF_X25 ITU-T X.25 / ISO-8208 protocol x25(7)
AF_AX25 Amateur radio AX.25 protocol
AF_ATMPVC Access to raw ATM PVCs
AF_APPLETALK AppleTalk ddp(7)
AF_PACKET Low level packet interface packet(7)
AF_ALG Interface to kernel crypto API
タイプ
type には通信のセマンティクスが入る。通信のスタイルとも言える。
table:通信セマンティクス
名前 概要
SOCK_STREAM Provides sequenced, reliable, two-way, connection-based byte streams. An out-of-band data transmission mechanism may be supported.
SOCK_DGRAM Supports datagrams (connectionless, unreliable messages of a fixed maximum length).
SOCK_SEQPACKET Provides a sequenced, reliable, two-way connection-based data transmission path for datagrams of fixed maximum length; a consumer is required to read an entire packet with each input system call.
SOCK_RAW Provides raw network protocol access.
SOCK_RDM Provides a reliable datagram layer that does not guarantee ordering.
SOCK_PACKET Obsolete and should not be used in new programs; see packet(7).
ソケット通信のコンセプトとして、データは パケット として送信されるが、通信スタイルは、パケットに対する処理方法/伝送方法の違いで定義される。主なものをいくつか抜き出して説明する。
Stream
コネクション指向ソケットとして知られ、TCP, SCTP, DCCP で利用される 信頼性があり、伝送順が保証され、パケットが喪失/再要求された場合自動で再送される
Datagram
コネクションレスソケットとして知られ、UDP で利用される
固定最大長をもったメッセージで、信頼性がなく、伝送順も保証されない
Raw
ルーター等のネットワーク機器で利用される
ネットワークプロトコルやインタフェースへの低レベルアクセスが可能
Reliably Delivered Messages
伝送順は保証されないが、パケットが喪失した場合自動で再送される
プロトコル
protocol には通信プロトコルが入る。通常、domain で指定したプロトコルファミリー内において、あるソケットタイプをサポートするプロトコルは唯 1 つ存在する。ただし、複数存在することも可能。プロトコルに対応する番号は、通信が実行される 通信ドメイン 内で定義される。詳しくは protocols(5) を参照とのことで、みてみると、/etc/protocols に定義されているとのこと。一部を抜き出すと以下のような感じ。
code:shell
ubuntu:~$ cat /etc/protocols
# Internet (IP) protocols
#
# sources.
# New protocols will be added on request if they have been officially
# assigned by IANA and are not historical.
# If you need a huge list of used numbers please install the nmap package.
ip 0 IP # internet protocol, pseudo protocol number
hopopt 0 HOPOPT # IPv6 Hop-by-Hop Option RFC1883 icmp 1 ICMP # internet control message protocol
igmp 2 IGMP # Internet Group Management
ggp 3 GGP # gateway-gateway protocol
ipencap 4 IP-ENCAP # IP encapsulated in IP (officially IP'')
st 5 ST # ST datagram mode
tcp 6 TCP # transmission control protocol
組み合わせについて
一部の通信可能な組み合わせ。◯は特別な名前は付いていないが、通信が可能なもの。
table:通信スタイルとプロトコルファミリー
SOCK_STREAM SOCK_DGRAM SOCK_RAW
AF_LOCAL ◯ ◯
AF_INET TCP UDP IPv4
AF_INET6 TCP UDP IPv6
利用するプロトコルファミリー、その中で利用するプロトコル、そしてソケット通信のタイプを指定し、後は API に乗っ取ってソケット通信を開始すれば良いようだ。
ソケットのシーケンス
table:システムコール
システムコール 概要
socket 通信のためのエンドポイントとして、ソケットディスクリプタを作成する
bind 外部との通信のためにソケットアドレスをソケトディスクリプタに紐づける。 ソケットに名前をラベルするとも言われる
listen ソケットを通信受け入れ可能な状態に変更する。また、許容接続数 = listen queue の数も設定する
accept キューに pending されたコネクションの先頭を抜き出し、同様のソケットタイプ及びアドレスファミリーで新しくソケットを作成 & 新しいファイルディスクリプタを割り当てる。socket の O_NONBLOCK が設定されていなければ、listen キューにコネクションが push されるまでブロックする
connect コネクションモードのソケットで接続を確立、あるいはコネクションレスモードのソケットで peer アドレスのセット/リセットを行う。指定されたソケットアドレスに接続を確立しにいく
send データを送信する。デフォルトだとデータを送信し終えるまでブロッキング
recv データを受信する。デフォルトだとデータを受信し終えるまでブロッキング
close ソケットを削除する。ソケットディスクリプタをファイルディスクリプタと同様に close する
https://www.ibm.com/support/knowledgecenter/ssw_ibm_i_71/rzab6/rxab6500.gif
ソケット API を利用すると、ネットワーク層やトランスポート層を介して通信が行える。
https://www.ibm.com/support/knowledgecenter/ssw_ibm_i_71/rzab6/rxab6501.gif
実装
サーバ
code:c
int main(void) {
int serv_sock_dipt;
int clit_sock_dipt;
struct sockaddr_in serv_sock_addr;
struct sockaddr_in clit_sock_addr;
unsigned int clit_sock_addr_len;
int recv_result;
serv_sock_dipt = socket(AF_INET, SOCK_STREAM, 6);
if (serv_sock_dipt == -1) {
fprintf(stderr, "Failed to create socket dicriptor");
exit(EXIT_FAILURE);
}
memset(&serv_sock_addr, 0, sizeof(struct sockaddr_in));
serv_sock_addr.sin_family = AF_INET;
inet_aton("127.0.0.1", &serv_sock_addr.sin_addr);
// ポート番号を network byte order に変換する
serv_sock_addr.sin_port = htons(8888);
if (bind(serv_sock_dipt,
(struct sockaddr *) &serv_sock_addr,
sizeof(struct sockaddr_in)) == -1) {
fprintf(stderr, "Failed to bind");
exit(EXIT_FAILURE);
}
if (listen(serv_sock_dipt, 1) < 0) {
fprintf(stderr, "Failed to listen");
exit(EXIT_FAILURE);
}
clit_sock_addr_len = sizeof(struct sockaddr_in);
clit_sock_dipt = accept(serv_sock_dipt,
(struct sockaddr *) &clit_sock_addr,
&clit_sock_addr_len);
if (clit_sock_dipt == -1) {
fprintf(stderr, "Failed to create new socket discriptor for client");
exit(EXIT_FAILURE);
}
printf("connected from %s.\n", inet_ntoa(clit_sock_addr.sin_addr));
recv_result = recv(clit_sock_dipt, buf, BUFFER_SIZE, 0);
if (recv_result == -1) {
fprintf(stderr, "Failed to receive message");
exit(EXIT_FAILURE);
} else if (recv_result == 0) {
fprintf(stderr, "No data received");
return EXIT_SUCCESS;
}
printf("received: %s\n", buf);
close(clit_sock_dipt);
close(serv_sock_dipt);
return EXIT_SUCCESS;
}
クライアント
code:c
int main(void) {
struct sockaddr_in sock_addr;
int sock_dipt;
char *msg = "Hello";
sock_dipt = socket(AF_INET, SOCK_STREAM, 6);
if (sock_dipt == -1) {
printf("Failed to create socket discriptor");
exit(EXIT_FAILURE);
}
memset(&sock_addr, 0, sizeof(struct sockaddr_in));
sock_addr.sin_family = AF_INET;
sock_addr.sin_port = htons(8888);
inet_aton("127.0.0.1", &sock_addr.sin_addr);
if (connect(sock_dipt,
(struct sockaddr *) &sock_addr,
sizeof(struct sockaddr_in)) == -1) {
fprintf(stderr, "Failed to connect server");
exit(EXIT_FAILURE);
}
if (send(sock_dipt, msg, strlen(msg), 0) == -1) {
fprintf(stderr, "Failed to connect server");
exit(EXIT_FAILURE);
}
// 即座に終了してしまうとサーバ側がメッセージを取得する前にコネクションが切られソケットが閉じられてしまう
sleep(1);
close(sock_dipt);
return EXIT_SUCCESS;
}
実行結果
code:shell
$ gcc server.c -o server.out
$ gcc client.c -o client.out
# サーバ側
$ ./server.out
connected from 127.0.0.1.
received: Hello
# クライアント側: 1秒待って終了する
$ ./client.out