Linuxのカーネルの開発に関する世界観(ソケット)です。
ソケットを使うことで、UNIXにおけるデータ通信を行うことができる。
二つのソケットが互いに関連付けられると、片方から書いたデータを、もう片方から読むことができる。
Linuxでは、socket()関数でソケットを作成できる。
送信側が「書き」、受信側が「読む」ことだけでどんなに複雑なプロトコルを用いた通信も可能となる。
ソケットはファイルとして扱われるため、普通のファイルと同じようにファイルディスクリプタを用いて操作する。
後日注記:ソケットAPIはBSDではじめて搭載された。BSDを参照のこと。
自分の書いたブログ「わたしの名はフレイ」2020/06/26より。
Linuxカーネルはネットワークをサポートしており、BSDソケットインターフェースからネットワーク通信ができる。
BSDソケットは、互いに2つのソケットが繋がると有効になり、片方に書き込むと片方から読み込むことができる。
TCPやUDPもサポートされている。
TCPでは、再送制御などの多くの機能がある。
UDPには、そうした機能がない代わり、ビデオや音声を送ったりする時に、誤りを無視して高速に通信することができる。
BSDソケットは多くの場合サーバー・クライアントシステムであり、片方のサーバが待ち受けをし、そのサーバに対してクライアントが接続する。
自分の書いたブログ「神々とともに生きる詩人」2021/01/27より。
TCP/IPの仕組みは、便利だがソフトウェアの開発においては複雑である。
よって、OSがソケットと呼ばれる仕組みで簡素化を行う。
ソケットは、BSD UNIXで最初に採用されたTCP/IPスタックであり、二つのソケットが繋がると有効になり、繋がったソケット同士では、片方からwrite()すればもう片方からread()できる。
BSDではソケットをファイルとして実現し、ファイルをシステムコールでやり取りする際の情報である、ファイルディスクリプタによって操作できるようにしたため、ファイルの読み書きと同様にソケットの読み書きができる。
ソケットを使うことで、カーネルだけがネットワークの詳細と接続情報の管理を行えばよい。
ソケットは通常サーバー・クライアントで接続を行い、ユーザーはソケットをサーバー側で待ち受け、クライアント側で接続を要求するだけでよい。
UNIXでは、これはsocket(), bind(), listen(), accept(), connect()の各APIで行われる。
カーネルの内部では、たとえばLinuxではsk_buff型のポインタを使っており、sk_buff型のポインタを各階層に順々に渡していくことで処理が行われている。
カーネルは、確実にパケットを目的の機器に送らなければならないが、このためにはルータのアドレスさえ知っておけばよい。
LinuxにおいてはBSDソケット層とINETソケット層で、複雑な接続情報の管理を行っている。
ソケットについては以下の書籍・ページが参考になります。
(ふつうのLinuxプログラミング Linuxの仕組みから学べるgccプログラミングの王道を参考に執筆しました。)
socket()は、クライアント側・サーバ側双方で使用する。
socket()関数には、
引数 | 型 | 引数名 |
---|---|---|
第一引数 | int型 | domain |
第二引数 | int型 | type |
第三引数 | int型 | protocol |
を指定する。戻り値の型はint。
socket()では、プロトコルファミリー(domain)、通信方式の型(type)、プロトコル(protocol)を指定し、ソケット(接続の端点)を表すファイルディスクリプタが返される。
ソケットをsocket()で作った段階では、対応するファイルディスクリプタが返されるだけで、まだ接続はされていない。ソケットを接続するためには次に説明するconnect()関数を使う。
クライアント側では、socket()でソケットを作成した後でconnect()でサーバに接続する。
connect()は、クライアント側で使用する。
connect()関数には、
引数 | 型 | 引数名 |
---|---|---|
第一引数 | int型 | sock |
第二引数 | constのsockaddr構造体型のポインタ | addr |
第三引数 | socklen_t型 | addrlen |
を指定する。戻り値の型はint。
connect()では、sockにソケットを指定し、addrにはIPアドレスとポート番号を格納する。ホスト名ではなくIPアドレスを指定するので、あらかじめホスト名をIPアドレスに変換しておく必要がある。addrlenはaddrのサイズ。
connect()を実行した時点で、ソケットははじめてサーバに接続される。
サーバ側、すなわちストリームの接続を待つ側では、やや手順が増えてsocket(), bind(), listen(), accept()の4つのシステムコール呼び出しが必要。
bind()は、サーバ側で使用する。
bind()関数には、
引数 | 型 | 引数名 |
---|---|---|
第一引数 | int型 | sock |
第二引数 | sockaddr構造体型のポインタ | addr |
第三引数 | socklen_t型 | addrlen |
を指定する。戻り値の型はint。
bind()では、サーバ側のアドレスとポート番号(addr)を、ソケット(sock)に関連付ける。
listen()は、サーバ側で使用する。
listen()関数には、
引数 | 型 | 引数名 |
---|---|---|
第一引数 | int型 | sock |
第二引数 | int型 | backlog |
を指定する。戻り値の型はint。
listen()では、カーネルに「ソケットが接続を待機している」ということを伝え、新しい接続がきたら受信するように伝達する。backlogは同時に受け付ける接続の最大数。
accept()は、サーバ側で使用する。
accept()関数には、
引数 | 型 | 引数名 |
---|---|---|
第一引数 | int型 | sock |
第二引数 | sockaddr構造体型のポインタ | addr |
第三引数 | socklen_t型 | addrlen |
を指定する。戻り値の型はint。
accept()では、クライアント側のソケットがサーバ側のソケットに接続した時、その接続を受ける。接続された時点で読み書き可能なストリームのファイルディスクリプタが返る。addrにはクライアント側のアドレスが格納される。
getaddrinfo()、getnameinfo()、freeaddrinfo()、gai_strerror()では、addrinfo構造体を使ってIPアドレスの情報を得る。
getaddrinfo()はホスト名・サービス名からIPアドレス・ポート番号を得るために使う。
これに対して、getnameinfo()はIPアドレス・ポート番号からドメイン名・サービス名を得るために使う。
getaddrinfo()関数には、
引数 | 型 | 引数名 |
---|---|---|
第一引数 | const char型のポインタ | node |
第二引数 | const char型のポインタ | service |
第三引数 | constのaddrinfo構造体型のポインタ | hints |
第四引数 | addrinfo構造体型のポインタのポインタ | res |
を指定する。戻り値の型はint。
getaddrinfo()では、接続するターゲットとなるnodeから、resへと得られたアドレス情報の候補のリストが書き込まれる。また、serviceとhintsで必要な情報を絞り込める。
addrinfo構造体はgetaddrinfo()でresへと書き込まれるリンクリストを表現したデータ構造で、resの中のai_nextがリンクとして次のaddrinfo構造体に続いていく。
addrinfo構造体のメンバには、
型 | メンバ |
---|---|
int型 | ai_flags |
int型 | ai_family |
int型 | ai_socktype |
int型 | ai_protocol |
socklen_t型 | ai_addrlen |
sockaddr構造体型のポインタ | ai_addr |
char型のポインタ | ai_canoname |
addrinfo構造体型のポインタ | ai_next |
が含まれる。
addrinfo構造体はmalloc()で割り当てられるため、freeaddrinfo()で明示的に解放する。
freeaddrinfo()関数には、引数に
型 | 引数名 |
---|---|
addrinfo構造体型のポインタ | res |
を指定する。戻り値の型はvoid。
socketの詳しい情報はmanpageにあるため参照してください。
Linuxカーネル公式のネットワークのドキュメントは以下にあります。(英語)
また、以下のページに参考になる内容があります。日本語版もあります。
Linuxのネットワークについては以下が参考になります。(英語)
(以下は「詳解 Linuxカーネル 第2版」を参考に執筆しました。)
NIC(ネットワークインターフェースカード)は、シンボル名(たとえばeth0)はあるがデバイスファイルの存在しない、特殊なI/Oデバイス。
Linuxでは、BSDソケットをsockfsという特殊ファイルシステムによって、ファイルとして実現している。
Linuxのソケット層は、大きく分けてBSDソケット層とINETソケット層の二つで実現している。BSDソケット層はネットワークにおける共通のインターフェースで、INETソケット層はアーキテクチャ固有のメンバやメソッドを持っている。
カーネルが相手のもとに確実にパケットを届けるためには、自分が属するLANのIPアドレス、すなわちルータのIPアドレスさえ分かっていればいい。
IPアドレスからデバイスの場所を知るためには、LAN上のすべてのホストにARPを使ってブロードキャスト(同じネットワークの全ホストへの通信)でパケットを送信し、どのデバイスが目的のIPアドレスを持っているかを確認する。
Linuxでは、ソケットバッファ構造体(sk_buff型)にデータを保管する。そして、このポインタを各層に渡していくことで、効率的にバッファを各層に受け渡す。
1.トランスポート層で、ユーザから与えられたバッファを元にペイロード(データのこと)を複製し、TCP/UDPのヘッダを付け加える。
2.ネットワーク層でIPヘッダを付け加える。
3.データリンク層でヘッダやトレーラを付け加えた上で、新しく送信キューとして追加し、パケットを送信する。
パケットを受信すると、データがsk_buffに変換され、ヘッダが削除される。
そのほか、カーネル内のネットワークスタックについての詳細は、以下の書籍・ページが参考になります。
Linuxカーネル業界では知らないものはいないという、リーナスの右腕であるアラン・コックス。
アラン・コックスは、初期のLinuxカーネルのネットワーク周辺のコードの多くのバグを修正し、初期のネットワーク・サブシステムを大きく書き直した。
そのように、アラン・コックスは頼りになる男である。とてもいい人間だ。
MBAの取得のためにLinuxの開発からは遠ざかることになったが、彼はLinuxの歴史に名を刻んだ「元祖Linuxハッカー」である。
Linux著名人も参照のこと。
2024.09.19
Linuxネットワーク1(コマンド)やLinuxネットワーク2(設定)を参照のこと。
ソケットはBSD UNIXで最初に採用されました。BSDも参照のこと。
DNS・名前解決についてはDNSサーバも参照のこと。