Dies ist die Client-Version der Fortsetzung von Einführung in die in C-Sprache erlernte Socket-API, Teil 1 Server Edition.
Da wir die Serversoftware das letzte Mal erstellt haben, werden wir uns diesmal mit dem Kommunikationsmechanismus zwischen verschiedenen Hosts befassen, während wir die Client-Software erstellen, die die Socket-API verwendet.
Da die vorherige Serversoftware das TCP-Protokoll verwendet hat, verwendet diese Client-Software auch das TCP-Protokoll.
Obwohl das Client-Programm und das Server-Programm ähnliche Teile haben, wie die Struktur der auszutauschenden Daten und die Methode zum Senden und Empfangen, gibt es einige Unterschiede. Achten wir also auf diese Teile.
Diesmal handelt es sich um Client-Software, daher habe ich sie unter Mac OS X erstellt und ausgeführt. Mac OS X 10.10.5. Der Compiler ist gcc. Eingeführt in Xcode. Der Pfad der Header-Datei wurde in meiner Umgebung in der tiefen Hierarchie "/ Applications / Xcode.app / Contents / Developer / Toolchains / XcodeDefault.xctoolchain / usr / include" gespeichert.
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;
}
Laden der erforderlichen Header. Ich musste nur das Gleiche wie auf den Server laden. Der Kommentar rechts beschreibt, was zur Verwendung gelesen wird. Es gibt einige, die von dort weiter aufgenommen und verwendet werden können, aber ich werde sie weglassen.
Diesmal ist es nicht erforderlich, aber es wird ab dem nächsten Mal erforderlich sein. Definieren Sie daher die Symbolkonstanten. Ich denke, dass die Anzahl der Symbolkonstanten in Zukunft zunehmen wird.
Ordnen Sie einen Speicherbereich für den gewünschten Datentyp zu. Die Definition des Datentyps in der Netzwerkprogrammierung wurde in Teil 1 beschrieben. Bitte beachten Sie dies auch.
Argumentprüfung.
Zum Zeitpunkt der Ausführung ist die IP-Adresse des Verbindungszielhosts eine Zeichenfolge in Punkt-Dezimal-Notation (IPv4), und die Portnummer kann durch eine beliebige Nummer angegeben werden.
Bei der Serverversion wurde angegeben, dass die Portnummer dem lokalen Host zugeordnet werden soll. Bei der Clientversion müssen Sie jedoch die IP-Adresse und die Portnummer des Remote-Hosts angeben, zu dem eine Verbindung hergestellt werden soll.
Löschen Sie wie bei der Server Edition den Bereich der Struktur sockaddr_in auf Null. Der Unterschied zur Server Edition besteht darin, dass die Adressinformationen des Verbindungszielhosts in dieser sockaddr_in-Struktur gespeichert sind. Dieses Mal werden wir keine sockaddr_in-Struktur für den lokalen Host vorbereiten.
Geben Sie wie bei der Server Edition AF_INET an, um anzugeben, dass es sich um die Internetadressfamilie (IPv4) in sin_family der Struktur sockaddr_in handelt.
Verwenden Sie die Funktion inet_aton, um eine IPv4-Zeichenfolge (Dot Decimal Notation) in eine binäre Darstellung der Netzwerkbyte-Reihenfolge zu konvertieren und im Feld sin_addr zu speichern. Im Gegensatz zur Server Edition wird sie durch Übergeben eines Zeigers gespeichert, es gibt jedoch mehrere Methoden, nur weil eine solche Methode erforderlich ist.
Im Beispiel für Anfänger ist die Funktion inet_addr, die die IPv4-Zeichenfolge (Dot Decimal Notation) in den Wert der binären Darstellung konvertiert und als Rückgabewert zurückgibt, verwendet. In der Funktion inet_addr beträgt der Rückgabewert zum Zeitpunkt des Fehlers jedoch -1. Ich habe diesmal die Funktion inet_aton verwendet, weil sie auf eine gültige IP-Adresse (255.255.255.255) verweist und der Fehlerwert etwas enthält.
Wie bei der Serverversion wird die Nummer der Portnummer des Arguments in eine binäre Darstellung in der Reihenfolge der Netzwerkbytes konvertiert und in sin_port der Struktur sockaddr_in gespeichert.
Fordern Sie die Erstellung eines Sockets mit dem Systemaufrufsocket () an. Jedes Argument ist das gleiche wie in der Server Edition. Überprüfen Sie dies daher.
Dies ist der größte Unterschied zur Serverversion des Programms. Der Client ruft den Systemaufruf connect () auf, um eine Verbindung mit dem Serverprogramm herzustellen.
Geben Sie den Socket-Deskriptor an, der den Socket identifiziert, den Sie zuvor im ersten Argument erstellt haben, die Struktur sockaddr_in, die die IP-Adresse und die Portnummer des Servers im zweiten Argument enthält, und deren Größe im dritten Argument.
Es ist dasselbe wie das Programm für den Server, aber da die Socket-API eine Allzweck-API ist, wird der Zeiger der Struktur sockaddr_in auf den Zeiger der Struktur sockaddr umgewandelt, bei dem es sich um einen Allzweck-Datentyp handelt.
Möglicherweise haben Sie hier eine Frage.
Ist es nicht erforderlich, den Socket des lokalen Hosts mit der IP-Adresse und der Portnummer über den Systemaufruf bind () zu verbinden, wie im Fall der Server Edition? Wann.
Korrekt.
Selbst der Client, der in der Lage ist, die Verbindung herzustellen, muss die Adressinformationen mit dem Socket verbunden haben, um kommunizieren zu können.
Wenn der Client den Systemaufruf connect () aufruft, wird die Socket-Struktur automatisch mit der lokalen IP-Adresse des lokalen Hosts und der offenen Portnummer sowie den Zieladressinformationen gefüllt.
Sie können die lokalen Adressinformationen explizit an den Socket binden, indem Sie den Systemaufruf bind () vor connect () aufrufen, genau wie auf einem Server. Es kann jedoch gesagt werden, dass diese Spezifikation normalerweise nicht notwendig ist.
Dies liegt daran, dass der Client die Adressinformationen des Servers im Voraus kennen muss, um eine Verbindung mit dem Server herzustellen, während der Server die Adressinformationen des Clients nicht im Voraus kennen muss.
Wenn connect () normal abgeschlossen und die Steuerung wiederhergestellt ist, wird der 3-Wege-Handshake erfolgreich abgeschlossen, sodass Sie den Status mit netstat
überprüfen können, das auch in der Server Edition verwendet wurde.
Führen Sie das in der Server Edition erstellte Programm aus (ändern Sie es einfach so, dass close () nicht aufgerufen wird) und in der Clientumgebung ./a.out xxx.xxx.xxx.xxx 8080
(xxx.xxx.xxx.xxx ist der Server) Wenn Sie "netstat -t" auf einem anderen Terminal ausführen, wird eine Meldung wie "tcp4 0 0 192.168.xxx.xxx.65486 xxx.xxx.xxx.xxx.8080 ESTABLISHED" angezeigt. Ich kann es schaffen
Die Portnummer 65486 ist die Portnummer des Client-Hosts, der automatisch vom Port zugewiesen wird, der vom Betriebssystem während connect () geöffnet wurde.
Wenn eine erfolgreiche Verbindung mit connect () hergestellt wurde, bedeutet dies, dass eine Verbindung zum Server hergestellt wurde, sodass eine Meldung angezeigt wird, die darauf hinweist.
Ich habe den Code für das nächste Mal etwas früher geschrieben, aber was ich jetzt beachten sollte, ist "(byteRcvd = recv (sock, recvBuffer, MSGSIZE, 0)" in Zeile 54 und "byteRcvd == 0" in Zeile 58. Dies ist der Teil von `.
recv () ist ein Systemaufruf, der die in der Empfangspufferwarteschlange gespeicherten Bytes in den Benutzerprozess abruft. Der Empfangspuffer kann als Recv-Q in "netstat" bestätigt werden.
recvBuffer bedeutet die Startadresse des Speicherbereichs, in dem die empfangene Bytezeichenfolge gespeichert ist, und MSGSIZE gibt die Größe an, die abgerufen werden soll. Normalerweise sollte die empfangene Bytezeichenfolge keine NULL-Zeichen enthalten. Fügen Sie daher bei Verwendung einer Zeichenfolgenausgabefunktion usw. das NULL-Zeichen am Ende der Erfassungsbytezeichenfolge hinzu.
Im 4. Argument wird 0 angegeben, dies ist jedoch ein Flag, um das Verhalten von recv zu ändern. 0 bedeutet das Standardverhalten des Blockierens der Programmaktivität, bis sie empfangbar ist.
Der Rückgabewert ist die Anzahl der empfangenen Bytes. Wenn er jedoch 0 ist, bedeutet dies, dass das Programm, mit dem Sie kommunizieren, die TCP-Verbindung getrennt hat.
Ich möchte, dass Sie das Programm der vorherigen Server-Edition überprüfen, aber dieses Programm wird sofort nach dem Akzeptieren getrennt und ermöglicht das Senden und Empfangen von Daten zwischen dem Client und dem Server.
Wenn Sie dieses Programm und das vorherige Programm auf dem Client bzw. Server ausführen, zeigt die Clientseite daher die Meldung "ERR_EMPTY_RESPONSE" an und beendet das Programm.
Diese Nachricht ist nach der Fehlermeldung benannt, die ich erhalten habe, als ich das letzte Mal, als ich Chrome als Client-Software verwendet habe, vom Server getrennt wurde.
Dieses Mal habe ich eine TCP-Client-Software erstellt und konnte mit dem vorherigen Serverprogramm kommunizieren.
Es tut mir leid, hin und her zu gehen, aber beim nächsten Mal möchte ich die Serversoftware erweitern, aussagekräftige Nachrichten an den Client senden und die Client-Software, die sie empfängt, abhängig vom Gesamtvolumen erweitern. ..
Recommended Posts