Il s'agit de la version cliente de la suite de Introduction à l'API Socket apprise en langage C, partie 1 Server Edition.
Depuis que nous avons créé le logiciel serveur la dernière fois, nous examinerons cette fois le mécanisme de communication entre les différents hôtes tout en créant le logiciel client qui utilise l'API socket.
Étant donné que le logiciel serveur précédent utilisait le protocole TCP, ce logiciel client utilise également le protocole TCP.
Bien que le programme client et le programme serveur aient des parties similaires telles que la structure des données à échanger et la méthode d'envoi et de réception, il existe quelques différences, alors prêtons attention à ces parties.
Cette fois, il s'agit d'un logiciel client, je l'ai donc créé sur Mac OS X et je l'ai exécuté.
Mac OS X 10.10.5. Le compilateur est gcc. Introduit dans Xcode.
Le chemin du fichier d'en-tête était stocké dans la hiérarchie profonde / Applications / Xcode.app / Contents / Developer / Toolchains / XcodeDefault.xctoolchain / usr / include
dans mon environnement.
tcpc.c
#include <stdio.h> //printf(), fprintf(), perror()
#include <sys/socket.h> //socket(), connect(), recv()
#include <arpa/inet.h> // struct sockaddr_in, struct sockaddr, inet_ntoa(), inet_aton()
#include <stdlib.h> //atoi(), exit(), EXIT_FAILURE, EXIT_SUCCESS
#include <string.h> //memset()
#include <unistd.h> //close()
#define MSGSIZE 32
#define MAX_MSGSIZE 1024
#define BUFSIZE (MSGSIZE + 1)
int main(int argc, char* argv[]) {
int sock; //local socket descripter
struct sockaddr_in servSockAddr; //server internet socket address
unsigned short servPort; //server port number
char recvBuffer[BUFSIZE]; //receive temporary buffer
int byteRcvd, totalBytesRcvd; //received buffer size
if (argc != 3) {
fprintf(stderr, "argument count mismatch error.\n");
exit(EXIT_FAILURE);
}
memset(&servSockAddr, 0, sizeof(servSockAddr));
servSockAddr.sin_family = AF_INET;
if (inet_aton(argv[1], &servSockAddr.sin_addr) == 0) {
fprintf(stderr, "Invalid IP Address.\n");
exit(EXIT_FAILURE);
}
if ((servPort = (unsigned short) atoi(argv[2])) == 0) {
fprintf(stderr, "invalid port number.\n");
exit(EXIT_FAILURE);
}
servSockAddr.sin_port = htons(servPort);
if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0 ){
perror("socket() failed.");
exit(EXIT_FAILURE);
}
if (connect(sock, (struct sockaddr*) &servSockAddr, sizeof(servSockAddr)) < 0) {
perror("connect() failed.");
exit(EXIT_FAILURE);
}
printf("connect to %s\n", inet_ntoa(servSockAddr.sin_addr));
totalBytesRcvd = 0;
while (totalBytesRcvd < MAX_MSGSIZE) {
if ((byteRcvd = recv(sock, recvBuffer, MSGSIZE, 0)) > 0) {
recvBuffer[byteRcvd] = '\0';
printf("%s", recvBuffer);
totalBytesRcvd += byteRcvd;
} else if(byteRcvd == 0){
perror("ERR_EMPTY_RESPONSE");
exit(EXIT_FAILURE);
} else {
perror("recv() failed.");
exit(EXIT_FAILURE);
}
}
printf("\n");
close(sock);
return EXIT_SUCCESS;
}
Chargement des en-têtes requis. Tout ce que j'avais à faire était de charger la même chose que sur le serveur. Le commentaire de droite décrit ce qui est lu pour utilisation. Certains d'entre eux peuvent être inclus et utilisés à partir de là, mais je les omettrai.
Ce n'est pas nécessaire cette fois, mais ce sera nécessaire à partir de la prochaine fois, définissez donc les constantes du symbole. Je pense que le nombre de constantes de symboles augmentera à l'avenir.
Allouez une zone de mémoire pour le type de données requis. La définition du type de données dans la programmation de réseau a été décrite dans la partie 1, veuillez donc vous y référer également.
Vérification des arguments.
Au moment de l'exécution, l'adresse IP de l'hôte de destination de la connexion est une chaîne de caractères en notation décimale à points (IPv4) et le numéro de port peut être spécifié par n'importe quel nombre.
Dans le cas de la version serveur, il a été spécifié d'associer le numéro de port à l'hôte local, mais dans le cas de la version client, spécifiez l'adresse IP et le numéro de port de l'hôte distant auquel se connecter.
Comme dans le cas de l'édition serveur, effacez la zone de la structure sockaddr_in à zéro. La différence avec l'édition serveur est que les informations d'adresse de l'hôte de destination de la connexion sont stockées dans cette structure sockaddr_in. Cette fois, nous ne préparerons pas de structure sockaddr_in pour l'hôte local.
Comme dans le cas de l'édition serveur, spécifiez AF_INET pour indiquer qu'il s'agit de la famille d'adresses Internet (IPv4) dans sin_family de la structure sockaddr_in.
Utilisez la fonction inet_aton pour convertir une chaîne de notation décimale à points (IPv4) en une représentation binaire d'ordre des octets du réseau et la stocker dans le champ sin_addr. Contrairement à l'édition serveur, elle est stockée en passant un pointeur, mais il existe plusieurs méthodes simplement parce qu'elle a pris une telle méthode.
Dans l'exemple pour les débutants, la fonction inet_addr qui convertit la chaîne de caractères de notation décimale à points (IPv4) en valeur de représentation binaire et la renvoie lorsque la valeur de retour est utilisée, mais dans la fonction inet_addr, la valeur de retour au moment de l'erreur est -1. J'ai utilisé la fonction inet_aton cette fois car elle pointe vers une adresse IP valide (255.255.255.255) et il y a quelque chose dans la valeur d'erreur.
Comme pour la version serveur, le numéro du numéro de port de l'argument est converti en une représentation binaire dans l'ordre des octets du réseau et stocké dans sin_port de la structure sockaddr_in.
Demandez la création d'un socket à l'aide de l'appel système socket (). Chaque argument est le même que dans l'édition serveur, veuillez donc le vérifier.
C'est la plus grande différence avec la version serveur du programme. Le client appelle l'appel système connect () pour établir une connexion avec le programme serveur.
Spécifiez le descripteur de socket qui identifie le socket que vous avez créé précédemment dans le premier argument, la structure sockaddr_in qui contient l'adresse IP et le numéro de port du serveur dans le deuxième argument, et sa taille dans le troisième argument.
C'est le même que le programme pour le serveur, mais comme l'API de socket est une API à usage général, le pointeur de la structure sockaddr_in est converti en pointeur de la structure sockaddr, qui est un type de données à usage général.
Vous pouvez avoir une question ici.
N'est-il pas nécessaire de connecter le socket de l'hôte local avec l'adresse IP et le numéro de port en utilisant l'appel système bind () comme dans le cas de l'édition serveur? Quand.
C'est vrai.
Même le client qui est en mesure de démarrer la connexion doit avoir les informations d'adresse connectées au socket pour communiquer.
En fait, lorsque le client appelle l'appel système connect (), la structure du socket est automatiquement remplie avec l'adresse IP locale de l'hôte local et le numéro de port ouvert, ainsi que les informations d'adresse de destination.
Vous pouvez lier explicitement les informations d'adresse locale au socket en appelant l'appel système bind () avant connect (), comme vous le feriez sur un serveur. Cependant, on peut dire que cette spécification n'est généralement pas nécessaire.
Cela est dû au fait que le client a besoin de connaître les informations d'adresse du serveur à l'avance pour démarrer une connexion avec le serveur, alors que le serveur n'a pas besoin de connaître les informations d'adresse du client à l'avance.
Lorsque connect () est terminé normalement et que le contrôle est restauré, la prise de contact à trois est terminée avec succès, vous pouvez donc vérifier l'état en utilisant netstat
, qui était également utilisé dans l'édition serveur.
Exécutez le programme créé dans l'édition serveur (modifiez-le simplement pour que close () ne soit pas appelé), et dans l'environnement client ./a.out xxx.xxx.xxx.xxx 8080
(xxx.xxx.xxx.xxx est le serveur Lorsque vous exécutez netstat -t
sur un autre terminal, vous verrez un message comme tcp4 0 0 192.168.xxx.xxx.65486 xxx.xxx.xxx.xxx.8080 ESTABLISHED
. Je peux le faire.
Le numéro de port 65486 est le numéro de port de l'hôte client attribué automatiquement à partir du port ouvert par le système d'exploitation lors de la connexion ().
Si une connexion réussie est établie à l'aide de connect (), cela signifie qu'une connexion a été établie avec le serveur, donc un message indiquant cela s'affiche.
J'ai écrit le code pour la prochaine fois un peu plus tôt, mais ce à quoi je dois faire attention maintenant, c'est (byteRcvd = recv (sock, recvBuffer, MSGSIZE, 0)
à la ligne 54 et byteRcvd == 0 à la ligne 58. C'est la partie de
.
recv () est un appel système qui récupère les octets stockés dans la file d'attente du tampon de réception dans le processus utilisateur. Le tampon de réception peut être confirmé comme Recv-Q dans netstat
.
recvBuffer signifie l'adresse de début de la zone mémoire où la chaîne d'octets reçue est stockée, et MSGSIZE spécifie la taille à obtenir. Normalement, la chaîne d'octets reçue ne doit pas contenir de caractères NULL, donc lorsque vous utilisez une fonction de sortie de chaîne de caractères, etc., ajoutez le caractère NULL à la fin de la chaîne d'octets d'acquisition.
0 est spécifié dans le 4ème argument, mais c'est un indicateur pour changer le comportement de recv. 0 signifie le comportement par défaut de blocage de l'activité du programme jusqu'à ce qu'elle soit recevable.
La valeur de retour est le nombre d'octets reçus, mais si elle est égale à 0, cela signifie que le programme avec lequel vous communiquez a déconnecté la connexion TCP.
J'aimerais que vous vérifiiez le programme de l'édition précédente du serveur, mais ce programme se déconnecte immédiatement après avoir accepté et rendu possible l'envoi et la réception de données entre le client et le serveur.
Par conséquent, si vous exécutez respectivement ce programme et le programme précédent sur le client et le serveur, le côté client affichera le message ʻERR_EMPTY_RESPONSE` et quittera.
Ce message est nommé d'après le message d'erreur qui a été affiché lorsque j'ai été déconnecté du serveur la dernière fois que j'ai utilisé Chrome comme logiciel client.
Cette fois, j'ai créé un logiciel client TCP et j'ai pu communiquer avec le programme serveur précédent.
Je suis désolé de faire des allers-retours, mais la prochaine fois, j'aimerais étendre le logiciel serveur, envoyer des messages significatifs au client et étendre le logiciel client qui le reçoit en fonction du volume total. ..
Recommended Posts