iPhoneからC言語を使ってTCP通信してみよう


前回、UDP通信を実装してみたところ、意外にも簡単にプログラミングできることに驚き感動しました。この勢いでTCP通信も試してみたいと思いましたが、いざ調べてみるとUDPよりもだいぶ複雑そうです。まずは、プログラミングの前にそもそもTCPがどのような通信なのか調べてみることにしました。

TCP通信の流れ

Google先生にTCPのことを聞いてみると、このような図がたくさん出てきます。



まずは大きく分けて考えてみます。すると、次の3つの流れ(シーケンス)に分けられるようです。
1. コネクションの確立
2. データ送信
3. コネクションの解放

実際のデータを送るのは2番の仕事ですが、その前後で1と3のコネクションという処理が出てきます。
これは人間のコミュニケーションに似ているところがあると思いました。つまり、「挨拶から始まり、さよならの挨拶で終わる」のです。
そしてこのような約束事を、プロトコルと呼びます。プロトコルを決めないと、人間だって機械だって会話が成り立たないということですね。

さて、UDPというのは、「User Datagram Protocol」の略でした。Datagramというのは、届くかどうかは保証されないですよという意味でした。それに対して、TCPは「Transmission Control Protocol」の略です。Transmissionは送信という意味です。つまり、送信状態を管理(コントロール)できるプロトコルであることがわかります。

ところで上の図をもう一度みてみます。矢印の流れの中に「SYN」や「ACK」などの文字が書かれています。これは一体何を意味するのでしょうか。調べてみると次のような意味であることが分かりました。

記号 意味
SYN Synchronize
ACK Acknowledge
FIN Final

それでも、いまいちピンときません。そこで、次ように読み替えるとわかりやすくなりました。

記号 意味
SYN 送ってもいいかな?
ACK おっけー
FIN 終わるねー

実際のTCPパケットの構造は次の図のようになっています。UDPパケットよりは複雑になっていることが分かります。それでも、ポイントを抑えればきっと私でも理解できるはずと思い、ここで挫けずに進めていきたいと思います。



パケットをよく見てみると、先ほどのACKやSYNというコントロールフラグが存在するのが分かります。つまりACKやSYNパケットであるかどうかという判断はこの部分を変えれば良いということが分かります。もっと言うと、フラグというのは1ビット単位の0か1かの信号状態のことです。フラグそのものは、決して難しいことをしている訳では無いということが分かりますね。

送信元ポートや送信先ポートはUDPでも出てきましたので大丈夫でしょう。気になるのはシーケンス番号とシーケンスACK番号(確認応答番号)という部分です。どうもこの部分がTCP通信で鍵を握っているようです。

さて、大雑把ですがTCP通信の雰囲気がつかめてきました。次では実際の通信のやり取りを詳しく見ていきます。そこで最初の「コネクションの確立」つまり、「3ウェイハンドシェイク」のやり取りを詳しく見ていくことにします。


3ウェイハンドシェイク




TCP通信を行うにあたって、まず最初に3ウェイハンドシェイクというコネクションの確率を行う必要があります。TCPパケットで意識する部分はたったの4つで良いので、その他のことはいったん忘れてしまいましょう。



また、次のようなルールがあることを先に覚えておくと理解しやすいと思います。

シーケンスACK番号のルール

「相手から受信したシーケンス番号」+「データサイズの値」
ただし、3ウェイハンドシェイクでは「相手から受信したシーケンス番号」に「1」を加算した値となる

特に今回の場合は3ウェイハンドシェイクの話ですので、「相手から受信したシーケンス番号」に「1」を加算した値となるの部分を意識すれば良いということになります。

それでは3ウェイハンドシェイクのやり取りを見ていきます。

最初に、クライアントがサーバーに対してSYNパケットを送ります。最初に述べた通り、SYNパケットといってもSYNフラグを1、ACKフラグを0にしただけのことです。シーケンス番号ですが、最初の送信ではランダムな値をとるようです。なので、ここでは仮に1000としました。



次に、 SYNパケットを受け取ったサーバーは、クライアントへSYN ACKパケットを返します。この時のシーケンスACK番号は、先ほどのルールに従うことになります。つまり、受信したシーケンス番号の1000に1を加えた1001の値になるということです。
シーケンス番号はですが、サーバー側でも最初の送信はランダム値をとるようです。そこで、ここでは仮に9000としました。



最後に、クライアント側を見ていきます。サーバーからSYN、ACKパケットを受けとったクライアントは、接続開始のACKパケットを送り返すことになっています。シーケンス番号は相手から受け取ったシーケンスACK番号になるという決まりがあります。つまりここでは、1001の値となることが分かります。また、シーケンスACK番号は先ほどと同様に、送られてきたシーケンス番号9000に1を足して9001となります。



以上のように、このまま続いていけばシーケンス番号とACK番号が加算されながら入れ替わっていくことが分かります。このように相手の状態をパケットに埋め込むことで、通信の状態をお互いに把握できるようになっていることが分かりました。つまり、どこまで送信受信できているかということは、このシーケンス番号とシーケンスACK番号を見れば確認できるということです。


参考:

https://www.infraexpert.com/study/tcpip9.html
https://ja.wikipedia.org/wiki/3%E3%82%A6%E3%82%A7%E3%82%A4%E3%83%BB%E3%83%8F%E3%83%B3%E3%83%89%E3%82%B7%E3%82%A7%E3%82%A4%E3%82%AF