So-net無料ブログ作成

Read関数の最大受信サイズを調べる [ColdFire V2]このエントリーを含むはてなブックマーク#

2047506

Read関数で大きなデータを受信するプログラムを作成しました。 この基板でネットワークらしいプログラムを組むのは、初めてだな。

サーバ側プログラムは、Javaで組んだ

実験を行うにあったって、サーバのプログラムをJavaで組みました。 送信されてきた数値に相当する長さのアスタリスクで埋め尽くされた文字列を送り返します。 簡単なプロトコルなので、telnetを使って、会話することも出来ます。

/*
 * --------------------------------------------
 * File         : TcpEchoServer.java
 * Package      : org.noritan.tcpecho
 * Copyright    : Copyright (c) 2008 noritan.org
 * Organization : noritan.org
 * Created      : 2008/09/22
 * --------------------------------------------
 */
package org.noritan.tcpecho;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * This TcpEchoServer class is a server
 * to send a packet to the client regarding the client's
 * request sent by a received packet.
 * 
 * @author noritan
 */
public class TcpEchoServer
  implements Runnable
{
  private int port;

  /**
   * Construct a TcpEchoServer object.
   *
   * @param port
   *   a port number to wait for a clinet.
   */
  public TcpEchoServer(int port) {
    this.port = port;
  }

  /**
   * A server process method.
   * This server accepts only one client at once and
   * quits when the connection has been closed.
   * 
   * @see java.lang.Runnable#run()
   */
  public void run() {
    try {
      // Create a server socket at a specified port.
      ServerSocket server = new ServerSocket(port);
      try {
        for (;;) {
          // Wait for a client.
          // Only one client can be accepted.
          Socket socket = server.accept();
          System.out.println("Connected by "
                  + socket.getInetAddress());
          System.out.println("SEND BUFFER="+socket.getSendBufferSize());
          try {
              echoback(
                       socket.getInputStream(),
                       socket.getOutputStream());
          } catch (IOException ex) {
              System.out.println("Failed to get streams");
          } finally {
              try {
                  socket.close();
              } catch (IOException ex) {
                  // do nothing for close
              }
              System.out.println("Socket closed");
          }
        }
      } catch (IOException ex) {
        System.out.println("Failed to accept a socket");
        ex.printStackTrace();
      } finally {
        try {
          server.close();
        } catch (IOException ex) {
          // do nothing for close.
        }
      }
    } catch (IOException ex) {
      System.out.println("Failed to open a server");
      ex.printStackTrace();
    }
  }

  /**
   * Send a echo-back using a specified input
   * and output streams.
   * 
   * @param inputStream
   *   An InputStream to get a request from a client.
   * @param outputStream
   *   An OutputStream to send an echo to a client.
   */
  private void echoback(
    InputStream inputStream, OutputStream outputStream
  ) {
    BufferedReader bufferedReader
      = new BufferedReader(
        new InputStreamReader(inputStream)
      );
    for (;;) {
      String line;
      try {
        // Get a line
        line = bufferedReader.readLine();
        // Quit the loop at EOF
        if (line == null) break;
      } catch (IOException ex) {
        System.out.println("Failed to read a line");
        break;
      }
      System.out.println("REQUEST="+line);
      try {
        outputStream.write(createReply(line));
      } catch (IOException ex) {
        System.out.println("Failed to send a packet");
      } finally {
        try {
          outputStream.flush();
        } catch (IOException ex) {
          // do nothing for flush
        }
      }
      System.out.println("Reply sent");
    }
  }

  /**
   * Create a reply to the client regarding the
   * request from the client.
   * 
   * @param request
   *   Request received by the client.
   * @return
   *   Reply to the clinet in byte[]
   */
  private byte[] createReply(String request) {
    byte[] buffer;
    int length = 1; // default length
    try {
      // Get a request as a packet size.
      length = Integer.parseInt(request);
    } catch (NumberFormatException ex) {
      System.out.println("Not a number");
    }
    // Create a reply, a specified size of packet
    // Filled with *
    buffer = new byte[length];
    for (int i = 0; i < length; i++) {
      buffer[i] = '*';
    }
    return buffer;
  }

  /**
   * Invoke a server thread.
   * 
   * @param args
   *   No arguments are used.
   */
  public static void main(String[] args) {
    new Thread(new TcpEchoServer(30049)).start();
  }
}

クライアント側プログラムは、SilentCで組んだ

クライアント側のプログラムは、われらがSilentCで組みました。 コンソールから入力された文字列に行末文字を追加してサーバに送り、返答を受け取ります。 返答された内容は、捨ててしまい、Read関数が返してきた値だけを記録していきます。 Read関数では、タイムアウト時間として10秒待ち、エラーコードが返ってくるまで受信を試みます。 すべてのデータを検出するためにわざと長いタイムアウト時間を使っています。

main(){
  char socket;
  char *host = "192.168.1.2";
  char *line = MemoryAlloc(32);
  int len;
  long sum;
  char *buf;
  
  do {
    socket = CreateSocket(1);
    do {
      if (Connect(socket,GetHostByName(host),30049) < 0) {
        PrStr("Failed to connect\r\n");
        break;
      }
      for (;;) {
        PrStr("\r\n> ");
        Gets(line,16);
        if (line[0] == 'q') {
          PrStr("QUIT detected\r\n");
          break;
        }
        StrCat(line,"\n");
        if (Write(socket,line,StrLen(line)) < 0) {
          PrStr("Failed to Write\r\n");
          continue;
        }
        if (WaitWriteComplete(socket) < 0) {
          PrStr("Failed to Complete Write\r\n");
          continue;
        }
        sum = 0;
        for (;;) {
          len = Read(socket,1000);
          if (len < 0) break;
          buf = GetReceiveBuffer(socket,1);
          MemoryFree(buf);
          PrHexWord(len);PrStr(" ");
          sum += len;
        }
        PrHexWord(len);PrStr(" ");PrNum(sum);
      }
    } while (0);
    CloseSocket(socket);
  } while (0);
  MemoryFree(line);
}

プログラムの実行結果

今回は、UART0のコンソールを使って通信を行わせています。 プログラムを実行した結果、Javaでは一括で送っているつもりのデータをSilentCでは分割して受け取っていることが分かりました。

> 100
0064 fffe 100
> 200
00c8 fffe 200
> 400
0190 fffe 400
> 800
0218 0108 fffe 800

Read関数の返してきた値に加えて、エラー以外の値の合計を計算させて表示しています。 これによると、バッファの単位は、536($0218)のようです。 これは、誰が決めた値なんだろう?

データのサイズを大きくするとデータが足りなくなってしまうことが多くなります。

> 1600
0218 0210 fffe 1064
> 1600
0218 0210 fffe 1064
> 1600
0218 0218 fffe 1072
> 1600
0218 0218 0210 fffe 1600
> 1600
0218 0210 fffe 1064

SilentC側の処理を早くしてみる

これは、単にSilentC側でバッファがあふれているのだろうと考えて、あふれる前にデータを引き取ることが出来るようにプログラムを変更しました。

main(){
  char socket;
  char *host = "192.168.1.2";
  char *line = MemoryAlloc(32);
  char **bufTab = MemoryAlloc(64);
  long *lenTab = MemoryAlloc(64);
  long retval,sum,i,index;
  
  do {
    socket = CreateSocket(1);
    do {
      if (Connect(socket,GetHostByName(host),30049) < 0) {
        PrStr("Failed to connect\r\n");
        break;
      }
      for (;;) {
        PrStr("\r\n> ");
        Gets(line,16);
        if (line[0] == 'q') {
          PrStr("QUIT detected\r\n");
          break;
        }
        StrCat(line,"\n");
        if (Write(socket,line,StrLen(line)) < 0) {
          PrStr("Failed to Write\r\n");
          continue;
        }
        if (WaitWriteComplete(socket) < 0) {
          PrStr("Failed to Complete Write\r\n");
          continue;
        }
        index = 0;
        for (;;) {
          retval = Read(socket,1000);
          if (retval < 0) break;
          lenTab[index] = retval;
          bufTab[index] = GetReceiveBuffer(socket,1);
          index++;
        }
        sum = 0;
        for (i=0;i<index;i++){
          PrHexWord(lenTab[i]);PrStr(" ");
          sum += lenTab[i];
          MemoryFree(bufTab[i]);
        }
        PrHexWord(retval);PrStr(" ");PrNum(sum);
      }
    } while (0);
    CloseSocket(socket);
  } while (0);
  MemoryFree(bufTab);
  MemoryFree(lenTab);
  MemoryFree(line);
}

バッファを受け取るごとに行っていたバッファ領域の開放と表示を後回しにしました。 しかし、効果は無く、あいかわらずデータの欠損が見られます。

> 100
0064 fffe 100
> 200
00c8 fffe 200
> 400
0190 fffe 400
> 800
0218 0108 fffe 800
> 1600
0218 0210 fffe 1064
> 1600
0218 0210 fffe 1064
> 1600
0218 0210 fffe 1064
> 1600
0218 0210 fffe 1064
> 1600
0218 0218 0210 fffe 1600
> 1600
0218 0210 fffe 1064

こうなったら、メモリ・リーク覚悟で、バッファの保存を省いてしまおう。

        index = 0;
        for (;;) {
          retval = Read(socket,1000);
          if (retval < 0) break;
          lenTab[index] = retval;
          GetReceiveBuffer(socket,1);
          index++;
        }
        sum = 0;
        for (i=0;i<index;i++){
          PrHexWord(lenTab[i]);PrStr(" ");
          sum += lenTab[i];
        }
        PrHexWord(retval);PrStr(" ");PrNum(sum);

結果は、ますますひどくなってしまったようです。

> 100
0064 fffe 100
> 200
00c8 fffe 200
> 400
0190 fffe 400
> 800
0218 fffe 536
> 1600
Failed to Complete Write

これは、サーバとクライアントが共にデータを送ろうとした結果、ハングしたものと思われます。

本日の結論

  1. "Read"関数には、127バイトを超えるデータを受信する能力がある。
  2. しかし、SilentCで大きなデータを受信するのは苦しい。バッファサイズの536バイトを上限としてボチボチ送ったほうが良さそうだ。

参考文献

Interface (インターフェース) 2008年 09月号 [雑誌]

Interface (インターフェース) 2008年 09月号 [雑誌]

  • 作者:
  • 出版社/メーカー: CQ出版
  • 発売日: 2008/07/25
  • メディア: 雑誌
TCP/IP解説書のお勧め募集中。

nice!(0)  コメント(5)  トラックバック(0)  このエントリーを含むはてなブックマーク#

nice! 0

コメント 5

hamayan

またどっかのバカたれが変なトラックバック付けている様ですが、そちらは置いていおいて。

536bytesはデフォルトのMSS値だと思います。私の方でも書いている様に、SilentCはコネクション時にオプションの交換を行わないので、Windows側がデフォルトのMSS値を使用したのでしょう。つまり分割して送っているのはPC側だと思われます。パケットモニタを仕掛ければ一発ですが。

しかし普通はWidnowサイズでフロー制御を行うのだから、TCPでデータの欠損が発生するのはまずいですね。

TCPの入門ならこれですか、
http://www.amazon.co.jp/マスタリングTCP-IP-入門編-第4版-竹下/dp/4274066770/ref=sr_1_1?ie=UTF8&s=books&qid=1222183661&sr=1-1

実装までするならRFCとこれですか、
http://www.amazon.co.jp/詳解TCP-IP〈Vol-1〉プロトコル-W-リチャード-スティーヴンス/dp/4894713209/ref=sr_1_4?ie=UTF8&s=books&qid=1222183661&sr=1-4

by hamayan (2008-09-24 00:36) 

noritan

なるほど、536バイトは、Windows(オペレーティング・システム)が分割したパケットサイズでしたか。

フロー制御で次のパケット(?)の送信を待たせるとしたら、どのタイミングが正しいのでしょうか。

Read関数で受信データ長を返してきて、GetReceiveBuffer関数でデータを取り出すのだから、GetReceiveBuffer関数の最後で次のパケットの送信を許可する他に実装しようがないと考えています。そのためのreleaseビットなのですよね。

by noritan (2008-09-24 06:34) 

hamayan

> フロー制御で次のパケット(?)の送信を待たせるとしたら、

いや、アプリケーション側でこう言った操作をする事は普通無いです。
TCPではソケットレベルで常に自分が受信可能なバッファサイズを報告しています。

例えばここでは
http://hamayan.blog.so-net.ne.jp/2008-09-14-2
SilentC側では自身の受信可能なサイズとして1454bytesを報告しているので、少なくともPC側は一気に1454bytes(3パケット連続)まで送信可能な筈です。
受信バッファが少なくなればそのサイズを、無くなれば0をACK応答の中で報告すれば、PC側は送信を一旦中断します。

欠損するとすればこのやり取りの何処かに問題が有るのでしょうけれど、やっぱりパケットモニタを仕掛けないと何処にそれが有るのか判らんです。

by hamayan (2008-09-24 06:52) 

masato

当方では Read() .. GetReceiveBuffer() .. Free() のループ内に SystemSleep() を入れることでデータの欠落を回避できましたが、どうでしょう。
by masato (2008-09-24 22:14) 

noritan

SystemSleep()、やってみました。
確かに欠損はなくなりましたが、あまりにもトロいので、さきほどパケット・ログを取ってみました。また、3000バイトの通信には耐えられないようです。

詳しくは、次回の記事で。

by noritan (2008-09-24 23:10) 

コメントを書く

お名前:
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

トラックバック 0

トラックバックの受付は締め切りました

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。