Maison > Article > Tutoriel système > Série Linux embarquée, partie 8 : ports réseau d'exploitation
Certains microcontrôleurs relativement performants disposent souvent d'interfaces Ethernet. Le port réseau est considéré comme un périphérique relativement complexe dans les MCU car il implique le fonctionnement de la pile de protocoles réseau. Normalement, la pile de protocoles réseau s'exécute dans un système d'exploitation en temps réel (RTOS), il est donc difficile pour les développeurs de microcontrôleurs ordinaires d'utiliser le port réseau. Sous Linux, le port réseau est une interface fréquemment utilisée, car Linux dispose d'une pile de protocoles de communication réseau mature et complète et le pilote sous-jacent a été fourni par le fabricant, il est donc relativement pratique à utiliser. Cet article résumera brièvement l'utilisation des ports réseau sous Linux, en espérant être utile à tout le monde.
2.1.Matériel
1) Carte de développement NUC972 réalisée par un tiers sur Internet :
Les amis intéressés par l'achat peuvent se rendre dans leur boutique Taobao pour acheter :
https://s.click.taobao.com/X8mza8w
Cet article traite principalement du port réseau de la carte.
2) 1 câble USB vers RS232, 1 câble réseau, 1 cordon d'alimentation, 1 câble Micro USB
2.2.Logiciel
1) Nous continuons à utiliser Uboot et Kernel de l'article précédent.
2) Nous utilisons Buildroot pour régénérer Rootfs. L'adresse de téléchargement de NUC972 Buildroot est : https://github.com/OpenNuvoton/NUC970_Buildroot La raison pour laquelle nous utilisons Buildroot pour régénérer Rootfs ici est d'utiliser l'outil Buildroot pour ajouter ce que nous. voulez, comme La fonction ssh dont nous avons besoin dans cet article sera très pratique, et c'est beaucoup plus facile que de la transplanter manuellement. Peut-être que vous ne le comprenez pas. Si vous êtes intéressé, vous pouvez vous référer au didacticiel en ligne pour transplanter manuellement dropbear afin d'implémenter la fonction ssh. En comparant les deux méthodes, vous aurez une compréhension approfondie.
3) La chaîne d'outils croisée arm_linux_4.8.tar.gz est toujours utilisée dans l'article précédent. Je suppose que cette chaîne d'outils est également générée par Buildroot.
Les étapes détaillées ne sont plus présentées ici. Vous pouvez vous référer à un article que j'ai publié auparavant "Utiliser Buildroot pour créer un système de fichiers racine pour I.MX6". Voici quelques points à expliquer :
1) Après avoir téléchargé le Buildroot officiel, entrez dans le répertoire correspondant et exécutez les instructions suivantes :
make nuvoton_nuc972_defconfig
faire
Le temps de première compilation sera un peu long, il faut que chacun soit patient, car il faudra télécharger beaucoup de fichiers en ligne.
2) Concernant la question de la chaîne d'outils croisée, la chaîne d'outils Buildroot est utilisée. La sélection de cette Buildroot créera la chaîne d'outils à partir de zéro. Une fois la compilation terminée, vous pouvez voir qu'il y aura une nouvelle chaîne d'outils dans le répertoire output/host/. Je suppose personnellement que la chaîne d'outils officielle vient de là.
3) Dropbear n'est pas sélectionné dans la configuration par défaut, vous pouvez le sélectionner vous-même.
4) Une fois la compilation terminée, le rootfs généré est output/images/rootfs.tar Afin de pouvoir le graver sur la carte NUC972, il doit d'abord être décompressé, puis utiliser mkyaffs2 pour générer le . fichier au format img.
5) Téléchargez à nouveau Uboot, Kernel, Rootfs, etc. sur la carte, configurez Dropbear et le port réseau, puis utilisez-le. Utilisez la commande passwd pour définir un mot de passe pour l'utilisateur root. L'avantage de définir un mot de passe est que. cela peut empêcher quiconque de se connecter directement au système.
Connectez le câble réseau à la carte et à l'ordinateur, définissez l'adresse IP de l'ordinateur sur 192.168.0.50 et entrez ifconfig eth0 192.168.0.100 dans l'interface de connexion série Afin de garantir que le réseau est disponible après le démarrage, ajoutez cette phrase à /. etc/init.d/ rcS fin du fichier. De cette façon, nous n'avons pas besoin de connecter le port série plus tard, nous pouvons nous connecter au système Linux en utilisant uniquement le port réseau, nous pouvons en même temps transférer des fichiers sur la carte. via le disque U comme avant, et l'efficacité sera grandement améliorée.
4.1.Commandes associées
Les commandes courantes liées au réseau incluent ifconfig, qui a été utilisée lors de la configuration précédente de la carte réseau, et ping, qui est utilisée pour tester si le réseau est accessible. D'autres incluent route, ethtool, etc., qui seront introduites plus tard lorsqu'elles seront utilisées. sont effectivement utilisés.
4.2.CLangueExemples
Les communications Udp et TCP sont les plus couramment utilisées. Leur introduction de base ne sera pas détaillée ici. Les étudiants qui n'en sont pas sûrs peuvent simplement lire deux articles sur Baidu. Prenons ici UDP comme exemple. Prenons un exemple très classique.
Les fonctions à mettre en œuvre sont :
1) Le client reçoit les données saisies manuellement
2) Le client envoie les données ci-dessus au serveur
3) Le serveur renvoie les données reçues au client
Allez directement au 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; }
Nous utilisons d'abord gcc pour compiler dans Ubuntu. Notez que nous ne compilons pas de manière croisée arm-linux-gcc. Nous exécutons d'abord le serveur sur le PC, puis le client. Vous pouvez voir l'effet. nous voulons.
Vous pouvez parcourir attentivement le code ci-dessus. Il y a quelques éléments qui nécessitent des explications :
1) UDP est différent de TCP en ce sens qu'il n'y a pas de processus de connexion et d'acceptation de demande, donc dans la pratique, il n'y a pas de distinction claire entre le serveur et le client. La dénomination ci-dessus du serveur et du client est juste pour faciliter la description. comment je comprends cela : envoyez d'abord les données (le client reçoit les données après avoir demandé les données), et le serveur reçoit d'abord les données, puis envoie les données.
2) Avez-vous remarqué que la fonction bind est appelée dans l'exemple du serveur, mais pas dans l'exemple du client. Quelle en est la raison ? La raison en est que le serveur doit d'abord recevoir des données pour fonctionner. Si le port n'est pas lié, il n'y a aucun moyen de savoir où recevoir les données. La raison pour laquelle le client n'a pas besoin de se lier est qu'il envoie en premier et peut recevoir des données sur le port d'envoi immédiatement après l'envoi.
3) Dans le travail réel, j'ai constaté que de nombreuses personnes, y compris moi-même, sont souvent déconcertées par les ports. Pour résumer ici, lors de la réception d'UDP, vous devez lier un numéro de port (le port du propre périphérique de ce port) avant de pouvoir recevoir des données de ce port. Après avoir reçu les données, vous obtiendrez l'adresse IP de l'autre partie et le numéro de port d'envoi. . Spécifiez simplement l'adresse IP et le port de l'autre partie lors de l'envoi. Le port d'envoi de cette machine est attribué de manière aléatoire et il n'est pas nécessaire de lier le port.
Afin de vérifier que le port envoyé sans lier le port est attribué de manière aléatoire, nous pouvons faire une autre petite expérience. Nous éteignons le client et le rouvrons. Nous regardons les informations de port imprimées deux fois avant et après. les numéros de port sont différents.
4) Lors de l'appel de socket pour créer une socket, le deuxième paramètre de la fonction est transmis SOCK_DGRAM, indiquant que le protocole UDP est utilisé. S'il s'agit de TCP, le paramètre est SOCK_STREAM.
5) Utilisez htonl(INADDR_ANY) pour obtenir automatiquement l'adresse IP lors de l'attribution d'une valeur à la variable membre addr_local.
L'avantage d'utiliser INADDR_ANY est que lorsque le logiciel s'exécute sur d'autres hôtes ou que l'adresse IP de l'hôte change, il n'est pas nécessaire de modifier le code source et de le recompiler, et il n'est pas nécessaire de le saisir manuellement lors du démarrage du logiciel. De plus, si plusieurs adresses IP ont été attribuées à un hôte, les données peuvent être reçues de différentes adresses IP tant que les numéros de port sont cohérents.
6) L'adresse IP que nous définissons lors de l'envoi dans le client est 127.0.0.1, qui est une adresse IP spéciale. Vous pouvez utiliser ifconfig pour la voir. Vous pouvez la voir sous Ubuntu et sur le tableau :
.J'ai trouvé une description en anglais sur Internet :
127.0.0.1 est l'adresse IP (Internet Protocol) de bouclage également appelée « localhost ». L'adresse est utilisée pour établir une connexion IP à la même machine ou ordinateur utilisé par l'utilisateur final. as Représente la machine locale elle-même.
Prochaine étape, nous allons compiler de manière croisée le code client et le mettre sur le tableau pour l'exécuter. Nous devons apporter deux modifications subtiles :
.Le premier addr_dest.sin_addr.s_addr=inet_addr(“127.0.0.1”); est remplacé par :
addr_dest.sin_addr.s_addr=inet_addr(“192.168.0.50”);
192.168.0.50 est l'adresse IP du PC.
Ces trois phrases dans le deuxième while(1)
char buff[1024] = {0x00};
printf("Veuillez saisir une chaîne : ");
fgets(buff,1024,stdin);
changé en :
char buff[1024] = « Bonjour amis TopSemic ! » ;
//printf("Veuillez saisir une chaîne : ");
//fgets(buff,1024,stdin);
Le but est de permettre au client d'envoyer et de recevoir automatiquement des données sans attendre que l'utilisateur saisisse des informations.
Sous Ubuntu, utilisez la commande scp pour placer directement le fichier dans le répertoire /opt de la carte
scp udp_client root@192.168.0.100:/opt
De plus, nous nous connectons au système Linux directement via la commande ssh sous Ubuntu
ssh root@192.168.0.100:/opt
Pour quitter, entrez simplement exit pour revenir à la fenêtre de ligne de commande Ubuntu.
De cette façon, le processus de connexion à la carte et de téléchargement de fichiers sur la carte peut être facilement utilisé dans Ubuntu. Par rapport à la connexion précédente au port série Windows et au transfert de fichiers sur disque U, c'est beaucoup plus pratique.
J'étais plein de joie lorsque j'ai exécuté udp_server sous Ubuntu. Je me suis connecté au tableau avec ssh sous Ubuntu et j'ai exécuté udp_client, mais quelque chose d'inattendu s'est produit.
Mais évidemment, la machine virtuelle Ubuntu peut se connecter à la carte et peut effectuer un ping avec succès, et la carte peut également cingler l'IP 192.168.0.50, pourquoi udp ne peut-il pas passer ? Plus tard, après une période de réflexion, le problème a été résolu. La solution est la suivante :
Le mode de configuration réseau par défaut de la machine virtuelle est le mode NAT illustré ci-dessous,
Nous l'avons modifié en mode pont comme indiqué ci-dessous :
Ensuite, débranchez le câble réseau, reconnectez-le et modifiez la configuration réseau dans la machine virtuelle Ubuntu
Modifiez la connexion filaire de la machine virtuelle en une IP fixe configurée manuellement, un segment de réseau 192.168.0.xx (n'entre pas en conflit avec Windows et l'IP de la carte). Vous pouvez utiliser ifconfig pour vérifier si le réglage est réussi
À ce moment-là, connectez-vous au tableau et envoyez un ping au 192.168.0.80, et vous pourrez cingler avec succès. Auparavant, j'ai envoyé une requête ping à 192.168.0.50. C'est l'adresse IP de l'hôte Windows. S'il peut être connecté, cela ne signifie pas qu'il peut être connecté à la machine virtuelle.
Enfin, changez l'IP dans le code ci-dessus,
addr_dest.sin_addr.s_addr=inet_addr(“192.168.0.80”);
Recompilez-le, téléchargez-le et exécutez-le une fois, et il fonctionnera normalement.
Point supplémentaire : lors du débogage de la carte, vous utiliserez souvent l'assistant de débogage réseau sous Windows. Pour utiliser cet outil, il vous suffit de configurer correctement le type de protocole, l'adresse de l'hôte local, le port de l'hôte local et l'hôte distant, puis envoyez-le, et vous pourrez ensuite voir les résultats.
Par exemple, nous pouvons également activer l'assistant de débogage réseau sous Windows pour simuler la communication entre le client et le serveur de la machine virtuelle, comme suit :
Citez une erreur très courante qui se commet facilement dans le travail réel.
Supposons que votre processeur communique avec un périphérique externe via le port réseau, en utilisant la méthode de communication UDP. Le flux de travail normal est celui indiqué ci-dessous. Vous envoyez d'abord les données, puis le périphérique externe vous répond.
Ce modèle est très similaire aux modèles Serveur et Client mentionnés ci-dessus. Ce que vous souhaitez implémenter, c'est le Client. Autrement dit, appelez d'abord la fonction sendto pour envoyer, puis appelez la fonction recvfrom pour recevoir. Dans des circonstances normales, il n'y a aucun problème à écrire un programme comme celui-ci, mais en pratique, vous devez considérer de nombreuses situations anormales, telles qu'un périphérique externe s'éteint puis se rallume soudainement ou redémarre pendant un fonctionnement normal (mais votre périphérique CPU ne s'alimente pas). off). Quels problèmes vont survenir ? Étant donné que le périphérique externe est éteint, la fonction recvfrom se bloquera car elle ne peut pas recevoir de données. Même une fois le périphérique externe réalimenté et initialisé, il ne donnera pas de données de réponse car il n'a pas reçu les données, ce qui entraînera l'activation de votre fonction recvfrom. être coincé. Ne bougez pas.
Si un tel code est publié sur le site, cela entraînera de grands dangers cachés, car il est normal que la situation ci-dessus se produise sur site. Par exemple, si votre périphérique CPU est allumé en premier et que le périphérique externe est allumé plus tard, les problèmes ci-dessus se produiront également. Dans mon projet précédent, ce problème a amené les clients à se plaindre des problèmes du produit. Le client a constaté qu'en cas d'échec de la communication, le problème ne pouvait être résolu qu'en éteignant à nouveau l'appareil.
解决上述问题的办法也很简单,可以设置一个超时,使用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,就可以正常工作了,不会再出现上述问题。
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!