Heim >System-Tutorial >LINUX >Embedded Linux-Serie Teil 8: Betreiben von Netzwerkports
Einige relativ leistungsstarke Mikrocontroller verfügen häufig über Ethernet-Schnittstellen. Der Netzwerkport gilt in MCUs als relativ komplexes Peripheriegerät, da er den Betrieb des Netzwerkprotokollstapels umfasst. Normalerweise wird der Netzwerkprotokollstapel in einem Echtzeitbetriebssystem (RTOS) ausgeführt, sodass es für normale Mikrocontroller-Entwickler schwierig ist, den Netzwerkport zu verwenden. Unter Linux ist der Netzwerkport eine häufig verwendete Schnittstelle, da Linux über einen ausgereiften und vollständigen Netzwerkkommunikationsprotokollstapel verfügt und der zugrunde liegende Treiber vom Hersteller bereitgestellt wurde, sodass die Verwendung relativ bequem ist. Dieser Artikel fasst kurz die Verwendung von Netzwerkports unter Linux zusammen und hofft, für alle hilfreich zu sein.
2.1.Hardware
1) NUC972-Entwicklungsboard, hergestellt von einem Drittanbieter im Internet:
Freunde, die am Kauf interessiert sind, können zum Einkaufen in ihren Taobao-Laden gehen:
https://s.click.taobao.com/X8mza8w
Dieser Artikel befasst sich hauptsächlich mit dem Netzwerkanschluss des Boards.
2) 1 USB-zu-RS232-Kabel, 1 Netzwerkkabel, 1 Netzkabel, 1 Micro-USB-Kabel
2.2.Software
1) Wir verwenden weiterhin Uboot und Kernel aus dem vorherigen Artikel.
2) Wir verwenden Buildroot, um Rootfs neu zu generieren. Die Download-Adresse von NUC972 Buildroot lautet: https://github.com/OpenNuvoton/NUC970_Buildroot. Der Grund, warum wir Buildroot verwenden, um Rootfs neu zu generieren, besteht darin, das Buildroot-Tool zu verwenden, um das hinzuzufügen, was wir wollen, wie zum Beispiel Die SSH-Funktion, die wir in diesem Artikel benötigen, ist sehr praktisch und viel einfacher als die manuelle Transplantation. Vielleicht verstehen Sie es nicht. Wenn Sie interessiert sind, können Sie sich das Online-Tutorial zur manuellen Transplantation von Dropbear ansehen, um die SSH-Funktion zu implementieren.
3) Die Cross-Tool-Kette arm_linux_4.8.tar.gz wird im vorherigen Artikel noch verwendet. Ich vermute, dass diese Tool-Kette auch von Buildroot generiert wird.
Die detaillierten Schritte werden hier nicht mehr vorgestellt. Sie können auf einen Artikel verweisen, den ich zuvor gepostet habe: „Verwenden von Buildroot zum Erstellen eines Root-Dateisystems für I.MX6“. Hier sind einige Punkte zur Erläuterung:
1) Nachdem Sie das offizielle Buildroot heruntergeladen haben, geben Sie das entsprechende Verzeichnis ein und führen Sie die folgenden Anweisungen aus:
make nuvoton_nuc972_defconfig
machen
Die erste Kompilierungszeit wird etwas lang sein, jeder muss geduldig sein, da viele Dateien online heruntergeladen werden.
2) In Bezug auf das Problem der Cross-Toolchain wird die Buildroot-Toolchain verwendet. Durch Auswahl dieses Buildroot wird die Toolchain von Grund auf erstellt. Nachdem die Kompilierung abgeschlossen ist, können Sie sehen, dass sich im Verzeichnis „output/host/“ eine neu erstellte Toolkette befindet. Ich persönlich vermute, dass die offizielle Toolkette daraus stammt.
3) Dropbear ist in der Standardkonfiguration nicht ausgewählt, Sie können es selbst auswählen.
4) Nach Abschluss der Kompilierung wird das generierte Rootfs als Ausgabe/images/rootfs.tar ausgegeben. Um es in das NUC972-Board brennen zu können, muss es zuerst dekomprimiert werden und dann mkyaffs2 zum Generieren verwenden. img-Formatdatei.
5) Laden Sie Uboot, Kernel, Rootfs usw. erneut auf das Board herunter, konfigurieren Sie Dropbear und den Netzwerkport und verwenden Sie dann den Befehl passwd, um ein Passwort für den Root-Benutzer festzulegen Es kann verhindern, dass sich jemand direkt im System anmeldet.
Verbinden Sie das Netzwerkkabel mit der Karte und dem Computer, stellen Sie die Computer-IP auf 192.168.0.50 ein und geben Sie ifconfig eth0 192.168.0.100 in die serielle Anmeldeschnittstelle ein. Um sicherzustellen, dass das Netzwerk nach dem Booten verfügbar ist, fügen Sie diesen Satz zu / hinzu. etc/init.d/ rcS Ende der Datei. Auf diese Weise müssen wir den seriellen Port nicht später nur über den Netzwerkport anmelden. Gleichzeitig können wir keine Dateien kopieren sie über die U-Disk wie zuvor, und die Effizienz wird erheblich verbessert.
4.1.Verwandte Befehle
Zu den allgemeinen Befehlen im Zusammenhang mit dem Netzwerk gehören ifconfig, das bei der vorherigen Konfiguration der Netzwerkkarte verwendet wurde, und ping, mit dem getestet wird, ob auf das Netzwerk zugegriffen werden kann. Weitere Befehle sind Route, Ethtool usw., die später vorgestellt werden tatsächlich genutzt werden.
4.2.CSpracheBeispiele
Udp- und TCP-Kommunikation werden am häufigsten verwendet. Auf die grundlegende Einführung wird hier nicht näher eingegangen. Schüler, die sich nicht sicher sind, können einfach zwei Artikel über Baidu lesen. Hier nehmen wir UDP als Beispiel. Schauen wir uns ein sehr klassisches Beispiel an.
Die zu implementierenden Funktionen sind:
1) Der Kunde erhält manuell eingegebene Daten
2) Der Client sendet die oben genannten Daten an den Server
3) Der Server sendet die empfangenen Daten zurück an den Client
Gehe direkt zum Code:
/*********************************************** * @{ * @file : udp_client.c * @brief : * @author: TopSemic * @email : topsemic@sina.com * @date : 2019-06-20 ***********************************************/ //-------------------------------------------------- // Copyright (c) Topsemic //-------------------------------------------------- #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define SEND_DEST_PORT 8888 int main() { int sockfd; int ret; struct sockaddr_in addr_sender; struct sockaddr_in addr_dest; int nlen = sizeof(struct sockaddr); int recvlen=0; // sender address bzero(&addr_sender,sizeof(addr_sender)); // dest address bzero(&addr_dest,sizeof(struct sockaddr_in));//每个字节都用0填充 addr_dest.sin_family=AF_INET; addr_dest.sin_addr.s_addr=inet_addr("127.0.0.1"); addr_dest.sin_port=htons(SEND_DEST_PORT); sockfd=socket(AF_INET,SOCK_DGRAM,0); //udp 创建套接字 if(sockfd printf("create socket failure,sockfd:%d\n",sockfd); return -1; } //不断获取用户输入并发送给服务器,然后接受服务器数据 while(1) { char buff[1024] = {0x00}; 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\n", buff); printf("sender ip:%s port:%d\n",inet_ntoa(addr_sender.sin_addr),ntohs(addr_sender.sin_port)); } printf("**************************************\n"); } close(sockfd); return 0; }
Zuerst verwenden wir gcc zum Kompilieren in Ubuntu. Wir führen zuerst den Server auf dem PC aus. Sie können den oben genannten Effekt erzielen wir wollen.
Sie können den obigen Code sorgfältig durchgehen. Es gibt ein paar Dinge, die einer Erklärung bedürfen:
1) UDP unterscheidet sich von TCP dadurch, dass es keinen Anforderungsverbindungs- und Annahmeprozess gibt, sodass in der Praxis keine klare Unterscheidung zwischen Server und Client erfolgt. Die obige Benennung von Server und Client dient nur der Zweckmäßigkeit Wie ich es verstehe: Zuerst Daten senden (Der Client empfängt die Daten, nachdem er die Daten angefordert hat), und der Server empfängt zuerst die Daten und sendet sie dann.
2) Ist Ihnen aufgefallen, dass die Bind-Funktion im Server-Beispiel aufgerufen wird, im Client-Beispiel jedoch nicht? Was ist der Grund? Der Grund dafür ist, dass der Server zuerst Daten empfangen muss, um zu funktionieren. Wenn der Port nicht gebunden ist, gibt es keine Möglichkeit zu wissen, wo er die Daten empfangen soll. Der Grund, warum der Client keine Bindung benötigt, liegt darin, dass er zuerst sendet und unmittelbar nach dem Senden Daten am sendenden Port empfangen kann.
3) Bei der tatsächlichen Arbeit habe ich festgestellt, dass viele Menschen, mich eingeschlossen, oft von Häfen verwirrt sind. Zusammenfassend lässt sich sagen, dass Sie beim Empfang von UDP eine Portnummer (den Port des Geräts dieses Ports) binden müssen, bevor Sie Daten von diesem Port empfangen können. Nach dem Empfang der Daten erhalten Sie die IP-Adresse und die Sendeportnummer der anderen Partei . Geben Sie beim Senden einfach die IP-Adresse und den Port der anderen Partei an. Der Sendeport dieses Computers wird zufällig zugewiesen und es ist nicht erforderlich, den Port zu binden.
Um zu überprüfen, ob der gesendete Port zufällig zugewiesen ist, können wir den Client ausschalten und ihn erneut öffnen. Wir sehen uns die Portinformationen zweimal an Portnummern sind unterschiedlich.
4) Beim Aufrufen von Socket zum Erstellen eines Sockets wird der zweite Parameter der Funktion SOCK_DGRAM übergeben, was angibt, dass das UDP-Protokoll verwendet wird. Wenn es sich um TCP handelt, lautet der Parameter SOCK_STREAM.
5) Verwenden Sie htonl(INADDR_ANY), um die IP-Adresse automatisch abzurufen, wenn Sie der Mitgliedsvariablen addr_local einen Wert zuweisen.
Der Vorteil der Verwendung von INADDR_ANY besteht darin, dass, wenn die Software auf anderen Hosts ausgeführt wird oder sich die Host-IP-Adresse ändert, der Quellcode nicht geändert und neu kompiliert werden muss und beim Starten der Software keine manuelle Eingabe erforderlich ist. Wenn einem Host außerdem mehrere IP-Adressen zugewiesen wurden, können Daten von verschiedenen IP-Adressen empfangen werden, sofern die Portnummern konsistent sind.
6) Die IP, die wir beim Einsenden des Clients festlegen, ist 127.0.0.1. Sie können sie unter Ubuntu und auf dem Board sehen
Ich habe eine englische Beschreibung aus dem Internet gefunden:127.0.0.1 ist die Loopback-Internet-Protokoll-Adresse (IP), die auch als „localhost“ bezeichnet wird. Die Adresse wird verwendet, um eine IP-Verbindung zu demselben Computer oder Computer herzustellen, den der Endbenutzer verwendet as Stellt den lokalen Computer selbst dar.
Im nächsten Schritt kompilieren wir den Client-Code und stellen ihn auf die Platine, um ihn auszuführen. Wir müssen zwei subtile Änderungen vornehmen:
Das erste addr_dest.sin_addr.s_addr=inet_addr(“127.0.0.1”); wird geändert in:
addr_dest.sin_addr.s_addr=inet_addr(“192.168.0.50”);
192.168.0.50 ist die IP-Adresse des PCs.
Diese drei Sätze im zweiten while(1)
char buff[1024] = {0x00};
printf(„Bitte geben Sie eine Zeichenfolge ein:“);
fgets(buff,1024,stdin);
geändert zu:
char buff[1024] = „Hallo TopSemic-Freunde!“;
//printf(„Bitte geben Sie eine Zeichenfolge ein:“);
//fgets(buff,1024,stdin);
Der Zweck besteht darin, dem Client das automatische Senden und Empfangen von Daten zu ermöglichen, ohne auf die Eingabe von Informationen durch den Benutzer warten zu müssen.
Verwenden Sie in Ubuntu den Befehl scp, um die Datei direkt im /opt-Verzeichnis des Boards abzulegen
scp udp_client root@192.168.0.100:/opt
Zusätzlich melden wir uns direkt über den ssh-Befehl unter Ubuntu am Linux-System an
ssh root@192.168.0.100:/opt
Zum Beenden geben Sie einfach „exit“ ein, um zum Ubuntu-Befehlszeilenfenster zurückzukehren.
Auf diese Weise kann die Anmeldung am Board und das Hochladen von Dateien auf das Board in Ubuntu einfacher durchgeführt werden als bei der vorherigen Windows-Anmeldung über die serielle Schnittstelle und der U-Disk-Dateiübertragung.
Ich war voller Freude, als ich udp_server unter Ubuntu ausführte. Ich dachte, es würde erfolgreich laufen, aber tatsächlich passierte überhaupt kein Ergebnis.
Aber offensichtlich kann sich die virtuelle Maschine Ubuntu beim Board anmelden und erfolgreich pingen, und das Board kann auch die IP 192.168.0.50 pingen. Warum kommt udp nicht durch? Später, nach einer Zeit des Nachdenkens, wurde das Problem gelöst. Die Lösung lautet wie folgt:
Der Standard-Netzwerkeinstellungsmodus der virtuellen Maschine ist der unten gezeigte NAT-Modus,
Wir haben es wie unten gezeigt in den Bridge-Modus geändert:
Ziehen Sie dann das Netzwerkkabel ab, schließen Sie es wieder an und ändern Sie die Netzwerkkonfiguration in der virtuellen Ubuntu-Maschine
Ändern Sie die Kabelverbindung der virtuellen Maschine auf eine manuell konfigurierte feste IP, 192.168.0.xx Netzwerksegment (kein Konflikt mit Windows und der Board-IP). Mit ifconfig können Sie überprüfen, ob die Einstellung erfolgreich ist
Melden Sie sich zu diesem Zeitpunkt beim Board an und pingen Sie 192.168.0.80, und Sie können erfolgreich pingen. Zuvor habe ich 192.168.0.50 angepingt. Das ist die IP des Windows-Hosts. Wenn eine Verbindung hergestellt werden kann, bedeutet dies nicht, dass eine Verbindung mit der virtuellen Maschine möglich ist.
Ändern Sie abschließend die IP im obigen Code,
addr_dest.sin_addr.s_addr=inet_addr(“192.168.0.80”);
Kompilieren Sie es erneut, laden Sie es herunter und führen Sie es einmal aus, dann wird es normal funktionieren.
Zusätzlicher Punkt: Beim Debuggen des Boards verwende ich häufig den Netzwerk-Debugging-Assistenten unter Windows. Um dieses Tool zu verwenden, müssen Sie nur den Protokolltyp, die lokale Hostadresse, den lokalen Host-Port und den Remote-Host korrekt konfigurieren und dann senden Klicken Sie darauf und Sie können die Ergebnisse anzeigen.
Zum Beispiel können wir auch den Netzwerk-Debugging-Assistenten in Windows aktivieren, um die Kommunikation zwischen dem Client und dem Server der virtuellen Maschine wie folgt zu simulieren:
Nennen Sie einen sehr häufigen Fehler, der bei der Arbeit leicht gemacht wird.
Angenommen, Ihr Prozessor kommuniziert mit einem externen Gerät über die UDP-Kommunikationsmethode. Der normale Arbeitsablauf sieht wie folgt aus: Sie senden zuerst die Daten und dann antwortet Ihnen das externe Gerät.
Dieses Modell ist den oben genannten Server- und Client-Modellen sehr ähnlich. Was Sie implementieren möchten, ist der Client. Rufen Sie also zuerst die Funktion sendto zum Senden und dann die Funktion recvfrom zum Empfangen auf. Unter normalen Umständen ist es kein Problem, ein solches Programm zu schreiben, aber in der Praxis müssen Sie viele ungewöhnliche Situationen berücksichtigen, z. B. wenn sich ein externes Gerät während des normalen Betriebs plötzlich aus- und wieder einschaltet oder neu startet (Ihr CPU-Gerät wird jedoch nicht mit Strom versorgt). aus). Welche Probleme werden auftreten? Da das externe Gerät ausgeschaltet ist, wird die Recvfrom-Funktion blockiert, da es keine Daten empfangen kann. Selbst nachdem das externe Gerät erneut eingeschaltet und initialisiert wurde, gibt es keine Antwortdaten, da es die Daten nicht empfangen hat, was dazu führt, dass Ihre Recvfrom-Funktion nicht funktioniert stecken bleiben. Nicht bewegen.
Wenn ein solcher Code auf der Website veröffentlicht wird, birgt er große versteckte Gefahren, da es normal ist, dass die oben genannte Situation auf der Website auftritt. Wenn beispielsweise Ihr CPU-Gerät zuerst und das externe Gerät später eingeschaltet wird, Die oben genannten Probleme werden ebenfalls auftreten. In meinem vorherigen Projekt führte dieses Problem dazu, dass sich Kunden über Produktprobleme beschwerten. Der Kunde stellte fest, dass das Problem bei einem Kommunikationsausfall nur durch erneutes Ausschalten des Geräts gelöst werden konnte.
解决上述问题的办法也很简单,可以设置一个超时,使用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,就可以正常工作了,不会再出现上述问题。
Das obige ist der detaillierte Inhalt vonEmbedded Linux-Serie Teil 8: Betreiben von Netzwerkports. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!