TCPとUDPでのデータ送信の違い(UDPの場合)

TCPとUDPとではアプリケーションデータを相手に届ける際の仕組みが異なります。今回はUDPの場合です。CでもJavaでも言語は何でも良いのですが、ここではC言語で通信を行うプログラムを作成したとします。C言語の場合、相手にデータを送る関数はsendto()を使用し、送られてきたデータを取り出す関数にはrecvfrom()を使用します。

送信側:sendto() ※データを送る。
 ↓
 IPやUDPの機能でデータが送られる。
 ↓
受信側:recvfrom() ※データを取り出す。
 
UDPがIPやイーサネットとどのように連携するのかも考えてみたいため、sendtoの送信バッファを数メガ単位で確保し、いっきにデータ送信しようとした場合で考えます。

注意:
実際はこのようなよろしくないプログラムは作らないと思いますが、そういう想定の話ということで。またソケットオプションのSO_SNDBUF、SO_RCVBUFもサイズ拡張しているという想定で。SO_SNDBUF、SO_RCVBUFはアプリケーションからOSに制御が渡ったときのOS側の送信/受信用のデータ格納領域です。

で、実際にそのような特大データをsendtoしてみると、エラーになります。UDPでは送信できるデータサイズに制限があるからです。送信できるデータサイズがいくつまでかというと65507バイトまでです。この数値がどこからきているのかということですが、以下にIPとUDPのヘッダフォーマットの図を示すのでまずは見てください。

IPヘッダにある「全パケット長」はIPパケットのサイズを表すのですが、全パケット長は16ビットであるため最大で2の16乗(つまり、65535バイト)までを格納します。このサイズはIPパケットのヘッダ長(20バイト)も含みますのでIPパケットのデータ部は65535バイトから20バイトを引いた65515バイトまでとなります。ここにUDPのデータグラムが格納されるわけですが、UDPにもヘッダがありUDPの実際のデータはもっと小さくなります。つまり、65515バイトからUDPヘッダ長(8バイト)を引いた65507バイトがUDPが一度に送信できるデータサイズとなります。なお、UDPヘッダの「長さ」のフィールドはUDPヘッダとデータ部をあわせた値が格納されます。

送信データのサイズを小さくして65507バイトのデータを送信したとしましょう。UDPはTCPと違い再送制御などの仕組みはないためアプリケーションデータはUDPからIPに引き渡されます。IPはイーサネットにデータを渡そうとするのですが、その前にMTUのチェックを行います。MTU(Maximum Trunsmission Unit)とはデータリンク層で送信できる最大データサイズのことです。イーサネットではMTUが1500バイト、光ファイバ(FDDI)は4352バイトと媒体により異なっています。アプリケーションデータがMTUのサイズを超えている場合、IPはパケットを分割してMTUのサイズ以下になるようにします。IPフラグメンテーションと言われている仕組みです。

なおフラグメントされたパケットのUDPヘッダですが、先頭のパケットのみにUDPヘッダが付与されます。残りのパケット(2個目以降のパケット)にはUDPヘッダは付与されません。経路途中にあるロードバランサがUDPヘッダの情報を見て処理をするようなケースでは、フラグメントされたパケットの処理がうまくできないので注意が必要です。

IPパケットの分割と組み立てはIPで行いますが、その際にIPヘッダにある「識別子」、「フラグ」、「フラグメントオフセット」のフィールドを利用します。IPはパケットを分割する際、フラグメンテーションしたパケットの識別子に同じID番号を割り当てます。そしてフラグを使い(厳密にはフラグの3ビット目を使い)フラグメントの途中のパケットか最後のパケットかを示します。フラグメントオフセットには元のデータのどの位置だったかを示す位置情報を格納するため、受信側でパケットを組み立てる際に利用します。

MTU以下に抑えられたパケットはイーサネットにわたり物理層のケーブルを通してネットワークに送られます。受信側にはパケットは分割されて届きますがIPがパケットの組み立てを行いUDPに引き渡します。受信側のアプリケーションプログラムは1回recvfromをすることで送られたデータを取り出すことができます。UDPでは1回の送信を1回の受信で行うことができます(パケットは分割されて到達しますが受信側で何度もrecvfromを行う必要はない、1回のrecvfromで受信できることが保証されている)。当たり前のことを書いているようですが、TCPにおいてはそうでもないのです。これについては次回書いてみようと思います。

CentOS6とCentOS7でのプロセス管理コマンドの比較

CentOS6で使えたプロセス管理の「service」コマンドがCentOS7では使えなくなり、代わりに「systemctl」コマンドになりました。プロセスの起動、停止、再起動などCentOS6と7とでプロセス管理コマンドの比較をしてみました。なお、CetOS7ではsystemdデーモンが各プロセスを管理する際にユニット、ターゲットという括りで管理していて、httpdやsshdなどのプロセスはserviceユニットとして管理されています。言い回しも「プロセス管理」ではなく「サービス制御」と言われているようですので、それにあわせて記載します。以下のプロセス名とサービス名は言い方は違いますが同じ意味と捉えてください。

httpdやsshdのことを、
CentOS6の場合はプロセスと、ここでは呼ぶ。
CentOS7の場合はサービスと、ここでは呼ぶ。

起動、停止、再起動など

■CentOS6の場合
# service プロセス名 start(起動)
# service プロセス名 stop(停止)
# service プロセス名 restart(再起動)
# service プロセス名 reload(リロード)
# service プロセス名 status(状態の確認)

■CentOS7の場合
# systemctl start サービス名(起動)
# systemctl stop サービス名(停止)
# systemctl restart サービス名(再起動)
# systemctl reload サービス名(リロード)
# systemctl is-active サービス名(状態の確認)
# systemctl status サービス名(状態の確認)

※CentOS7の場合、状態の確認にis-activeとstatusの2種類があるがstatusのほうがより詳細な情報が表示される。

自動起動の確認、設定、解除

■CentOS6の場合
# chkconfig --list(自動起動の確認)
# chkconfig プロセス名 on(自動起動の設定)
# chkconfig プロセス名 off(自動起動の解除)

■CentOS7の場合
# systemctl list-unit-files(自動起動の確認)
# systemctl enable サービス名(自動起動の設定)
# systemctl disable サービス名(自動起動の解除)

ランレベル確認、変更

■CentOS6の場合
ランレベルの確認は「/etc/inittab」ファイルを開いて「id:○:initdefault:」を見て確認します。○のところにランレベルが記載されています。「id:3:initdefault:」となっていればランレベルは「3」となります。
ランレベルの変更は「/etc/inittab」ファイルの○の部分を書き換えてください。

■CentOS7の場合
# systemctl get-default(ランレベルの確認)
# systemctl set-default ターゲット名(ランレベルの設定)

CentOS7の場合、ランレベルの変更は「systemctl」コマンドで実施できます。ランレベルとターゲットの対比は以下となります。

ランレベル ターゲット名 別名
0 poweroff.target runlevel0.target
1 rescue.target runlevel1.target
2 multi-user.target runlevel2.target
3 multi-user.target runlevel3.target
4 multi-user.target runlevel4.target
5 graphical.target runlevel5.target
6 reboot.target runlevel6.target

これらの設定は次回起動時から有効になります。

なお、即時でランレベルの変更するにはCentOS6では「init」コマンドを使用、CentOS7では「isolate」オプションを使用します。

# init ランレベル(CentOS6の場合)
# systemctl isolate ターゲット名(CentOS7の場合)

ubuntuのプロセス制御

CentOSについてプロセスの起動、停止、再起動やランレベルの変更方法を書いてみましたが、ubuntuの場合も書いておこうと思います。ubuntu 18.04 LTSの場合ですが「service」コマンドや「chkconfig」コマンドは使えません。代わりに「systemctl」が使用できますのでCentOS7と同様の方法でプロセスの起動、停止、再起動やランレベルの変更が行なえます。「man systemctl」で詳細が見られますので確認してみてください。

※systemctlを含めCentOS7についての詳細本。