ホームページ >システムチュートリアル >Linux >組み込み Linux シリーズ パート 8: ネットワーク ポートの操作
一部の比較的高性能のマイクロコントローラーにはイーサネット インターフェイスが搭載されていることが多く、ネットワーク ポートはネットワーク プロトコル スタックの動作に関与するため、MCU では比較的複雑な周辺機器と見なされます。通常、ネットワーク プロトコル スタックはリアルタイム オペレーティング システム (RTOS) で実行されるため、一般のマイクロコントローラー開発者がネットワーク ポートを使用するのは困難です。 Linux では、ネットワーク ポートが頻繁に使用されるインターフェイスです。これは、Linux には成熟した完全なネットワーク通信プロトコル スタックがあり、基礎となるドライバーが製造元から提供されているため、比較的使いやすいためです。この記事では、Linux でのネットワーク ポートの使用について簡単にまとめ、皆様のお役に立てれば幸いです。
2.1.ハードウェア
\1) インターネット上のサードパーティ製 NUC972 開発ボード:
購入に興味のある友達は、淘宝網ストアにアクセスして購入できます:
https://s.click.taobao.com/X8mza8w
この記事では主にボードのネットワーク ポートについて説明します。
\2) USB - RS232 ケーブル 1 本、ネットワーク ケーブル 1 本、電源コード 1 本、Micro USB ケーブル 1 本
2.2.ソフトウェア
\1) 前回の記事から引き続き Uboot とカーネルを使用します。
\2) Buildroot を使用して Rootfs を再生成します。NUC972 Buildroot のダウンロード アドレスは、https://github.com/OpenNuvoton/NUC970_Buildroot です。ここで Buildroot を使用して Rootfs を再生成する理由は、Buildroot ツールを使用して追加するためです。この記事で必要な ssh 機能などは非常に便利で、手動で移植するよりもはるかに簡単です。よくわからないかもしれませんが、興味のある方はオンラインチュートリアルを参照して、dropbear を手動で移植して ssh 機能を実装すると、二つの方法を比較することでより理解が深まります。
3) 前回の記事でもクロスツールチェーン arm_linux_4.8.tar.gz が使われていますが、このツールチェーンも Buildroot によって生成されるものと思われます。
詳細な手順はここでは紹介しません。以前に投稿した記事「Buildroot を使用して I.MX6 のルート ファイル システムを作成する」を参照してください。ここではいくつかの点を説明します:
\1) 公式 Buildroot をダウンロードした後、対応するディレクトリに入り、次の手順を実行します。
nuvoton_nuc972_defconfigを作成する###作る###
最初のコンパイル時間は少し長くなりますが、オンラインで大量のファイルがダウンロードされるため、皆さんは辛抱強く待つ必要があります。2) クロスツールチェーンの問題に関しては、Buildroot ツールチェーンが使用されますが、この Buildroot を選択するとツールチェーンが最初から作成されます。コンパイルが完了すると、output/host/ ディレクトリに新しく作成されたツールチェーンが作成されていることがわかりますが、個人的には公式のツールチェーンはこれから来ているのではないかと推測しています。
3) Dropbear はデフォルト設定では選択されていません。自分で選択できます。
4) コンパイルが完了すると、生成された rootfs は、output/images/rootfs.tar になります。これを NUC972 ボードにプログラムできるようにするには、まず解凍してから、生成するために使用する必要があります。 mkyaffs2 を介した .img 形式ファイル。
\5) Uboot、Kernel、Rootfs などをボードに再ダウンロードし、dropbear とネットワーク ポートを設定して使用します。passwd コマンドを使用して root ユーザーのパスワードを設定します。パスワードは誰でも使用できないようにするためのもので、システムに直接ログインできます。ネットワーク ケーブルをボードとコンピュータに接続し、コンピュータの IP を 192.168.0.50 に設定し、シリアル ポートのログイン インターフェイスに ifconfig eth0 192.168.0.100 と入力します。起動後にネットワークが利用可能であることを確認するには、これを追加します。 /etc/init.d/rcS ファイルの終わりへの文。これにより、後でシリアル ポートを接続する必要がなく、ネットワーク ポートだけを使用して Linux システムにログインでき、同時にファイルをボードに転送でき、ファイルをコピーする必要がなくなりました。以前と同様に U ディスクを使用すると、効率が大幅に向上します。
4.1.関連コマンド
ネットワークに関連する一般的なコマンドには、先ほどネットワーク カードを構成するときに使用した ifconfig や、ネットワークにアクセスできるかどうかをテストするために使用した ping などがあります。その他には、後で紹介する Route、ethtool なども含まれます。実際に使用するとき。
4.2.C言語例
最も一般的に使用されるのは udp 通信と tcp 通信です。これらの基本的な概要についてはここでは詳しく説明しません。よくわからない学生は、Baidu で 2 つの記事を読むだけで済みます。ここでは UDP を例として、非常に古典的な例を見てみましょう。
実装する機能は次のとおりです:
\1) クライアントは手動で入力されたデータを受け取ります
\2) クライアントは上記のデータをサーバー
に送信します\3) サーバーは受信したデータをクライアントに送り返します
コードを直接アップロードします:
リーリーまず、gcc を使用して Ubuntu でコンパイルします。arm-linux-gcc をクロスコンパイルしていないことに注意してください。最初に PC 上でサーバーを実行し、次にクライアントを実行します。効果がわかります。これにより、上記のことが実現されます。私たちが欲しい機能について言及しました。
上記のコードをよく読んでください。説明が必要な点がいくつかあります:
1) UDP は TCP とは異なり、リクエストの接続と受け付けのプロセスがありません。したがって、実際にはサーバーとクライアントの間に明確な区別はありません。上記のサーバーとクライアントの名前は説明の便宜上のものです。私は次のように理解しています: send first クライアントが最初にデータ (リクエスト データ) を受信し、サーバーが最初にデータを受信してからデータを送信します。
\2) バインド関数がサーバーの例では呼び出されているのに、クライアントの例では呼び出されないことに気づきましたか?その理由は何ですか?その理由は、サーバーが動作するにはまずデータを受信する必要があるためで、ポートがバインドされていない場合、データをどこで受信するかを知る方法がありません。クライアントがバインドする必要がない理由は、クライアントが最初に送信し、送信後すぐに送信ポートでデータを受信できるためです。
\3) 実際に仕事をしていると、私を含めて多くの人がポートでよく混乱していることがわかりました。ここでまとめると、UDPを受信する場合、このポートからデータを受信する前にポート番号(このポートの自装置のポート)をバインドする必要があり、データ受信後に相手のIPアドレスと送信ポート番号を取得することになります。 。送信時に相手のIPとポートを指定するだけで、本機の送信ポートはランダムに割り当てられるため、ポートをバインドする必要はありません。
ポートをバインドせずに送信されたポートがランダムに割り当てられていることを確認するために、別の小さな実験を行うことができます。クライアントをオフにして、再度開きます。前後に 2 回出力されたポート情報を確認します。 2回のポート番号が異なります。を参照してください。
\4) ソケットを作成するためにソケットを呼び出す場合、関数の 2 番目のパラメーターには SOCK_DGRAM が渡され、UDP プロトコルが使用されることを示します。 TCP の場合、このパラメータは SOCK_STREAM.
です。\5) addr_local メンバー変数に値を割り当てるときに、htonl(INADDR_ANY) を使用して IP アドレスを自動的に取得します。
INADDR_ANY を使用する利点は、ソフトウェアが他のホストで実行される場合、またはホストの IP アドレスが変更される場合に、ソース コードを変更して再コンパイルする必要がなく、ソフトウェアの起動時に手動で入力する必要がないことです。また、1 つのホストに複数の IP アドレスが割り当てられている場合、ポート番号が一致していれば、異なる IP アドレスからデータを受信できます。
\6) クライアントで送信するときに設定した IP は 127.0.0.1 で、これは特別な IP アドレスです。ifconfig を使用して確認できます。Ubuntu およびボード上で確認できます:
インターネットで英語の説明を見つけました:
127.0.0.1 は、「ローカルホスト」とも呼ばれるループバック インターネット プロトコル (IP) アドレスです。このアドレスは、エンドユーザーが使用している同じマシンまたはコンピュータへの IP 接続を確立するために使用されます。単純に理解すると、ローカル マシン自体を表すことになります。
次のステップでは、クライアント コードをクロスコンパイルし、実行するためにボードに配置します。2 つの微妙な変更を加える必要があります:
最初の addr_dest.sin_addr.s_addr=inet_addr(“127.0.0.1”); は次のように変更されます:
addr_dest.sin_addr.s_addr=inet_addr(“192.168.0.50”);
192.168.0.50 は PC の IP アドレスです。
2 番目の while(1) のこれら 3 つの文
文字バフ[1024] = {0x00};
printf(“文字列を入力してください: “);
fgets(buff,1024,stdin);
は次のように変更されました:
char buff[1024] = “TopSemic フレンズの皆さん、こんにちは!”;
//printf(“文字列を入力してください: “);
//fgets(buff,1024,stdin);
目的は、ユーザーによる情報の入力を待たずに、クライアントが自動的にデータを送受信できるようにすることです。
Ubuntu では、scp コマンドを使用して、ファイルをボードの /opt ディレクトリに直接配置します
scp udp_client root@192.168.0.100:/opt
さらに、Ubuntu で ssh コマンドを使用して Linux システムに直接ログインします。
ssh root@192.168.0.100:/opt終了するには、「exit」と入力するだけで、Ubuntu コマンド ライン ウィンドウに戻ることができます。
このように、Ubuntu ではボードへのログインとボードへのファイルのアップロードのプロセスが簡単に操作できるようになり、以前の Windows シリアル ポート ログインや U ディスク ファイル転送と比較して、はるかに便利です。
Ubuntu で udp_server を大喜びで実行しました。Ubuntu で ssh でボードにログインし、udp_client を実行しました。正常に実行されると思いましたが、予期せぬ事態が発生しました。実際、結果はまったく出力されませんでした。
しかし明らかに、仮想マシン Ubuntu はボードにログインでき、正常に ping でき、ボードは IP 192.168.0.50 にも ping できるのに、なぜ udp が通過できないのでしょうか?その後、しばらく考えた後、問題は解決されました。解決策は次のとおりです:
仮想マシンのデフォルトのネットワーク設定モードは、以下に示す NAT モードです。
これを次の図に示すブリッジ モードに変更します。
次に、ネットワーク ケーブルを取り外して再接続し、Ubuntu 仮想マシンのネットワーク構成を変更します。 仮想マシンの有線接続を、手動で構成された固定 IP、192.168.0.xx ネットワーク セグメントに変更します (Windows およびボード IP と競合しないようにしてください)。 ifconfig を使用して、設定が成功したかどうかを確認できます この時点で、ボードにログインして 192.168.0.80 に ping を実行すると、ping は成功します。以前、192.168.0.50 に ping を実行しましたが、これは Windows ホストの IP であり、接続できても仮想マシンに接続できるとは限りません。
最後に、上記のコードの IP を変更します。
addr_dest.sin_addr.s_addr=inet_addr(“192.168.0.80”);
再コンパイル、ダウンロードして一度実行すると、正常に動作します。
追加点: ボードをデバッグする場合、Windows のネットワーク デバッグ アシスタントがよく使用されますが、このツールを使用する場合は、プロトコルの種類、ローカル ホスト アドレス、ローカル ホスト ポート、およびリモート ホストを正しく設定するだけで済みます。 、それを送信して表示します。たとえば、次のように、Windows のネットワーク デバッグ アシスタントを有効にして、クライアントと仮想マシン サーバー間の通信をシミュレートすることもできます。
5 実践作業の概要
このモデルは、上記のサーバー モデルとクライアント モデルに非常に似ており、実装する必要があるのはクライアントです。つまり、最初に sendto 関数を呼び出して送信し、次に recvfrom 関数を呼び出して受信します。通常の状況では、このようなプログラムを作成することに問題はありませんが、実際には、外部デバイスの電源が突然オフになってからオンになったり、通常の動作中に再起動したりするなど、多くの異常な状況を考慮する必要があります (ただし、CPU デバイスの電源は入っていません)。オフ) これにより、どのような問題が発生しますか?外部デバイスの電源がオフになっているため、データを受信できないため、recvfrom 関数はブロックされます。外部デバイスの電源が再投入されて初期化された後でも、データを受信していないため、応答データが返されず、recvfrom 関数が動かないでください。
このようなコードがサイトに公開されると、上記のような状況がサイトで発生するのは通常のことであるため、隠れた大きな危険がもたらされます。たとえば、CPU デバイスの電源が最初にオンになり、外部デバイスの電源がオンになった場合です。その後、上記の問題も発生します。私の以前のプロジェクトでは、この問題が原因で顧客から製品の問題に関する苦情が寄せられましたが、顧客は、通信に失敗した場合、デバイスの電源を再度オフにすることによってのみ問題を解決できることに気づきました。
解决上述问题的办法也很简单,可以设置一个超时,使用setsockopt函数,让接收函数在超时时间内没有接收到数据时就返回就行了。返回后再接着重头发送数据即可,框架如下:
/* 设置阻塞超时 */
struct timeval timeout = {3, 0}; // 设置3s超时
if(setsockopt(sockfd,SOL_SOCKET,SO_RCVTIMEO,(char *)&timeout,sizeof(struct timeval))
{
<code style="display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px">printf("time out setting failed "); </code>
}
.
.
.
/* 数据阻塞接收 */
int receivePacketLen = recvfrom(sockfd,buffer,sizeof(buffer),0,(struct sockaddr*)& addr_sender,&addrLen);
if(receivePacketLen != -1)
{
//接收到数据
…
}
else if (errno == EAGAIN) //阻塞接收超时
{
<code style="display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px">printf("udp receive timeout! "); return -1; </code>
}
为了大家更直观的感受这个问题,我们在上面实验的基础上来模拟这个场景,我们先运行upd_client,后运行udp_server,大家看下现象,结果自然是没有数据输出。
道理不难想明白,client程序运行后,先发送了数据,然后就阻塞在读那里不动了。我们把程序简单修改下:
// Max Recv block timeout in second #define gMaxRecvBlockTimeout 3 … … … // Set recv timeout struct timeval timeout = {gMaxRecvBlockTimeout, 0}; if(setsockopt(sockfd,SOL_SOCKET,SO_RCVTIMEO,(char *)&timeout,sizeof(struct timeval)) printf("time out setting failed "); } //不断获取用户输入并发送给服务器,然后接受服务器数据 while(1) { char buff[1024] = "Hello TopSemic Friends!"; //printf("Please Input a string: "); //fgets(buff,1024,stdin); sendto(sockfd, buff, strlen(buff), 0, (struct sockaddr*)&addr_dest, sizeof(struct sockaddr_in)); recvlen = recvfrom(sockfd,buff,sizeof(buff),0,(struct sockaddr *)&addr_sender,(socklen_t *)&nlen); if(recvlen > 0) { buff[recvlen] = 0x00; printf("Message form server: %s ", buff); printf("sender ip:%s port:%d ",inet_ntoa(addr_sender.sin_addr),ntohs(addr_sender.sin_port)); } else if(errno == EAGAIN) // 阻塞接收超时 { printf("udp receive timeout! "); } printf("************************************** "); } close(sockfd); return 0;
这时我们先运行client,
打印如上,然后再运行Server,就可以正常工作了,不会再出现上述问题。
以上が組み込み Linux シリーズ パート 8: ネットワーク ポートの操作の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。