WonderPlanet DEVELOPER BLOG

ワンダープラネットの開発者ブログです。モバイルゲーム開発情報を発信。

C/C++でネットワークプログラミングをしてみよう(1)

目的

筆者の専門分野でもあり、最近は色んなネットワークのライブラリーーcurl、photonのようなーーが多いので、手作りのクライアントやサーバも少なくなっています。 そのために問題が生じてもどのような問題なのか分からず、ライブラリーのパッチをいつまでも待たないといけなくなったり、効率の悪い回避策を入れたりすることになります。
しかしながら、オープンソースが今のように発展していない一昔は、こういうライブラリは自前で作ってきたものです。
ライブラリーを使うのに慣れている現在のエンジニアさんには難しく思えるのかも知れませんが、実際にポイントを押さえていれば、そこまで難しいものではありません。
この連載によって、基本的なソケット通信のやり方を覚え、実戦で応用ができるならばいいと思っています。

対象

 ・ソケットプログラミングに興味のある方
 ・Photonなどのネットワークフレームワークやライブラリの内部構造が気になる方
 ・ハッキング?に興味のある方

内容

1話 TCP/IPにおけるOSIの参照モデルについて
2話 ソケットプログラミングを実際にやってみよう
3話 プロトコルを実装してみよう(HTTP編)
4話 OpenSSLをつかったSSL通信をしてみよう
5話 PROXYサーバを作ってみよう
6話 NAT超えをしてみよう(P2P通信)

1話 TCP/IPにおけるOSIの参照モデルについて

概要

ネットワークプログラミングに興味のある方は、一度は耳にしたことがあると思います。
このOSI参照モデルというのは、ISOで策定されたコンピュータの通信機能において、必要であるべき機能を階層化したものです。これは、厳密な規約ではありませんが、ネットワークの実装時に推奨されるモデルです。
今は、ほとんどの通信がTCP/IPプロトコル上で動いているので、こういったモデルの意味も薄れていますが、一昔はZModem、AppleTalk、NetBEUI・・・など無数な通信プロトコルがあったためです。
このモデルの階層は7階層でできており、これをOSI’s 7Layersと言います。
本章では、TCP/IPプロトコルにおける参照モデルを中心に説明していこうと思っています。

階層は以下の通り

  1. ハードウェア層
  2. データリンク層
  3. ネットワーク層
  4. トランスポート層
  5. セッション層
  6. プレゼンテーション層
  7. アプリケーション層

既にご存知の方々もいらっしゃると思いますが、初めての方はこれだけでは何を意味するのか分からないと思うので、これに関して、わかりやすく説明をしたいと思います。

1. ハードウェア層

ある人が面白いことを考えました。
パソコンとパソコンをつないで通信をさしょうと思ったわけです。
そうするためには、送受信ができる装置が必要となります。
この装置にはモデム、ISDN、ケーブルモデム、イーサネットカードなどがありますが、一旦ここではイーサネットカードのみを想定します。
この装置をパソコンに入れてパソコンとパソコン同士をつなぎます。
これがハードウェア層となります。

2. データリンク層

さて、パソコンとパソコンを繋いだ状態で、AからBへメッセージを送ろうとします。
イーサネットカードに何かのデータを書き込みと読み取りができないと行けません。
そこで、イーサネットカードのバッファーにデータを書き込み・読み込むモジュールを作成しました。保護モードで動くOSでは、デバイスドライバーに当てはまります。
このドライバーの書き込みや読み込みの機能がデータリンク層*1になります。
基本、データリンク層ではハードウェア層にリクエストを投げることになります。

3. ネットワーク層

ただ、これだけでは面白くありません。今度は複数のパソコンに繋いで見たくなりました。
そうするためには、送り先を指定しなければなりません。
なのでパソコンに住所をつけることにしました。
その一つの規約*2がip protocol (internet protocol)です。
ipアドレスというのは、ipヘッダーの識別子になります。
そういうわけで、デバイスの方にipprotocolを実装し、転送するようにしました。
送信パケットにipヘッダーをつけて、送り先のアドレスと一緒に送信するような感じです。
この場合、送信先までに送るための中継機器が必要となります。一旦、その機器をルーターとしておきます。
一方で、受信する側のパソコンでは、自分のipアドレスのみ*3を受信できるようになります。
このデバイスドライバーのipプロトコルの実装と中継機器がネットワーク層になります。
ちなみにpingはこのネットワーク層までで動いています。
ネットワーク層ではデータリンク層にリクエストを投げるようになります。

4. トランスポート層

これで複数のパソコンで通信ができるようになりました。
でも、もう少し面白いことがしたいところです。
パソコン同士でチャットをしたり、ファイルのやりとりを同時にやりたい。
でも、ipプロトコルだけでは、どれがチャットのメッセージでどれがファイルのメッセージなのか区別がつきません。
そこで工夫された一つの規約がTCPプロトコルです。
ポート番号を設け、デバイスに届くデータを振り分ける仕組みです。
TCPプロトコルをデバイスに実装しました。*4
なので、データを送る際にTCPヘッダー+実データにして、ネットワーク層へ要求を出すことになります。
この機能がネットワーク層とトランスポート層に当てはまります。
このトランスポート層では、データのロスを防ぐために、データが失われた場合は、再要求を送信したりするような機能も実装してあります。
トランスポート層では基本的にネットワーク層にリクエストを投げるようになります。

5. セッション層

これで、パソコン同士が通信ができるようになりました。
ただ、LANケーブルはノイズがあり、データが失われたり、接続が不安定なことが多いです。
その接続の回復などの処理ーー通信の再送とは別ーーを行う機能を追加しており、この処理を行うのがセッション層であります。*5

TCP/IPにおいて、ハードウェア層からセッション層までは全てハードウェアとデバイスドライバーの内部の話になります。
実際にこの階層で綺麗に分かれているような実装になっているかどうかは不明ですが。。。
プレゼンテーション層というのは、アプリ層の方で、プレゼンテーション層へ要求を出す際に形式的な変換ーー例えば、文字コードの変換ーーのような事をしますが、TCP/IPにおいて、プレゼンテーション層というのは、デバイスドライバーの方で対応することが難しいです。
なぜなら、TCP/IPの上に色んなプロトコルが存在するために一概に処理をすることができないためです。
なので、TCP/IPのネットワークプログラミングの話は主にプレゼンテーション層とアプリ層の話になります。
抽象的で分かりづらいとは思いますが、実際にソケットプログラミングをすると分かってくるかと思います。

最後に次回の話のための何を使ってソケット通信のプログラムをするかを説明したいと思います。

Burkey Socket

ネットワークデバイスでは、以下のようなインターフェースを提供しています。
これらはシステムコールで、以下のシステムコールの形式をBurkey Socketと言います。BSD Unixに初めて実装され、今はほとんどのOSで採用されています。

  • socket
    openと同じようにdescriptorを生成します。ファイルと同じくdescriptorはi-nodeとして管理されます。
  • close
    socketで生成されたdescriptorを解放します。
  • send/write
    データを送信します。ファイルと同じくwriteもサポートしていますが、ioctlなどで調整が必要となります。なのでsendを使う方をお勧めします。
  • recv/read
    データを受信します。
  • ioctl/fctl
    ソケットの受信をnon-block状態にしたり、送信時のバッファーを設定したり、ブロードキャストの設定をしたり細かい設定を行うためのインターフェース。 OSの実装の仕方によって、どれをどう使うかになります。ほぼどちらも使えますが、OSによっては片方しか使えなかったりします。
  • select
    設定したdescriptorから何か変化があるまで、スレッドをスリープ状態にします。

ストリーム(TCPのコネクション型時の追加のシステムコール)

  • connect
    リモートと接続*6を計ります。
  • bind
    ポート番号とdescriptorを結びつけます。
  • listen
    クライアントのconnectを待ちます。
  • accept
    listenから接続が確立したセッションに対してdescriptorを生成します。

ソケットプログラミングにおいて、使うのはは上記のシステムコールのみとなります。
今回は文章が多く退屈だったかと思いますが、次回では簡単なクライアントとサーバのプログラムを作成したいと思います。

*1:厳密には、イーサネットカードのヘッダーも含まれる

*2:ipアドレスなしでどのように通信を行うのか疑問に思う方もいると思うので説明すると、そういう場合はMACアドレスやドメイン名などで通信することになります。ただし、ローカル内での通信までしかできない

*3:今のスウィッチングハブは、違うipアドレスの機器には転送しないが、その前まではつながっている全ての機器に送信していたの>で、各機器の方で違うipアドレスのパケットを弾く処理を行なっていた。

*4:デバイスドライバーの実装はここから複雑になっていく

*5:TCP/IPプロトコルに置いては、セッション層はデバイスの方ではそんなに処理がない。AppleTalkのような通信プロトコルでは、かなり多くの機能が入る

*6:ハンドシェーク