uIP TCP/IPについて

先ごろ(だいぶ前ですが)ルネサスサイトから uIP TCP/IP Protocol Stack Demostration Document number:R01AN0169EU0100 Ver 1.01 NOTEs: * The demostration directory structure is explained in the application note. * This application note supports RX62N and the demostration project runs on RSK+RX62N. UIPのサンプルを入手しましてSH7670へ移植しました、その結果とても良好な結果を得ましたので報告します 自前のソースでTCP/IPを実装して稼動させていましたがよく応答ができなくなったりしていました いろいろな遷移状態でわけがわからないような(たぶんプロトコルが十分でない)遷移もありました UIPにすると必ず応答して、応答不良などまだ経験がありません、 ただこのUIP長い(大きい)データ転送には弱いんです、”どうしてこんなに時間がかかるの”というぐらい なにか説明ではACKが200mセコンドウェイト方式を採用しているからだとか これの速度はもっと速くはならないものでしょうか? IWIPなど聞きますが、UIPと比べてどうなのでしょうか、IWIPのほうがいいのでしょうか?
Parents
  • こんにちは、ルネサスマイコン(RXマイコン中心ですが)でTCP/IP関連をいろいろ実験しておりますシェルティと申します。

    TCP/IPですが、複雑怪奇な仕様であり、スクラッチ開発となると相当な難易度ですね。

    実験/研究レベルならスクラッチ開発もありかもしれませんが、量産製品に適用となると、

    第一にネットワーク関連ソフトを組み込みマイコン向けに製品化しているソフトウェアハウス製のものがおすすめです。

    例えば、図研エルミック殿やデータテクノロジー殿、ユビキタス殿などの組み込み用TCP/IPが有名です。

    ルネサス製でも組み込み用TCP/IP M3S-T4-Tinyというのが有償製品として提供されています。(SH2A用の無償ダウンロードもあるようです)

    http://japan.renesas.com/mw/t4

     

    第二に組み込み用OSに内包されているTCP/IP機能を使うのも良いです。

    (ここでいう組み込み用OSはリアルタイムOSではなく、組み込み用Linuxを指します)

     

    第三にuIPやlwIP等のオープンソース系のコードを頑張って量産製品に適用する道です。

    第四にスクラッチ開発でTCP/IPを手作りする道です。

    第三・第四の道は技術力に自信が有る場合におすすめですね。

    安く仕上げられるというメリットも捨てがたいと思います。

    -------

    さて、uIPで大きいデータ転送に弱い件ですが、uIPのTCP処理の実装に問題があります。

    lwIPでも私が調査した限り同様の問題を含んでいます。

    組み込み用TCP/IP M3S-T4-TinyもRXマイコン用パッケージにて、最近ソースコードが公開されたので中の実装を確認したところ、

    uIPやlwIPよりはマシですが、もう一段の改善が必要といったところです。

    各TCP/IPを比較すると以下の通りです。

    uIP or lwIP : TCPの送信アルゴリズムが貧弱。データ送信1回につき1個のACKが戻ってくることを期待している。

             多くの一般的なTCP/IPはTCPデータ受信時は2個データを受信する or 1個データ受信し200ms経過後タイムアウトしACKを返す実装になっている。

             その結果、毎回タイムアウトが発生し1秒に5回のデータ転送しか実現出来ない。

    組み込み用TCP/IP M3S-T4-Tiny : TCPの送信アルゴリズムが少し貧弱。データ送信は2個セットでACKが戻ってくることを期待している。

             多くの一般的なTCP/IPはTCPデータ受信時は2個データを受信する or 1個データ受信し200ms経過後タイムアウトしACKを返す実装になっている。

             その結果ACKがすぐ戻ってくるネットワーク環境(LAN)においては転送速度に問題は発生しないが、

             ACKがすぐに戻ってこないネットワーク環境(WAN)においては転送速度がACKが返ってくる時間に律速する。

             ACKが返ってくるまで200msかかる(東京-北米西海岸等)通信をする場合は、uIP or lwIPと変わらない程度の通信速度に落ちる。

             ※ローカル環境ではSH7216@200MHz で、TCP送受信が60Mbps程度出せることを確認しました。(RX62N@100MHz では30Mbps程度確認)

    -----

    これは組み込み用TCP/IPが多くの場合、RAM容量に制限の多いマイコンでの動作を期待しているため、

    送信用/受信用のバッファを多く持たないことに起因します。

    TCPの規格上、お互いに受信用のバッファサイズ(受信ウィンドウと呼ばれます)を常にTCPヘッダの特定16bitエリア(0xffff=最大64KB)に載せて

    通知し合っているわけですが、上述したTCP/IPたちはここにTCPパケット1個分(=1460バイト)の容量を設定しているのです。

    #組み込み用TCP/IP M3S-T4-Tinyではここが可変になっていて柔軟にチューニングが効くようにはなっています。

    その上、送信アルゴリズムも上述した通り貧弱ですのでいずれにしても大きなデータ通信には向きません。

    -----

    長くなりました。

    結論ですが、量産製品にTCP/IP機能を付ける目的であれば、有償のTCP/IP製品をお求めになるのが良いかと思います。

    とにかくまず実験して感触を掴んでみたい/通信速度が気になる/LANでしか通信しない、ということであれば、

    SH2A用の組み込み用TCP/IP M3S-T4-Tinyを使って実験されるのが良いかと思います。

    ただ、ソフトは無償ダウンロードがありますが、SH7216の評価ボードとE10Aエミュレータが必要となり、ちょっと実験してみるのには敷居が高そうですね。

    (TCP/IP処理部分はSH7670でも動作するでしょうけれど、サンプル全体がSH7216用に作られているため、

     Etherドライバ付近とボード周りの設定プログラム、スタートアップ等を調整してあげる必要はありそうです)

    もちろん、せっかくuIPをSH7670に移植されたのですから、大きなデータ通信については目をつむり、そのまま使用し続ける案も十分に有りと思います。

    以上です。

     

     

  • すみません、少し書き漏れました。

    一般的なTCP送信アルゴリズムは、「通信相手の受信ウィンドウサイズに空きがあればACKが返ってこなくてもお構いなしに送信する」です。

    このように実装するためには、通信相手の受信ウィンドウサイズの最大値(0xffff=64KB)の容量の送信バッファが必要です。

    しかも、通信相手が複数同時に居る場合は通信相手の数分だけ64KBの送信バッファが要ります。もちろん受信ウィンドウも要ります。

    従って、多くの(オープンソース系)組み込み用TCP/IPは多くのマイコンで動作させることを優先し、

    バッファサイズを制限し通信速度を犠牲にしたのだと思います。

    組み込みソフトを専業でやっているソフトウェアハウスのTCP/IPの場合、この辺りが柔軟に設定できるものがあると聞きますので

    本格的に量産製品を作られる場合は一度ソフトウェアハウスに相談してみるのが良いかと思います。

     

     

  • E_WBLK len=-83 E_WBLK len=-83 これは正常なt4_callbackでもこれを返すようです、正常であればデータ数を返すとなっていますがtelnetの画面はエコーしているのですが、tcp_rcv_datの返り値はE_WBLKです、送信の数値を見るとercdはrercd=1です、ということは返り値で判断せずrercdでデータ数を判断できるということのように理解したほうが良さそうです。

  • t4_callbackですが受信から始めないといけないみたいで、最初に、tcp_snd_dat(をやってしまいますとその後telnet応答しなくなります

    tcp_rcv_datを先にしますと(telnetタミナル送信から)しますとその後やり取りができるようです、telnetの場合先にtcp_snd_datをやりたいのでこれをどうするか思案中です。

  • IKUZOさん

    シェルティです、こんにちは。

    下記回答します。

    > t4_callback内のTFN_TCP_ACP_CEP:は確立済みと思いまして、
    > tcp_snd_dat(cepid,をやってtelnetターミナル画面には文字が表示されます、
    > その後case TFN_TCP_SND_DAT:からlen=(int)tcp_rcv_dat(を実行しますと、
    > E_QOVR 、E_WBLK、E_WBLK等で受信に失敗します、アドバイスいただけませんか?

    →続きのレスで解決しておられますね。
     ご推察の通り、tcp_snd_dat()の後にbreak;が必要です。
     1回のコールバックで発行できるAPIは通信端点毎に1個だけです。
     なぜならば、このTCP/IPは同一通信端点に対しリクエストを
     キューイングできないからです。

    > tcp_snd_dat(cepid,をやってtelnetターミナル画面には文字が表示されます、
    > そこをbreak;しますとE_QOVR は出ません、後E_WBLKノンブロッキングコール受付ですがこれは何を意味するのか?

    →E_WBLKは「ノンブロッキングコールを受け付けた」という意味です。

    > E_WBLK len=-83 E_WBLK len=-83 これは正常なt4_callbackでもこれを返すようです、
    > 正常であればデータ数を返すとなっていますがtelnetの画面はエコーしているのですが、
    > tcp_rcv_datの返り値はE_WBLKです、送信の数値を見るとercdはrercd=1です、
    > ということは返り値で判断せずrercdでデータ数を判断できるということのように理解したほうが良さそうです。

    →ノンブロッキングコールの場合コールバック関数の第3引数のポインタの先に受信APIの実行結果(受信データ長)が入っています。
     APIをノンブロッキングコールした瞬間はデータ受信はしておらず、先に説明した通り、
     「ノンブロッキングコールを受け付けた」という意味のエラーコードE_WBLKが戻ってくるわけですね。
     少し注意が要るのが、TCP/IPはストリーム型通信といってストリームの終わりはアプリが判断しないと
     いけないことです。これはTCP/IPに10バイト送れと命令してもTCP/IPや通信経路上の通信機器がその場の都合で
     データを分割することがあるためです。このため、アプリは希望するデータ長、または、使用するプロトコルで
     規定された終了コードが来るまで受信データをバッファリングし、受信終了条件を満たすかどうかを
     受信コールバック毎に判定する必要があります。
     
    > t4_callbackですが受信から始めないといけないみたいで、最初に、
    > tcp_snd_dat(をやってしまいますとその後telnet応答しなくなります
    > tcp_rcv_datを先にしますと(telnetタミナル送信から)しますとその後やり取りができるようです、
    > telnetの場合先にtcp_snd_datをやりたいのでこれをどうするか思案中です。

    →t4_callback()内に以下switch-caseをbreak;付きで用意しましょう。
     これでmain()や他の通信端点と非同期に延々と通信を行う循環機構が完成します。
     この骨組みを維持すればあとは送信データを加工したり、受信データに応じた処理をしたり、
     自由に作れます。
     
     TFN_TCP_ACP_CEP:
       tcp_snd_dat(...);
       break;
     TFN_TCP_SND_DAT:
       tcp_rcv_dat(...);
       break;
     TFN_TCP_RCV_DAT:
       tcp_cls_cep(...);
       break;
     TFN_TCP_CLS_CEP:
       tcp_acp_cep(...);
       break;

     接続完了コールバック(TFN_TCP_ACP_CEP)→送信(tcp_snd_dat)→
     送信完了コールバック(TFN_TCP_SND_DAT)→受信(tcp_rcv_dat)→
     受信完了コールバック(TFN_TCP_RCV_DAT)→切断(tcp_cls_cep)→
     切断完了コールバック(TFN_TCP_CLS_CEP)→接続待ち(tcp_acp_cep)→
     <繰り返し>

     先に述べた受信のバッファリングは今後手当していってください。
     ウェブサーバのコードがこのあたりのバッファリングをどのようにしているか
     参考にできると思います。
     
    以上です

  • シェルティさん

    いつもお世話になります

    いろいろやりました、一つのことが課題でして

    tcp_snd_datと tcp_rcv_datはcallbackの中で使用可能ということで

    このことはtcp_snd_datのすぐ後にtcp_rcv_datする

    tcp_rcv_datのすぐ後にtcp_snd_datということで良くわかります

    tcp_snd_datで1Byte送信これを続けさまに発信するなどうまくいきます

    tcp_rcv_datの場合は相手方がデータが入らない場合は無理です

    ということでcallbackの中で全てを処理すれば

    telnetは即可能であると感じています

    ただtelnetを構成する場合、

    現在使用しているインタープリタに結合させたいと思っています

    telnetから入力したコマンドラインをインタープリタに送信して

    インタープリタから出力される文字列をtelnetに出力したいと思います

    この場合callback外の箇所からtelnet送信を起動しないといけません

    uIPの場合ですと、パケットがなくてもcallbackが常に一定間隔で呼ばれており

    その呼ばれたタイミングでデータがある場合はtelnetに出力できます

    t4ですとcallbackイベントがないと飛んできません

    外部からtelnet出力を起動できますでしょうか?

    インタープリタをcallback中に作りこめばできることはわかりますが、

    そうなるとソフト作成が大変なことになるので

    アドバイスでもいただけましたら、幸いです。

  • httpサーバーの方は画面が一応出ました、ファイルリストがブラウザーに表示されました、とりあえずご報告まで。

  • IKUZOさん

    こんにちは、シェルティです。

    なるほど、IKUZOさんが実現されたいことと合致するか少し不安ですが、
    callback処理外から送信することは可能です。

    callbackではtcp_rcv_dat()を呼び出しておいて、
    インタプリター側が送信したくなったら、インタプリター側で
    tcp_rcv_dat()をキャンセルして、キャンセル完了のcallbackで
    tcp_snd_dat()を呼び出し、tcp_snd_dat()の完了で再度tcp_rcv_dat()を
    呼び出す、というものです。

    キャンセルして送信している間も、TCP/IPライブラリ内部にある程度は
    (config_tcpudp.c のtcp_ccep構造体に設定した受信ウィンドウ分は)
    バッファリングされますので、キャンセル/送信中に
    受信データをこぼすこともありません。

    この動作を実現しているサンプルがありますので参考にしてみてください。

    https://www.renesas.com/mw/t4
      ->画面下のほうのTCP/IPサンプルプログラム
        ->画面左のほうのサンプルプログラム
          ->RXファミリ 組み込み用TCP/IP M3S-T4-Tinyを用いたサンプルプログラム Firmware Integration Technology
            ->解凍データの奥底にある「tcp_nonblocking_cancel_sample」フォルダのecho_srv.c

    また、ファイルリストがブラウザに表示されたとのことで、よかったです。       

    ネットワークはいろいろ難儀なところが多いので動き始めるとうれしさも大きいですね。

    以上です

  • シェルティさん

    いつもありがとうございます

    インタプリター側でtcp_rcv_dat()をキャンセルして、キャンセル完了のcallbackでtcp_snd_dat()を呼び出すとのことですが

    キャンセル完了のcallbackというのが良くわからなくて、

    サンプルではstrstrで<CRLF> checkをやってから送信しているので、この場所でコマンド解析というようなことですが

    「インタプリター側でtcp_rcv_dat()をキャンセルして」具体的にはどのようにできますか?

  • サンプルコードによると、受信データが1秒以内に受信できない場合はキャンセルを発令するという意味のように取れますが

    それで、受信のところtcp_rcv_dat(で停止することがないと受け取れますが、たぶんそうですよね、

    その考え方でやってみたいと思います、

    やってみましたがTFN_TCP_RCV_DAT:には1秒毎には来てくれませんね。

    やはり外部から「インタプリター側でtcp_rcv_dat()をキャンセルして」やるひつようがあるようです。

    シェルティさん、すみません、コードミスってました、今一度確認します。

    シェルティさん、解りましたTFN_TCP_RCV_DAT:が外部から送信する窓口ですね、この線でやってみます。

  • シェルティさん

    できました、ありがとうございました。

  • IKUZOさん

    こんにちは、シェルティです

    おお、見事にインタプリタの入出力がtelnetに繋がっていますね。
    telnetサーバの通信端点を増やせば、複数人同時ログインもできると思います。

    後発の方のために、IKUZOさんの疑問点(キャンセル動作)にも回答しておきたいと思います。

    ■前提条件
    (1)通常はmain()ループでプログラムカウンタが周回している
    (2)10ms周期タイマ割り込みまたは、Etherコントローラ割り込みを起点とし
     TCP/IPの処理が実行される。(必要に応じてTCP/IPからユーザにコールバックする)

    ■1通信端点での送信・受信同時動作(受信キャンセル動作の応用)
    (1)main()でまずはTCP接続の1回目の接続待ち受け(tcp_acp_cep())をノンブロッキングコール
    (2)TCP接続が完了するとコールバックが発生、イベントコードはTFN_TCP_ACP_CEPとなる。
    (3)TFN_TCP_ACP_CEPを伴ってコールバックが来たら、tcp_rcv_dat()を実行し
     受信予約(インタプリタへのコマンド待ち)
    (4)TCP受信が完了するとコールバックが発生、イベントコードはTFN_TCP_RCV_DATとなる。
    (5)TFN_TCP_RCV_DATを伴ってコールバックが来たら、tcp_rcv_dat()で
     指定した受信バッファポインタに受信データが入っているので
     これを取り出してインタプリタ側に送出、
     続いてtcp_rcv_dat()を実行し受信予約(次のインタプリタへのコマンド待ち)
    (6)main()ではインタプリタの制御を行っており、(5)で詰め込まれたキューを監視しているとして、
     キューに何かが入ってきたら、キューに登録されたコマンド処理を実行し、実行結果を
     送信バッファに書き込み、tcp_can_cep()を使用して、(5)の受信をキャンセルする
    (7)キャンセルが完了するとコールバックが発生、イベントコードはTFN_TCP_RCV_DATとなる。
    (8)TFN_TCP_RCV_DATとエラーコード(E_RLWAI)を伴ってコールバックが来たら、
     tcp_snd_dat()で(6)の送信バッファを指定して実行
    (9)TCP送信が完了するとコールバックが発生、イベントコードはTFN_TCP_SND_DATとなる。
    (10)TFN_TCP_SND_DATを伴ってコールバックが来たら、tcp_rcv_dat()で再度受信待ちする
     一見(6)~(10)の間に受信したデータは取りこぼしそうですが、TCP/IP内部の受信ウィンドウに
     自動的に保持されACKが返る(溢れた分もEtherコントローラに数KBは保持される模様、
     これも溢れて本当に受信データが破棄されてもTCPの再送制御で通信相手は
     もう一度同じデータを送ってくる)ので、受信データが消えてしまうことはありません。
     
    以上です

Reply
  • IKUZOさん

    こんにちは、シェルティです

    おお、見事にインタプリタの入出力がtelnetに繋がっていますね。
    telnetサーバの通信端点を増やせば、複数人同時ログインもできると思います。

    後発の方のために、IKUZOさんの疑問点(キャンセル動作)にも回答しておきたいと思います。

    ■前提条件
    (1)通常はmain()ループでプログラムカウンタが周回している
    (2)10ms周期タイマ割り込みまたは、Etherコントローラ割り込みを起点とし
     TCP/IPの処理が実行される。(必要に応じてTCP/IPからユーザにコールバックする)

    ■1通信端点での送信・受信同時動作(受信キャンセル動作の応用)
    (1)main()でまずはTCP接続の1回目の接続待ち受け(tcp_acp_cep())をノンブロッキングコール
    (2)TCP接続が完了するとコールバックが発生、イベントコードはTFN_TCP_ACP_CEPとなる。
    (3)TFN_TCP_ACP_CEPを伴ってコールバックが来たら、tcp_rcv_dat()を実行し
     受信予約(インタプリタへのコマンド待ち)
    (4)TCP受信が完了するとコールバックが発生、イベントコードはTFN_TCP_RCV_DATとなる。
    (5)TFN_TCP_RCV_DATを伴ってコールバックが来たら、tcp_rcv_dat()で
     指定した受信バッファポインタに受信データが入っているので
     これを取り出してインタプリタ側に送出、
     続いてtcp_rcv_dat()を実行し受信予約(次のインタプリタへのコマンド待ち)
    (6)main()ではインタプリタの制御を行っており、(5)で詰め込まれたキューを監視しているとして、
     キューに何かが入ってきたら、キューに登録されたコマンド処理を実行し、実行結果を
     送信バッファに書き込み、tcp_can_cep()を使用して、(5)の受信をキャンセルする
    (7)キャンセルが完了するとコールバックが発生、イベントコードはTFN_TCP_RCV_DATとなる。
    (8)TFN_TCP_RCV_DATとエラーコード(E_RLWAI)を伴ってコールバックが来たら、
     tcp_snd_dat()で(6)の送信バッファを指定して実行
    (9)TCP送信が完了するとコールバックが発生、イベントコードはTFN_TCP_SND_DATとなる。
    (10)TFN_TCP_SND_DATを伴ってコールバックが来たら、tcp_rcv_dat()で再度受信待ちする
     一見(6)~(10)の間に受信したデータは取りこぼしそうですが、TCP/IP内部の受信ウィンドウに
     自動的に保持されACKが返る(溢れた分もEtherコントローラに数KBは保持される模様、
     これも溢れて本当に受信データが破棄されてもTCPの再送制御で通信相手は
     もう一度同じデータを送ってくる)ので、受信データが消えてしまうことはありません。
     
    以上です

Children
No Data