M5Stack同士でWiFi, UDPによる双方向リアルタイム同時通信する実験

M5Stackを2台使ってWiFi,,UDP双方向送受信通信 M5Stack

UDPで双方向送受信するには? ポート番号とは?

さて、以上のことから、M5StackやESP32で双方向同時通信するならば、UDPが手軽で良いのではないかと思いますよね。
ただ、気を付けなければいけないのは、割り当てたポート番号相手には一方向しか通信できないということです。たぶん・・・。(間違えていたらスイマセン)

(間違えていました。コメント投稿でteruさんが指摘してくださいました。
一番下のコメント投稿欄を参照してください。)

これはどういうことでしょうか?

私は工事担任者の資格試験を受けた時に勉強しましたが、もうすっかり忘れてしまいました。
ネットワーク技術のポート番号って、いまいち理解し辛いんですよね。

そういえば、今まで私はパソコンやスマホ等のネットワーク設定でポート番号は一つしか設定したことがありません。

でも、ブラウザでWebページを見る時に、双方向通信できますよね。
それに、Arduino core ESP32 でTCPプログラミングを組む時でも、ポート番号は80とか443しか指定しませんよね。

ですから、今まで何となくですが、1つのポートで双方向通信ができるものと思い込んでいました。
その思い込みで私はArduino core ESP32 のUDP通信プログラムを組んで、ポート番号を指定して双方向通信を実現しようとしてしまい、途中で分けわからなくなって、つまずきました。

ならば、そもそもポート番号とは何ぞや?
という話になって来ますが、ネット上には沢山の情報があるので、詳細は割愛させていただきますが、要するに、IPアドレスが相手の住所を示すとすれば、ポート番号は部屋番号だと思えと、どこのサイトにも書いてあります。

TCPの場合、例えばポートを443と指定してWebページを見る場合、大抵はルーターを介してインターネットに接続していると思います。
すると、インターネットの相手先サーバーはポート443を開放して、クライアント側からリクエストデータが送信されて来るのを待ち受けています。
では、サーバー側からデータが送信される場合には、ルーターのWAN側の空いているポート番号を自動で割り当て書き換えて送られてくるそうです。たぶん・・・。
この辺は私の知識も曖昧なので、正確なことはネットで調べてみて下さい。

つまり、結局のところ、ポート番号とは、その端末やデバイス側が開放してデータが送信されてくるのを待ち受けるものということのようです。

考えてみれば、送られてくるパケットには、IPアドレスとポート番号データが含まれているわけで、IPアドレスによってサーバーまでデータがたどり着いたとすると、あとはサーバー側のソフトウェアでパケットデータのヘッダ部分からポート番号を抽出して、サーバー側が開放した番号と一致すればデータを受信するという流れならば納得いきます。
そういえば、「ポート開放」という言葉自体が普通の人には理解を妨げる紛らわしい言葉ですね。

もっと簡単に言うと、「ポート開放」とは、その番号がついたデータを受信OKしますよ!ということです。当然、一方向の受信のみ受け付けますよ! ということです。
マイコンをやっている人達にはこっちが分かりやすいかもね。

ということで、デバイス側の一つのポートには一方向しかデータは送れないという根拠がわかるかと思います。間違えていたらゴメンナサイ。
(間違えていました。一番下のコメント投稿でteruさんが指摘してくださいました。)

ところで、ユーザーがネットワーク上で自由に使えるポート番号は49152~65535番です。

【TCP,UDPポート】
0~1023番 特殊ポート
(well-known port ウェルノウンポート)
1024~49151番 登録済みポート
49152~65535番 ユーザーが自由に割り当てられる

ポート番号の詳細はWikipediaの以下のリンクを参照してください。

TCPやUDPにおけるポート番号の一覧

では、一方向しか送れないのなら、双方向通信するにはどうすればいい?
次の節では、M5Stack同士でUDP双方向通信をするにはどうするかを説明します。

WiFiUDPライブラリによるUDP双方向通信について

では、Arduino core for the ESP32内のWiFiUDPライブラリによるUDP双方向通信の概要を私個人的に解釈した方法で説明します。

例として、2台のM5Stack同士でUDP通信する場合を考えます。
一方をSoftAPモードで起動し、アクセスポイントおよびルーターにします。
もう一方をSTAモードで起動して、SoftAPの192.168.4.1に接続した状態にします。

すると、STAモードのIPアドレスは192.168.4.2となります。
SoftAPモード側のM5Stackは、Arduino core ESP32のWiFiUDPライブラリのbegin関数を使って、部屋番号にあたるポート番号を55555と設定して開放し、待ち受け状態にします。
begin関数については、今まで何となく使っていましたが、自デバイスのポートを開放して待ち受け設定する関数だったようですね。(たぶん・・・。)
そこを私は今まで勘違いしていました。

同じく、STAモード側のM5Stackもbegin関数でポート番号を55556と割り当てて開放し、そこへデータが送信されてくるのを待ちます。

対して、データを送信する場合には、WiFiUDPライブラリのbeginPacket関数を使って、宛先のIPアドレスとポート番号を指定して、write関数で実データを送信すれば良いわけです。

すると、データパケットの先頭部分には、IPアドレスとポート番号が含まるヘッダ部分と、その後に実データを含めて全体として1パケットとなって送信されます。
下図を参照してください。

この図を見れば、前節で説明したように各々のM5Stackのポート番号開放(受信許可)状態にして、各々そのポートに向かってデータを送信すれば双方向通信可能だということがわかると思います。

実は、双方のポート番号は同じ番号でもOKです。

そりゃ、そうですよね。
それぞれのM5Stackは独立しているので、同じポート番号を割り当てたとしても競合しません。
それぞれのM5Stackは送信されてきたパケットにあるポート番号が55555なら受信OKと設定しているだけですから、双方同じ番号でも良いわけです。

ただ、同じポート番号にしてしまうと、自分でプログラミングする時に自分自身が勘違いして一つのポートで送受信していると思い込んでしまうので、ここではポート番号を55555と55556と分けています。

ところで、受信ポート番号は良いとして、送信ポート番号は???

・・・すいません・・・。
ここでは考えない事にします。

なぜなら、WiFiUDPライブラリでは送信ポートを指定することは無いからです。
本当はUDPの1パケットのヘッダ部分には送信ポート番号と相手先受信ポート番号が含まれているはずです。
ライブラリを深くたどって解読すれば解るかも知れませんが、正直、送信ポート番号については良く分かりませんでした。
受信ポート番号だけ考えれば双方向通信できるので、それで良いのかと思います。
(※コメント投稿でteruさんから指摘していただき、送信ポート番号について判明しました。
一番下のコメント投稿欄を参照してください。)

さて、ここで注意していただきたいのは、Arduino core ESP32 のWiFiUDPライブラリで送信できる1パケット内の実データの最大バイト数は1460 byteです。

これは、WiFiUdp.cppのソースコードにベタ書きされています。
それ以上のデータはパケットを意図的に分割して送ることになります。

ということは、画像データなどの1460byte以上の多量のデータを連続して送りたい場合、パケットを分割して送ることになります。
つまり、パケット毎にヘッダ情報を含めなければなりません。
このことは、TCPも同様です。
よって、TCPに比べてUDPの方がヘッダ部分のデータが少ない為にリアルタイム動画配信には有利だということが分かりますね。

UDP通信はブロードキャストにすると速度が極端に遅くなるので注意

Arduino core for the ESP32 のWiFiUDPライブラリには予めサンプルスケッチが用意されています。
WiFiUDPClientというサンプルスケッチでは、IPアドレスが
192.168.0.255
というブロードキャストアドレスを使った例で書かれています。

私はこれを真に受けて、動画の転送実験を何度もやりました。
でも、どうしても速度が上がらず、パケットロスも多かったのです。
これでは動画ストリーミングなんて無理だなと思いました。

そこで、IPアドレスをユニキャスト(例えば192.168.0.13等)とすると、何と、動画転送がメチャメチャ速くなるではありませんか!

そこで、ネットでいろいろ調べると、ブロードキャストのような全デバイスにデータを転送する設定には、予めルーター等で転送速度を遅くしているとのこと。
ブロードキャストアドレスの速度を低下させることによって、ブロードキャストよりも重要なユニキャストのアドレス転送の速度を上げることができるとのことだそうです。

なるほど!!!

そんなことすっかり忘れていました。
ネットワークの知識って、知らないと意外と損をすることが多いもんだなぁと改めて実感しましたね。

さて、では次の節からいよいよ Arduino IDE でプログラミングしてみます。

Arduino core for the ESP32 のインストール

Arduino IDE はver 1.8.10 で動作確認しています。
Arduino core for the ESP32 は stable 1.0.4 で動作確認しています。
Arduino core for the ESP32 のインストール方法は以下の記事を参照してください。

Arduino core for the ESP32 のインストール方法

M5Stackライブラリのインストール

今回は、自作ライブラリではなく、M5Stackの多くのユーザーが使用していて汎用性が高いM5Stack社純正ライブラリを使います。
そのインストール方法は以下の記事を参照してください。
ver 0.2.9 で動作確認しています。

M5Stack ライブラリインストール方法 ( Arduino IDE 用)

では、いよいよ次の節では実際にプログラミング例を紹介してみます。

コメント

  1. teru より:

    「デバイス側の一つのポートには一方向しかデータは送れない」ですが、普通に間違いですね。
    この記事のUDP通信サンプルの場合、 udp.begin(myIP, my_server_udp_port); でソケットにポートを割り当て、同じ udp インスタンスを使用してパケット受信とパケット送信をしているので、「1つのポートで送受信」を行っています。
    パケットには宛先IPアドレス・ポートだけではなく、送信元IPアドレス・ポートが含まれており、通常アプリケーションから取得可能です。
    WiFiUdpのソースコードを読んでみたところ、 udp.parsePacket() でデータを受信した後に、 udp.remoteIP()、 udp.remotePort() で取得できるようですので、確認してみてください。
    udp.beginPacket(to_udp_address, to_udp_port) を呼び出すと、 remoteIP、remotePort の値は上書きされてしまうようですので、出力するタイミングにはご注意ください。
    下記のようなコードで、別の WiFiUdp インスタンスを使用する場合と比べると、違いが分かりやすいと思います。

    WiFiUdp udp_send;
    udp_send.beginPacket(to_udp_address, to_udp_port);
    udp_send.write((uint8_t)send_position);
    udp_send.write(send_direction);
    udp_send.write(send_color_num);
    udp_send.endPacket();

    Webページを取得するのに、相手先IPアドレス・ポートを指定して自分のIPアドレス・ポートを指定しなくてよいのは、送信元IPアドレス・ポートは、OSが自動的に割り当てるためです。

    WiFiUdp がどこで送受信のIPアドレスとポートを割り当てているかというと…… bind 関数の呼び出しがそれになります。
    bind をせずにいきなり sendto すると、その時にOSが自動的にIPアドレスとポートを選択して、sendtoに指定したソケットに割り当てます。

    * 「OSが~」と書いていますが、これは普通のPCを念頭においていいます。ESP32の場合は事情が異なる気がします。
    * WiFiUdpの動作に付いては、ソースコード見て判断した内容になります。Arduinoは使用していないので実際に確認していない点、ご容赦ください。
    * WiFiUdpのソースコードは https://github.com/espressif/arduino-esp32/blob/master/libraries/WiFi/src/WiFiUdp.cpp を参照しました。使用しているライブラリが違っていたらごめんなさい。
    * 「ポート開放」ってファイアウォールの話と混ざってませんか……?

    • mgo-tec mgo-tec より:

      teruさん

      先日別記事でご指摘いただいたのに、新たに記事をご覧いただいた上にご指摘いただき、本当にありがとうございます。
      当方で検証する時間が取れなくて、お返事遅くなり申し訳ございません。

      結論は、teruさんのおっしゃる通りでした。
      シリアルモニターでremotePort関数出力をしてもイマイチ良く分からないので、Windows PC のソフトWiresharkでパケットを解析してみました。
      udp.begin関数で、ポートを55555に設定した場合、受信ポートは55555ですが、送信ポート番号は下図の様になっていました。

      Wireshark解析

      これによると、送信ポート番号も受信ポートと同様に55555になっていることが判明しました。
      おかげ様で、teruさんのご指摘が無かったら、Wiresharkで調べようという意欲が働かず、曖昧なまま終わっていたと思います。
      とっても勉強になりました!!!

      今、次にアップする記事の編集に追われているので、それが済み次第徐々にこの記事も修正していく予定です。
      こんなド素人ブログを読んでいただき、そして適切なコメントをしていただき、感謝感謝感謝です。
      ありがとうございました。
      m(_ _)m

  2. Crane より:

    参考になります。
    1対多の場合の方法をが知りたいのですが、やられていますか。

    • mgo-tec mgo-tec より:

      Craneさん

      随分古い記事ですが、ご覧いただきありがとうございます。
      1対多については、この記事で書いてあるようにブロードキャストアドレスを使えば良いと思います。
      ただ、ブロードキャストアドレスを使うと、ルータの設定によって極端に速度が遅くなる場合があるようです。
      現に私の場合は遅く過ぎて使い物にならなかった記憶があります。

      • Crane より:

        ブロードキャストの場合、複数相手に同じデータを送信すると思うのですが、送信元が複数あり受信先は1つとして、送信されてきたデータを1箇所で集めることが出来たらと思った次第です。

        • mgo-tec mgo-tec より:

          Craneさん

          なるほど、そういうことでしたか。
          そういうのはUDPではやったことがないので残念ながら私にはわかりません。
          ただ、その場合は動画ストリーミングの様な重いデータではなく、短いデータ送信ならば、UDPではなくTCPでM5Stackサーバーを作れば、複数端末から送信でも受信できるかと思います。要するにM5Stackでホームページを作るようなものです。
          ただ、私は今は多忙でM5Stackを触ることもプログラミングすることもできない状態なので、あくまで想像でしかお答えできませんが…。

          • RUNA より:

            よく参考にさせていただいています。
            すみませんお手数ですが、この「UDPによる双方向リアルタイム同時通信プログラム」でひとつ教えてください。

            M5.begin() を setup()ではなく、別タスクで実行されている理由を教えていただけませんでしょうか。

            よろしくお願いします。

          • mgo-tec mgo-tec より:

            RUNAさん

            記事をご覧いただき、ありがとうございます。

            この記事は2019年に書いた古いもので、私自身は今となってはM5Stackをまったく触っておりません。
            そんなわけで、すっかり忘れてしまいました。

            ですが、久々にソースコード見てみると、何となく思い出しました。

            loop関数でUDPデータの送受信を行っていて、別タスクではLCD(液晶ディスプレイ)への描画をしています。
            LCDの描画はかなりCPUを消費するので、同じタスクに置いてしまうとUDP送受信している間はLCD描画がストップしてしまいます。
            すると、動画がカクカクした状態になって、スムースに動かなくなるためです。
            よって、UDP送受信とLCD描画は別タスクにして、同時進行させています。
            これはデュアルコアのESP32だから可能なのです。

タイトルとURLをコピーしました