超デバック2 TTCP

超がついていますが、あまりやってません。ちょっとやってみて小説を読んだり、ちょっとやってみて2ちゃんねるを見たり、知人が明日面接でもう吐きそうとか言うので、ちょっかいかけたりしています。


今作っているのは、パケットの到達確認と送信速度を制御している層で、プロトコル名は『TTCP』にしました。Trivial TCPの略ですが、『TCP』の部分は、かの有名な『TCP/IP』のTCPではなく、単にTransmission Control Protocolの略です。
TestTCPというツールとかぶっているようですが気にしません。


一応、次の機能を持っています。

  • パケットは送った順に届く
  • パケットが届かないときは再送する
  • 再送が一定数を越えても届かないときはエラー
  • パケットの境界を認識できる(逆に意識したくない場合はバッファリングするストリームクラスでラップすればいいはず)
  • 双方向から通信が可能

簡単にいうと、TFTPを全二重化(もちろん仮想的に)しただけです。
それと、オプションとしてパケットのGZIP圧縮機能をつけてみました。一応、プロトコルレベルでは1パケット1024バイト以内にしようとしているのですが、GZIPオプションを使うと一度に2000バイトくらいのデータは圧縮して送れます(テキスト)。200〜500バイトを超えるパケットは圧縮したほうが通信量は減るようですが、負荷が高くなります(後々いろいろ試したい)。

なんの役に立つのか?

  • たたない

もともと、OSリソースの制限を受けないようにと、同一パケットを複数のソケットに同時送信する際の再送データを共有するために作り始めましたが、失敗だったなと思い始めています。なんたって、Javaだし……
他、UDP hole punchingすれば、別NAT内のPC同士が直接接続できます!(今のところ使わない)
ファイル転送のような操作は得意ではありません(これはまた別の案がある!)。制御用のコネクションとして使えるように作っています。

現状

正常系は動きます。
あとは、タイムアウトや不正パケットを織り交ぜてもうまいこと復帰やエラー処理ができているかチェックして、最適化なども含めたリファクタリングで終了予定。
ちょっと汚いけど、以下のコードは正常に動いています。

サーバ1つに対して2つのクライアントが接続し、送受信をします。別バージョンで双方から送信をした後、双方で受信するということも試していますがOKだったので、とりあえず送信用と受信用の線は完全に分離できていると思います。
パフォーマンスは微妙。

    public static void main(String[] argv)
    {
        Thread server = new Thread(new Runnable() {
            public void run(){
                TTCPServerSocket session;
                byte[] buffer = new byte[2000];
                
                try {
                    session = TTCPServerSocket.open(8000);
                    
                    TTCPSocket client1 = session.accept();
                    TTCPSocket client2 = session.accept();
                                        
                    for (int i = 0; i < 1000; ++i) {
                        client1.send(String.format("hello client1! %d\n", i).getBytes());
                        client2.send(String.format("hello client2! %d\n", i).getBytes());
                    }
                    for (int i = 0; i < 1000; ++i) {
                        int len = client1.receive(buffer);
                        System.out.println("server:client1: " + new String(buffer, 0, len));
                        len = client2.receive(buffer);
                        System.out.println("server:client2: " + new String(buffer, 0, len));
                    }
                    client1.close();
                    client2.close();
                } catch (Exception e) {
                    e.printStackTrace();
                    return;
                }
            }
        });
        Runnable clientProcess =  new Runnable() {
            public void run()
            {
                TTCPSocket session;
                byte[] buffer = new byte[2000];
                try {
                    session = TTCPClientSocket.open(
                            new InetSocketAddress(System.getenv("COMPUTERNAME"), 8000));
                    
                    for (int i = 0; i < 1000; ++i) {
                        int len = session.receive(buffer);
                        String str = new String(buffer, 0, len);
                        System.out.println("client: " + str);
                    }
                    for (int i = 0; i < 1000; ++i) {
                        session.send(String.format("hello server! %d\n", i).getBytes());
                    }
                    session.close();
                } catch (Exception e) {
                    e.printStackTrace();
                    return;
                }                
            }
        };
        Thread client1 = new Thread(clientProcess);
        Thread client2 = new Thread(clientProcess);
        
        server.start();
        try { Thread.sleep(2000); } catch (InterruptedException e){}
        client1.start();
        client2.start();
    }