[TCP UDP thread and a little attempt in basic knowledge of weidongshan Embedded Linux Application Development

Luo xiaohei a un beau dossier. 2022-07-23 19:34:06 阅读数:834

tcpudpthreadlittleattempt

8 Communication réseau

LinuxDémarrage de la programmation réseau【Se référer principalement à ce tutoriel】

Client
Dans un programme réseau,Si un programme communique activement avec un programme externe,On appelle ça un programme client.. Par exemple, nous utilisonsftpLorsque le programme obtient des fichiers d'un autre endroit,C'est à nous.ftpLe programme communique activement avec l'extérieur(Obtenir des fichiers), Donc cet endroit est à nous.ftpUn programme est un programme client.
Serveur
Le programme correspondant au client est le programme serveur.Un programme qui attend passivement un programme externe pour communiquer avec lui - même est appelé un programme serveur..

Les communications réseau sont les mêmes que toutes les communications,Réglage requisObjet de la communication,Cache d'information,Longueur du message.

Pourquoi l'identification d'un sujet nécessite - t - elle à la foisIPPort requis?
Parce qu'un ordinateur en a unIP【Ah, non.,Il peut y avoir beaucoup deIPDe】,Si vous avez deux navigateurs ouverts,Pour postuler pour le même site Web,Ça dépend du port..
Donc, il suffit de distinguer les ports,Le serveur local peut également communiquer avec le client local,Deux..cLes fichiers, c'est tout..
Le programme client doit entrer leipAdresse,Ensuite, le numéro de port doit correspondre au serveur..
Insérer la description de l'image ici

Terminal

$ ifconfig Vous pouvez voir combien il y a sur le tableau de développement actuel IP,Regarde.inet addr
$ ps -a Voir la situation en ligne du serveur et du client ?

Les ordresnetstat Est utilisé pour afficher les connexions réseau , Informations sur les réseaux tels que les tables de routage et les statistiques d'interface .netstat Il y a beaucoup d'options .
Les options que nous utilisons sont -na Utilisé pour afficher l'état détaillé du réseau

Si notre programme serveur écoute 8888Port,Nous pouvons utiliser
telnet localhost 8888
Pour voir l'état du serveur .

ping 192.168.0.1
Ça veut dire qu'on veut voir 192.168.0.1 Si la connexion matérielle est normale

TCP

Détails côté serveur :

fd = socket() //Similaire àopen() La valeur de retour peut être utilisée comme document fdÇa marche comme ça.
link() // Connectez le tuyau de communication Comment les ports sont écrits au hasard , Mais le port client doit correspondre à ce numéro de port
listen() //Écouter les données
accept() // Demandes reçues
send/recv() //Envoyer et recevoir des messages

  1. int socket(int domain, int type,int protocol)

domain:
EntréeAF_UNIXNe peut être utilisé que pour un seulUnix Communication entre les processus du système,
EntréeAF_INETOui.InternetDe, Permettre la communication entre les hôtes distants
type:
EntréeSOCK_STREAM Ça veut dire qu'on utilise TCP Accord,Cela permettra de,Fiable,Dans les deux sens,Bitstream orienté connexion.
EntréeSOCK_DGRAM Ça veut dire qu'on utilise UDPAccord, Cela ne donnera qu'une longueur fixe ,Pas fiable,Communication sans connexion.
protocol: Écris.0

Valeur de retour :
.Descripteur de fichier retourné avec succès,Retour en cas d'échec-1,Regarde.errnoPour plus de détails sur l'erreur.

  1. int bind(int sockfd, struct sockaddr *my_addr, int addrlen)

bindPort local identique àsocketLes descripteurs de fichiers retournés sont groupés
sockfd: C'est parsocketLe descripteur de fichier retourné par l'appel.
my_addr: C'est une indicationsockaddrPointeur vers.

 struct sockaddr_in{
unsigned short sin_family;
unsigned short int sin_port;
struct in_addr sin_addr;
unsigned char sin_zero[8];
}
Nous utilisons cette structure au lieu de sockaddrStructure,sockaddr_in La classification des adresses est plus claire
Mais nous devons forcer la conversion de type lorsque nous passons les paramètres

Attribution des variables structurelles :
sin_familyEn généralAF_INET,
sin_addrSet toINADDR_ANYIndique qu'il peut communiquer avec n'importe quel hôte, Sur la machine IPOn peut le trouver.
sin_port C'est le numéro de port qu'on va écouter 【 Écrivez un code à quatre chiffres , Le client doit être cohérent 】 Mais les quatre chiffres du numéro de port doivent être convertis en format réseau , Donc, pour assigner une valeur htons(8888) //Cette fonction signifie host to net ,Set toshortType【2Octets】, Convertir les octets de l'hôte en ordre d'octets réseau
sin_zero[8] C'est pour remplir .Avecmemset() Définissez tous ces huit octets en mémoire à 0, Initialisation de la mémoire nouvellement demandée .
Insérer la description de l'image ici

addrlen: - Oui.sockaddrLongueur de la structure.sizeof(struct sockaddr)
Valeur de retour:
Le succès est le retour0,Cas d'échec etsocketC'est pareil

  1. int listen(int sockfd,int backlog)

C'est passé.bind()Après la fonction,sockfd Cette poignée est liée au port dont nous avons besoin ,Maintenant.listen()Pour écouter.
sockfd: C'est parsocket L'appel est retourné et a été bind() Descripteurs de fichiers liés .
backlog: Est un tampon pour la demande de message , Est la longueur maximale de la file d'attente demandée .Cet article est défini comme suit:10.
Valeur de retour:
Le succès est le retour0,Cas d'échec etsocketC'est pareil

  1. int accept(int sockfd, struct sockaddr *addr,int *addrlen)

sockfd: C'est parsocketRetour de l'appel&&Parbind()Liens&& Descripteur de fichier à écouter .
addr,addrlenEst utilisé pour remplir le programme pour le client, Du côté serveur, il suffit de créer une variable 、 Passez le pointeur. .
acceptAu moment de l'appel, Les programmes côté serveur seront toujours Blocage Il y a un Le client a émis la connexion .
Valeur de retour:
accept Renvoie un nouveau Descripteur de fichier capable d'envoyer et de recevoir des messages ,À ce moment - là, le côté serveur peut écrire des informations au descripteur.
Retour en cas d'échec-1

acceptFonctions
En plus
Pour pouvoir écouter plusieurs IPMessage de,Je l'ai utilisé ici.fork()Fonctions, Générer des sous - processus pour l'écoute . Si le programme actuel est dans le processus parent ,fork()Ce n'est pas0. Si vous êtes actuellement dans un sous - processus ,fork()Il reviendra.0
///C'est - à - dire, Papa cherche le port 、Port d'écoute、 Attendez que le port envoie la demande de connexion et accepte , Mais au moment de recevoir la demande d'un autre , J'ai confié la tâche à mes enfants , Laissez l'enfant s'accroupir au port pour recevoir des messages , Papa lui - même a continué à écouter le port , S'il y a autre chose IP Envoyez une demande , Donnez le port à un autre enfant .
Insérer la description de l'image ici
Insérer la description de l'image ici
recv Est fonction de la réception du message .
Insérer la description de l'image ici
sockfd: C'est parsocketRetour de l'appel&&Parbind()Liens&&J'écoute.&& Descripteur de fichier pour la demande du client reçue .
buf: - Oui. unsigned char ucRecvBuf[1000]Nom du tableau pour
len: Longueur
flag: - Oui.0,Pas d'explication.
Valeur de retour: Longueur du message

Message pour afficher le tampon Souviens - toi de la fin
Insérer la description de l'image ici

  1. N'oubliez pas d'éteindre la poignée close(Poignée);

Code source du programme serveur 【 C'est différent du Mont Weidong. ,Pour information seulement

/******* Programme serveur (server.c) ************/
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <arpa/inet.h>
int main(int argc, char *argv[])
{

int sockfd,new_fd;
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
int sin_size,portnumber;
char hello[]="Hello! Are You Fine?\n";
if(argc!=2)
{

fprintf(stderr,"Usage:%s portnumber\a\n",argv[0]);
exit(1);
}
if((portnumber=atoi(argv[1]))<0)
{

fprintf(stderr,"Usage:%s portnumber\a\n",argv[0]);
exit(1);
}
/* Le serveur commence à construire socketDescripteur */
if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1)
{

fprintf(stderr,"Socket error:%s\n\a",strerror(errno));
exit(1);
}
/* Remplissage côté serveur sockaddrStructure */
bzero(&server_addr,sizeof(struct sockaddr_in));///bzero () Les blocs de mémoire(String)AvantnRéinitialisation des octets
server_addr.sin_family=AF_INET;
server_addr.sin_addr.s_addr=htonl(INADDR_ANY);
server_addr.sin_port=htons(portnumber);
/* BundlesockfdDescripteur */
if(bind(sockfd,(struct sockaddr *)(&server_addr),sizeof(struct sockaddr))==-1)
{

fprintf(stderr,"Bind error:%s\n\a",strerror(errno));
exit(1);
}
/* ÉcoutersockfdDescripteur */
if(listen(sockfd,5)==-1)
{

fprintf(stderr,"Listen error:%s\n\a",strerror(errno));
exit(1);
}
while(1)
{

/* Blocage du serveur, Jusqu'à ce que le client établisse une connexion */
sin_size=sizeof(struct sockaddr_in);
if((new_fd=accept(sockfd,(struct sockaddr *)(&client_addr),&sin_size))==-1)
{

fprintf(stderr,"Accept error:%s\n\a",strerror(errno));
exit(1);
}
fprintf(stderr,"Server get connection from %s\n",
inet_ntoa(client_addr.sin_addr));
if(write(new_fd,hello,strlen(hello))==-1)
{

fprintf(stderr,"Write Error:%s\n",strerror(errno));
exit(1);
}
/* Cette communication est terminée */
close(new_fd);
/* Boucle suivante */
}
close(sockfd);
exit(0);
}

Détails du client:

socket() // Une poignée est également nécessaire
connect() //Demande de connexion
send/recv() //Envoyer et recevoir des messages

  1. int socket(int domain, int type,int protocol)
  2. int connect(int sockfd, struct sockaddr * serv_addr,int addrlen)
    sockfd: C'est parsocketLe descripteur de fichier retourné par l'appel.
    serv_addr: Comme du côté serveur , Mais le troisième point à déterminer IP.C'estIP C'est en cours d'exécution. .c À saisir au moment du programme ,Et ça marche.inet_aton()Effectuer la conversion de format
    Insérer la description de l'image ici
    inet_aton()À saisirIPInformationargv[1] Enregistrer dans la structure après conversion du format sin_addrAllez.,Retour en cas d'échec0
    Insérer la description de l'image ici

connectLa fonction est utilisée par le client pour se connecter au serveur.
Retour à0,Retour en cas d'échec-1.

  1. Envoyer des données
    Insérer la description de l'image ici
    Obtenir l'entrée du clavierstream Enregistrer dans son propre tableau ucSendBuf- Oui.
    Insérer la description de l'image ici
    Par poignée, Mettre les caractères dans le tampon Envoyer par sa longueur .
    Insérer la description de l'image ici

Code source du programme client 【 C'est différent du Mont Weidong. ,Pour information seulement


/******* Programme client client.c ************/
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <arpa/inet.h>
int main(int argc, char *argv[])
{

int sockfd;
char buffer[1024];
struct sockaddr_in server_addr;
struct hostent *host;
int portnumber,nbytes;
if(argc!=3)
{

fprintf(stderr,"Usage:%s hostname portnumber\a\n",argv[0]);
exit(1);
}
if((host=gethostbyname(argv[1]))==NULL)
{

fprintf(stderr,"Gethostname error\n");
exit(1);
}
if((portnumber=atoi(argv[2]))<0)
{

fprintf(stderr,"Usage:%s hostname portnumber\a\n",argv[0]);
exit(1);
}
/* Le client commence à construire sockfdDescripteur */
if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1)
{

fprintf(stderr,"Socket Error:%s\a\n",strerror(errno));
exit(1);
}
/* Le client remplit les données du serveur */
bzero(&server_addr,sizeof(server_addr));
server_addr.sin_family=AF_INET;
server_addr.sin_port=htons(portnumber);
server_addr.sin_addr=*((struct in_addr *)host->h_addr);
/* Le client lance une demande de connexion */
if(connect(sockfd,(struct sockaddr *)(&server_addr),sizeof(struct sockaddr))==-1)
{

fprintf(stderr,"Connect Error:%s\a\n",strerror(errno));
exit(1);
}
/* Connexion réussie */
if((nbytes=read(sockfd,buffer,1024))==-1)
{

fprintf(stderr,"Read Error:%s\n",strerror(errno));
exit(1);
}
buffer[nbytes]='\0';
printf("I have received:%s\n",buffer);
/* Fin de la communication */
close(sockfd);
exit(0);
}

Exécution

Insérer la description de l'image ici
Insérer la description de l'image ici
Celui - là.127.0.0.1De $ifconfigCommande trouvée
Insérer la description de l'image ici

Processus de blocage

$ ps -a Voir la situation en ligne du serveur et du client
La figure ci - dessous montre: Créer un serveur en premier , Puis un client a été créé , Le serveur a créé un sous - processus sur la connexion , Puis un autre client et un autre sous - processus .

Insérer la description de l'image ici
Quand vous éteignez le processus du client , Il aurait dû y avoir un client de moins et le Sous - processus serveur correspondant , Mais le serveur montre que le Sous - processus est gelé
Insérer la description de l'image ici
Ajoutez ceci au programme serveur , On dirait qu'un signal a été ignoré
Insérer la description de l'image ici

Mon entraînement

socketPremier paramètre pour,C'est du soutien.IPv4. J'ai déjà vu un peu d'information.
Insérer la description de l'image ici
Insérer la description de l'image ici

Je suis là.windowsPour le systèmeipconfigJ'ai trouvé.

Insérer la description de l'image ici


sockaddr_in Cette structure nécessite #include <arpa/inet.h>Ce fichier d'en - tête.
accept() Le paramètre de longueur est à évaluer , Pour créer une nouvelle variable pour lui .
server_addr.sin_addr Il y a aussi une classification des structures , Avant d'attribuer une macro


Après avoir écrit sur le serveur , Cycle mort en cours d'exécution ,Je suismain J'ai commencé à écrire printfAucun affichage, Les résultats des tests ont été imprimés à mi - chemin .Je ne sais pas ce qui se passe.. Écrivez d'abord sur le client ...

bind()Etconnect() Les paramètres d'entrée sont identiques .


Si le serveur.cNon., Au lieu de cela, lancez d'abord le client .c , Clientconnect()La connexion échouera.
Veuillez ajouter une description de l'image


Bien que connecté , Mais le serveur n'a pas reçu le message .
Souris!, Quand j'écris le Code, mon Dieu. tm Tout d'un coup, le tutoriel
recv(sclient, buffer, 999, 0);
C'est changé.
recv(sclient, buffer, strLen(buffer), 0);
Maintenant, c'est fini. ..... Mais le programme ne peut recevoir les messages correctement qu'une seule fois ???【 Où suis - je encore? ???Oh, mon Dieu...Oublie ça.whileC'est. Le Sous - processus ne reçoit des informations qu'une seule fois après sa création , Puis il n'y a pas de programme qui s'exécute pour le Sous - processus , Mais le Sous - processus est vivant , Le processus parent n'a pas recv() Alors, tout est resté ...

socketLors de la transmission des données, Les octets passés , Nous acceptons recvOu envoyersendHeure,Vous pouvez utiliser une chaînechar*Accepté,Mais ça ne marche pas.strLen Indique la longueur des données acceptées , Parce que lorsque la longueur des données acceptées ne correspond pas à la longueur réelle ,socket La prise se ferme ,recv Retour au résultat naturellement 0
C++ProgrammationsocketPour la programmationrecvRéception de la fonction,La valeur de retour est0Solutions

Insérer la description de l'image ici


Le Code que je pratique utilise des serveurs Multi - processus simultanés ,C'est - à - dire: Chaque fois qu'un client demande plus de communication, il fork Un sous - processus est lancé pour traiter le message .
Il n'y a pas de séquence fixe pour l'exécution de ces deux processus , Quel processus exécute d'abord la politique d'ordonnancement des processus qui dépend du système . Je suis un sous - processus avancé ,Et à l'intérieurwhile(1)C'est, Le thread parent fonctionne également plus tard .
Insérer la description de l'image ici

fork()Détails de la fonction
TCPServeurs simultanés multithreadés


Lire plus:
Comprendre les connaissances du réseau ,IP、Masque de sous - réseau、Passerelle、DNS、Numéro de port
windows socketProgrammation réseau I: La configuration la plus simple du serveur et du client

UDP

Serveur:

socket() Changement de mode SOCK_DGRAM 【 Celui - ci a été modifié et n'a pas été reçu TCP Message du client en mode
bind()Comme d'habitude. 【Etclose()C'est un couple.,J'étaiswhile La fin de la boucle est écrite close(), Ce qui fait que le serveur ne peut être désactivé qu'une seule fois ,Plus jamaisrecv() Pas de message . On pense que ça va recommencer bind() Maisbind() Je ne peux pas le mettre dans whileViens ici....
listen()Non, non【Dis - le., Il n'a qu'une seule longueur de file d'attente .Remplacer parUDP Je me fiche de perdre mon sac. ,Ce n'est pas nécessaire.listen?
accept()Non, non,Tu sais pourquoi?.Parce que
recvfrom() Paramètres d'entrée pour= recv()Paramètres d'entrée pour + accept()Paramètres d'entrée pour

Client:

socket() Changement de mode SOCK_DGRAM
En fait, il suffit de le changer comme ça ,On peut essayer.
connect()Etsend()Ensemble
Supprimerconnect()Juste pourUDPDesendto()Fonction.Parce que
sendto() Paramètres d'entrée pour= send()Paramètres d'entrée pour + connect()Paramètres d'entrée pour

9 Programmation multithreadée

std::threadAvecpthreadComparer

hello Nouveau thread

pthread_create(Poignée,Propriétés【Peut écrireNULL】,Fonction de rappel,Paramètres d'entrée de la fonction【Peut écrireNULL】)
Une fois créé , Va entrer dans cette fonction de rappel

La première fois que vous lancez ce thread ,Oui.gcc -o Ajouter à la fin de cette commande -lpthread Pour lier les bibliothèques statiques .

Contient le fichier d'en - tête , Seules les déclarations avec fonction thread peuvent être expliquées , Mais ce n'est pas encore fait , Plus-lpthread C'est au stade du lien ,Lien vers cette bibliothèque

ps -T
Voir les fils dans le système

kill -9 <Numéro du processus>
Le système d'exploitation force un processus à tuer à partir du niveau du noyau

Insérer la description de l'image ici

La mémoire partagée par fil est une variable globale …

Après tout, une fois le thread créé Le thread a commencé à fonctionner , La première chose qui vient à l'esprit est certainement d'utiliser une variable globale comme bit de drapeau , Attendez la ligne principale getsDonnées.
Mais laissez le nouveau thread continuer while Interrogation cyclique en attente de variables g_hasDataPosition1 , Ça va être épuisant cpu.
Ne l'utilisez pas.whileMort, etc.

Utilisez le sémaphore pour empêcher le nouveau fil d'attendre

Processus avec sémaphore
Créer un nouveausem_t Variable globale de type
sem_init Ce sémaphore
Avecsem_wait()Pour bloquer
Avecsem_post()Notification
【 Ne sera avisé qu'une seule fois par défaut , Le nouveau thread est bloqué après une course 】

Nouveau thread lors de la préparation de la sortie , Les variables globales peuvent être modifiées à mi - chemin

Protéger les fils avec des mutex
Processus:
Créer unpthread_mutextVariable de type, Assigner une définition de macro spécifiée PTHREAD_MUTEX_INITIALIZER 【 Ou vous pouvez le faire avec une fonction pthread_mutex_init()】
Et puis on pourra utiliserpthread_mutex_lock() Et pthread_mutex_unlock() Protéger un petit morceau de code

LinuxThread-Mutex Lockpthread_mutex_t

Le fil principal court trop vite ,fgets Lisez le contenu et tournez la tête printf Après l'impressionfunc Le thread a fini de lire le message et printfCette phrase...recv.
C'est une situation où seul le fil principal peut obtenir la serrure après l'ajout de la serrure , Le nouveau thread ne peut être vu que sur le côté .
Le fil principal est principalement fgetsÇa bloque., Alors bloquez avec . Mais les variables globales doivent encore être partagées , Impossible de bloquer cette zone ,Alors laissezgets Bloquer dans son propre fief , Après avoir obtenu les données, courez dans la zone critique pour télécharger les données . Pour que le fil principal ne vole pas la serrure .

Insérer la description de l'image ici

Insérer la description de l'image ici
【Huh!, Mais une telle procédure permettrait quand même printf Les indices ne sont pas dans le bon ordre .】

Les variables conditionnelles mutex peuvent être utilisées avec , Sans sémaphore

pthread_cond_wait Il s'agit de variables conditionnelles , Combiné avec un mutex

Initialisation et mutex mutexMême chose
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
La notification a été faite par
pthread_cond_signal(&cond);
En attendant la notification
pthread_cond_wait(&cond,&mutex);

J'allais aussi mettre un avis d'attente en dehors de la zone critique , Mais il y a ce mutex dans ce paramètre d'entrée , Ça doit être dans la zone critique. , J'ai besoin de la serrure avant d'être informé .

La différence entre un sémaphore et une variable conditionnelle

Je veux mettreTCP Pour communiquer entre les processus

Analyse de plusieurs modes de communication entre les processus ( Y compris le code source de l'Instance )

InTCPDans l'exemple de, Le processus principal n'écoute que , Dès réception de la demande de communication client fork() Le Sous - processus prend le relais , Par sous - processus printf.
Je veux Piper le message du sous - processus au processus principal pour l'afficher .

ParpipeCréation de fonctions:
#include <unistd.h>
int pipe(int pipefd[2]);

Adoptionclose(pipefd[0]) Éteignez la lecture du tuyau .【 En général, le processus d'écriture des données ne lit pas , Le processus de lecture des données n'écrit pas , Sinon, il y aura toutes sortes de confusion. 】

Adoptionread()Ouwrite()Lire et écrire des données dans ce fichier.
【C'est incroyable, Les poignées de fichiers et de tuyaux sont intType】

write(pipefd[1],msg,strlen(msg));
【 Le système de fichiers est vraiment génial. ,Prends - le.writeC'est bon】

Et puis il y a une question , Le processus principal ne peut pas être exécuté une seule fois ,Pas du tout.whileMort, etc, Alors il faut ajouter une fonction de rappel avec un signal égal .
【Je suis passé deMode de notification asynchroneJ'ai trouvé.sigqueueFonctions Pour envoyer un message 】
sigqueue La fonction envoie un signal au processus lui - même SIGUSR1Signal, Et ajouter une chaîne d'information
C'est - à - dire, Le processus principal doit appeler la fonction de rappel à travers le signal et les informations de chaîne qu'il contient ... Je ne vais pas devoir mettre cette fonction de signalisation dans le Sous - processus ... Ça ne marchera pas ,Excusez - moi.
【 Enfin, le plus commun signal()DeSIGIO C'est fait 】
Retouchez le processus :
Processus principalTCPPort d'écoute
Lorsque le client demande un message ,Processus principalforkProcessus enfant sortantrecvDonnées, La branche du processus principal enregistre une fonction de rappel pour la communication asynchrone , La poignée utilise pipe Lire la poignée de .
recv Les données sont écrites dans le Sous - processus pipe
La fonction de rappel du signal est responsable readDonnées, Et ajouter des données à la fin du tableau des variables globales .
Insérer la description de l'image ici

Copyright:Cet article est[Luo xiaohei a un beau dossier.]Établi,Veuillez apporter le lien original pour réimprimer,remercier。 https://fra.fheadline.com/2022/204/202207231743380062.html