ネットワーク

【ネットワーク】プロセス間の通信【TCP・UDP(トランスポート層)】

プロセス間の通信とは?

 

プロセスとは、あるホストで実行されているプログラムのことです。

・同じホスト(コンピュータ)で2つのプロセスは、OSが提供しているプロセス間通信機能を用いて通信をします

・異なるホスト上のプロセスは、 ネットワークを通じて、メッセージの交換によって通信をします。

トランスポート層は、プログラム間が通信を行う部分のみを行う。コンピューター間を繋ぐ部分に関しては、ネットワーク層が行う。
→トランスポート層は、エンドツーエンドのプロトコルであると言われる。 

ソケット通信

 

ソケットとは、アプリケーション層とトランスポート層のインターフェースになります。

トランスポート層の役割

トランスポート層のプロトコル(ソケット通信を支えるもの)

プロトコルの種類 信頼性 速さ 通信の時にする事
TCP

(Transmission Control Protocol)

信頼性がある

(事前に相談の上でやる)

確実に届けるために厳密な仕組みのためUDPに比べて遅い エラーに対処する

データの順番を制御する

重複を排除する

フロー制御・輻輳制御を行う

UDP

(User Datagram Protocol)

ある部分の情報が抜け落ちる場合がある チェックを省いているためTCPに比べて早い 相手との接続の交渉をせず、一方的にデータを送る

トランスポート層は、ネットワーク越しに通信するプログラム間をつなぐプロトコルです。コンピュータネットワーク上の通信では、あるコンピュータのあるプログラムと別のコンピュータの別のプログラムががデータのやり取りを行うが、プログラムとプログラムの直接的なやり取りを行うのがトランスポート層で、コンピュータとコンピュータをつなぐのは、ネットワーク層になります。

TCP通信(Transmission Control Protocol)

コネクション型通信。
全てのパケットが相手に届いたかを確認しながら通信を行い、パケットの喪失が起こった場合はその事が検出され、再送処理が行われる。再送処理が行われたり、パケットの通る道によって到着順番が異なる場合があります。受信側のTCPでは、これを順序制御により正しく並び替えます。→信頼性を提供する

TCPのヘッダフォーマット

コネクションの確立と切断

TCPは通信の準備として、通信相手と1往復半のやり取りをする。3つのパケットが行き来してから通信を始めるために3-way- handhsake(スリーウェイハンドシェイク)と呼ばれている。

コネクション確立時

①SYN(Synchronization)というコネクション確立要求のパケットを送信する

②送信側のSYNに対するACKと受信側からのSYNを送信する

③受信側のSYNに対するACKを受信側に送る

コネクション切断時

④FINというコネクション切断要求

⑤送信側のFINに対するACKを送信側に送る

⑥受信側から送信側にFINを送る

⑦受信側のFINに対するACK送る

⑤の時点では、ハーフクローズと呼ばれるコネクションが半分閉じた状態になっている。受信側が送信側に何か送りたいデータが残っている場合は、そのデータを送ることができる。

実際のコードで見てみる(Java)

クライアント側
import java.net.Socket;
import java.io.InputStream;
import java.io.DataInputStream;
import java.io.OutputStream;
import java.io.DataOutputStream;

class Client {
    public static void main(String args[]) {
        try {
            String server = args[0];
            int port = Integer.parseInt(args[1]); //サーバー側のポート番号
            Socket s = new Socket(server, port);

            // サーバーに数値を送信
            OutputStream os = s.getOutputStream();
            DataOutputStream dos = new DataOutputStream(os);
            dos.writeInt(Integer.parseInt(args[2]));

            // 演算結果を受信
            InputStream is = s.getInputStream();
            DataInputStream dis = new DataInputStream(is);
            int res = dis.readInt();
            System.out.println(res);

            // ストリームを閉じる
            dis.close();
            dos.close();
        }
        catch (Exception e) {
            System.out.println("Exception: " + e);
        }
    }
}
サーバー側
import java.net.ServerSocket;
import java.net.Socket;
import java.io.InputStream;
import java.io.DataInputStream;
import java.io.OutputStream;
import java.io.DataOutputStream;

class Server {
    public static void main(String args[]) {
        try {
            int port = Integer.parseInt(args[0]); //サーバ側の待受ポート番号
            ServerSocket ss = new ServerSocket(port);

            while(true) {
                Socket s = ss.accept(); //クライアントからの通信開始要求が来るまで待機

                // 以下、クライアントからの要求発生後
                InputStream is = s.getInputStream(); //クライアントから数値を受信
                DataInputStream dis = new DataInputStream(is);
                int req = dis.readInt();

                OutputStream os = s.getOutputStream(); //二乗した結果を送信
                DataOutputStream dos = new DataOutputStream(os);
                dos.writeInt(req*req);

                // ストリームを閉じる
                dos.close();
                dis.close();
            }
        }
        catch(Exception e) {
            System.out.println("Exception: " + e);
        }
    }
}
実行例

javac Server.java
javac Client.java
java Server 8888 &
java Client localhost 8888

7 //入力(クライアント側で入力)
49 //結果(クライアント側で出力)

UDP通信

コネクションレス型通信
受信側にパケットが届いたかどうかを確認しないので、受信側ではパケットが喪失していても、あるいは順序が入れ替わっても、一切関知せず届いたパケットをそのまま上位層に渡します。 → 信頼性はない

UDPのヘッダフォーマット

まず、送信側で擬似ヘッダを付加し、パディング(不足するデータの補填)を行って16ビットの倍数になるようにします。擬似ヘッダには、「送信IPアドレス」「宛先IPアドレス」「プロトコル番号」の3項目が擬似ヘッダに含まれている。送信側でこれらのデータのチェックサムの計算を行い、チェックサムフィールドの値と照らし合わせ、途中でビッよエラーが起きてないかどうかを調べます。

チェックサムの計算

チェックサムには、1の補数和を利用します。0x1234に0xFFFF(1の補数の-0)を足した場合を考えるとこれは、0x11233となり、桁上がりを右から足し込んで0x1234、つまり元の数になります。

チェックサムをビットエラーの確認に用いる場合、送信側ではまず擬似ヘッダを加えチェックサムフィールドを0で埋めます。そして16ビット単位で区切ってパケット全体の1の補数和を求め、この値の1の補数を求めてチェックサムフィールドに入れ送信します。この時、チェックサムフィールドには、それ以外の部分の1の補数和を計算した値に対する1の補数が入っていることになります。 このパケットがビットエラーを起こさずに受信側についた場合、送信側と同様の処理を行ってチェックサムを含むパケット全体の1の補数和を計算すると0(0xFFFF)になるはずです。なぜなら、チェックサム以外の部分の1の補数和とチェックサム部分とは1の補数の関係にあるからです。もしそうならなかった場合には送信中に何処かのビットが化けてしまったことがわかり、ビットエラーを検出できます。このようなチェックサムはUDPでも利用されている。

コネクションの確立と切断

UDP の場合TCPのようにコネクションの確立はせずに、予告なしにいきなりデータを相手に送り、何も終わることを受信側に伝えずに通信を終わる

実際のコードで見てみる(Java)

クライアント側
import java.net.DatagramSocket;
import java.net.DatagramPacket;
import java.net.InetAddress;

class Client {
    public static void main(String args[]) {
        try {
            InetAddress addr = InetAddress.getByName(args[0]);
            int port = Integer.parseInt(args[1]); //サーバ側の待受ポート番号

            // 送信するパケットを生成
            byte buffer[] = args[2].getBytes();
            DatagramPacket dp = new DatagramPacket(buffer, buffer.length, addr, port);

            // ソケットを生成してパケットを送信
            DatagramSocket ds = new DatagramSocket();
            ds.send(dp);
            ds.close();
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
}
サーバー側
import java.net.DatagramSocket;
import java.net.DatagramPacket;

class Server {
    public static void main(String args[]) {
        try {
            int port = Integer.parseInt(args[0]); //サーバー側の待受ポート番号
            DatagramSocket ds = new DatagramSocket(port);

            byte buffer[] = new byte[32]; //受信データを書き込むためのバッファ
            while(true) {
                // 受信したデータは実際にはbufferに格納される
                DatagramPacket dp = new DatagramPacket(buffer, buffer.length);
                ds.receive(dp); //受信するまで待機

                // 以下、受信後
                byte received[] = new byte[dp.getLength()];
                byte tmp[] = dp.getData(); //32バイト (buffer.length)
                for(int i=0; i<dp.getLength(); ++i) received[i]=tmp[i];

                // 文字列化して出力
                String str = new String(received);
                System.out.println(str);
            }
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
}
実行例

javac Server.java
javac Client.java
java Server 8888 &
java Client localhost localhost 8888

poskey //入力(クライアント側での入力)
poskey //結果(サーバー側で出力)