[LINUX] Einführung in die Socket-API in C-Sprache 3. TCP-Server / Client Nr. 1

[Einführung in die Socket-API in C Language 2nd Client Edition] Dies ist die Server / Client-Version Nr. 1 der Fortsetzung von (http://qiita.com/tajima_taso/items/fb5669ddca6e4d022c15). Es ist unwahrscheinlich, dass dieses Thema mit nur einem Beitrag abgeschlossen wird, daher habe ich beschlossen, es zur Nummer 1 zu machen.

Ich werde die Socket-Programmierung weiterhin auf dem Quellcode der C-Sprache basieren, aber da ich die wichtigsten Dinge wie das Erstellen von Sockets und das Herstellen von Verbindungen durch TCP beim letzten Mal und zweimal zuvor behandelt habe, sofern keine neuen Informationen vorliegen Ich werde die Erklärung dieses Teils weglassen.

Dieses Mal werden wir uns den tatsächlichen Nachrichtenaustausch zwischen Client und Server ansehen (Daten, die von der Netzwerkanwendung ausgetauscht werden) und einige Anzeichen des Anwendungsprotokolls zeigen.

Ausführungsumgebung

Client-Software Mac OS X 10.10.5 gcc CentOs 6,8 gcc Server-Software x86_64

Schauen wir uns zunächst die Client-Software an.

Quellcode der Client-Software

tcpc.c


#include <stdio.h> //printf(), fprintf(), perror()
#include <sys/socket.h> //socket(), bind(), accept(), listen()
#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(), strcmp()
#include <unistd.h> //close()

#define MSGSIZE 1024
#define BUFSIZE (MSGSIZE + 1)

int main(int argc, char* argv[]) {

	int sock; //local socket descriptor
	struct sockaddr_in servSockAddr; //server internet socket address
	unsigned short servPort; //server port number
	char recvBuffer[BUFSIZE];//receive temporary buffer
	char sendBuffer[BUFSIZE]; // send temporary buffer

	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));

	while(1){
		printf("please enter the characters:");
		if (fgets(sendBuffer, BUFSIZE, stdin) == NULL){
			fprintf(stderr, "invalid input string.\n");
			exit(EXIT_FAILURE);
		}

		if (send(sock, sendBuffer, strlen(sendBuffer), 0) <= 0) {
			perror("send() failed.");
			exit(EXIT_FAILURE);
		}

		int byteRcvd  = 0;
		int byteIndex = 0;
		while (byteIndex < MSGSIZE) {
			byteRcvd = recv(sock, &recvBuffer[byteIndex], 1, 0);
			if (byteRcvd > 0) {
				if (recvBuffer[byteIndex] == '\n'){
					recvBuffer[byteIndex] = '\0';
					if (strcmp(recvBuffer, "quit") == 0) {
						close(sock);
						return EXIT_SUCCESS;
					} else {
						break;
					}
				}
				byteIndex += byteRcvd;
			} else if(byteRcvd == 0){
				perror("ERR_EMPTY_RESPONSE");
				exit(EXIT_FAILURE);
			} else {
				perror("recv() failed.");
				exit(EXIT_FAILURE);
			}
		}
		printf("server return: %s\n", recvBuffer);
	}

	return EXIT_SUCCESS;
}

Zeilen 1 bis 50

Header, Datentyp, Socket-Erstellung, Verbindungsaufbau usw. entsprechen der Client-Version von Last time, daher werde ich sie weglassen.

Zeilen 51-56

Die while-Schleife startet und printf () gibt eine Anweisung aus, um die Zeicheneingabe zur Standardausgabe aufzufordern.

Verwenden Sie dann fgets, um die Zeichenfolge von der Standardeingabe zu empfangen. Abhängig von der Umgebung ist die Eingabemethode die Tastatur des Benutzerterminals. fgets nimmt Eingaben vor, bis sie einen EOF- oder Zeilenvorschubcode erhalten. Beachten Sie jedoch, dass am Ende ein Nullzeichen hinzugefügt wird.

Beachten Sie, dass MSGSIZE für die maximale Größe der angezeigten Zeichenfolge und BUFSIZE für die maximale Größe der gespeicherten Bytezeichenfolge einschließlich Nullzeichen vorgesehen ist.

Zeilen 58-61

Verwenden Sie den Systemaufruf send (), um Daten an den Remote-Host zu senden, zu dem Sie eine Verbindung herstellen.

Das erste Argument gibt den Socket-Deskriptor an, für den die Verbindung hergestellt wurde.

Das zweite Argument ist der Zeiger, der die zu sendende Nachricht enthält, und das dritte Argument ist die Länge der Nachricht. Da die strlen-Funktion zum Angeben der Nachrichtenlänge verwendet wird, werden die von fgets hinzugefügten Nullzeichenbytes ausgeschlossen.

Wie bei der vorherigen Empfehlung wird für das 4. Argument 0 angegeben. Dies ist das Standardverhalten beim Blockieren des Programms, bis die Nachricht im Sendepuffer des Socket-Moduls gespeichert ist, bis die angegebene Nachrichtenlänge gespeichert ist.

Eine mögliche Blockierungssituation besteht darin, dass der Sendepuffer des Socket-Moduls voll ist und keine Nachrichten gesendet werden können. Dies liegt daran, dass im Empfangspuffer des Remote-Hosts kein freier Speicherplatz für die Verbindung vorhanden ist, sodass aufgrund der Flusssteuerung basierend auf der TCP-Fenstergröße keine Daten vom Sendepuffer an das untere Modul gesendet werden können und auf die Datenübertragung gewartet wird. Machen.

Zusätzlich wird der Sendepuffer des Sockets durch die Datenstruktur der Warteschlange realisiert und kann auch als SendQ in "netstat" bestätigt werden.

Was hier wichtig ist, ist, dass der Erfolg von send () darin besteht, dass das Betriebssystem die Übertragung verarbeitet und die Nachricht im Übertragungspuffer des Socket-Moduls gespeichert hat und es möglich war, die Daten tatsächlich an den Remote-Host zu senden. Das heißt das nicht.

Nach dem Vorgang von send () werden verschiedene Prozesse vom TCP-Modul, IP-Modul und Gerätetreiber im Betriebssystem ausgeführt, und das Paket (Byte-Zeichenfolge) wird einer physischen Konvertierung von der Hardware wie der Netzwerkschnittstellenkarte unterzogen, und es wird ein physisches Medium verwendet. Danach erreicht es den verbundenen Host.

Zeilen 64 bis 64

Definiert eine Ganzzahl, die die Anzahl der Bytes im empfangenen Puffer (byteRcvd) und den Index des Arrays im Empfangspuffer (byteIndex) angibt. Beachten Sie, dass der byteIndex mit der Gesamtzahl der in dieser Implementierung empfangenen Bytes übereinstimmt.

Zeilen 65-86

Solange die Gesamtzahl der empfangenen Bytes innerhalb der MSG-GRÖSSE liegt, wiederholt recv den Empfang.

Ich habe beim letzten Mal den Systemaufruf recv () angesprochen, aber das erste Argument ist der Deskriptor des Sockets, in dem die Verbindung hergestellt wird, das zweite Argument ist der Zeiger der zu empfangenden Nachricht, das dritte Argument ist die Länge der zu empfangenden Nachricht und das vierte Argument. Geben Sie 0 an, um das Standardverhalten für das Blockverhalten anzugeben.

Das Standardverhalten besteht darin, die Programmausführung zu blockieren, bis eine Zeichenfolge von Bytes bis zur angegebenen Nachrichtenlänge aus dem Empfangspuffer des Sockets abgerufen werden kann. Der Empfangspuffer des Sockets wird durch die Datenstruktur der Warteschlange realisiert und kann als RecvQ in "netstat" bestätigt werden.

Recv () ist wie send () ein Systemaufruf, der das Betriebssystem auffordert, eine Byte-Zeichenfolge aus dem im Socket gespeicherten Empfangspuffer an den Benutzerprozess zu übertragen, und ist (in diesem Fall) ein Transportmodul der unteren Schicht. Empfängt nur die vom TCP-Modul übergebenen Bytes über die Socket-API.

Da das physikalische Signal, das die Hardware wie die Netzwerkschnittstellenkarte erreicht, nur die vom Protokollstapel aufgrund des Hardware-Interrupts oder Software-Interrupts verarbeiteten Daten empfängt, führt recv () die Datenempfangsverarbeitung durch. Es bedeutet nicht, dass Sie dort sind.

Mit anderen Worten, der Paketempfangsprozess selbst, unabhängig davon, ob recv () ausgeführt wird oder nicht, wird ständig ausgeführt, und wenn der Puffer jedes Moduls voll ist oder die Daten beschädigt sind, wird er verworfen. ..

Richtige Nachrichten, die erst in der Nachbearbeitungsphase des TCP-Moduls verworfen wurden, werden im Socket-Empfangspuffer gespeichert. Recv () leitet die Nachricht daher an den Benutzerprozess weiter, wenn dort Daten vorhanden sind.

Gehen Sie zurück zum Quellcode.

Dieses Mal wird jeweils ein Byte an das Array recvBuffer übertragen.

Wenn die empfangene Bytezeichenfolge vom Stream-Typ mit \ n endet, wird das Anwendungsprotokoll bis zu diesem Punkt als Block aussagekräftiger Daten entworfen. Wenn also \ n empfangen wird, wird der Zeilenumbruch mit einem Nullzeichen überschrieben. Ob es beendet wird oder nicht, wird mit der Funktion strcmp bestimmt.

Wenn es mit quit übereinstimmt, wird der Vorgang beendet. Schließen Sie daher den Socket des Clients und beenden Sie das Programm.

Ist dies nicht der Fall, verlassen Sie die while-Schleife und zeigen Sie die empfangene Zeichenfolge mit Zeilenumbrüchen an. Da am Ende der Bytezeichenfolge ein Nullzeichen hinzugefügt wird, kann es von der Zeichenfolgenausgabefunktion der Sprache C ausgegeben werden.

Dieses Mal wird recv () byteweise ausgeführt, um das Programm zu vereinfachen. Da es sich bei recv () jedoch um einen Systemaufruf handelt, ist der Aufwand für das Umschalten zwischen dem Kernelmodus und dem Benutzermodus der CPU nicht gering.

Daher sollte die Ausführungsleistung verbessert werden, indem eine etwas große Byte-Zeichenfolge mit einem recv () im Puffer des Benutzerprozesses gespeichert und jeweils byteweise verarbeitet wird, jedoch mit dieser Datenmenge Ich denke nicht, dass sich die menschliche Erfahrung ändern wird.

Wie wir im nachfolgenden Serverprogramm einchecken werden, ist die vom Server gesendete Nachricht außerdem die vom Client selbst eingegebene Zeichenfolge, sodass dieselbe eingegebene Zeichenfolge in die Standardausgabe ausgegeben wird, wie sie ist.

Schauen wir uns als nächstes die Serversoftware an. Die Serversoftware ist diesmal einfacher als die Client-Software.

Quellcode der Serversoftware

tcps.c



#include <stdio.h> //printf(), fprintf(), perror()
#include <sys/socket.h> //socket(), bind(), accept(), listen()
#include <arpa/inet.h> // struct sockaddr_in, struct sockaddr, inet_ntoa()
#include <stdlib.h> //atoi(), exit(), EXIT_FAILURE, EXIT_SUCCESS
#include <string.h> //memset()
#include <unistd.h> //close()

#define QUEUELIMIT 5
#define MSGSIZE 1024
#define BUFSIZE (MSGSIZE + 1)

int main(int argc, char* argv[]) {

	int servSock; //server socket descriptor
	int clitSock; //client socket descriptor
	struct sockaddr_in servSockAddr; //server internet socket address
	struct sockaddr_in clitSockAddr; //client internet socket address
	unsigned short servPort; //server port number
	unsigned int clitLen; // client internet socket address length
	char recvBuffer[BUFSIZE];//receive temporary buffer
	int recvMsgSize, sendMsgSize; // recieve and send buffer size

	if ( argc != 2) {
		fprintf(stderr, "argument count mismatch error.\n");
		exit(EXIT_FAILURE);
	}

	if ((servPort = (unsigned short) atoi(argv[1])) == 0) {
		fprintf(stderr, "invalid port number.\n");
		exit(EXIT_FAILURE);
	}

	if ((servSock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0 ){
		perror("socket() failed.");
		exit(EXIT_FAILURE);
	}

	memset(&servSockAddr, 0, sizeof(servSockAddr));
	servSockAddr.sin_family      = AF_INET;
	servSockAddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servSockAddr.sin_port        = htons(servPort);

	if (bind(servSock, (struct sockaddr *) &servSockAddr, sizeof(servSockAddr) ) < 0 ) {
		perror("bind() failed.");
		exit(EXIT_FAILURE);
	}

	if (listen(servSock, QUEUELIMIT) < 0) {
		perror("listen() failed.");
		exit(EXIT_FAILURE);
	}

	while(1) {
		clitLen = sizeof(clitSockAddr);
		if ((clitSock = accept(servSock, (struct sockaddr *) &clitSockAddr, &clitLen)) < 0) {
			perror("accept() failed.");
			exit(EXIT_FAILURE);
		}
		printf("connected from %s.\n", inet_ntoa(clitSockAddr.sin_addr));

		while(1) {
			if ((recvMsgSize = recv(clitSock, recvBuffer, BUFSIZE, 0)) < 0) {
				perror("recv() failed.");
				exit(EXIT_FAILURE);
			} else if(recvMsgSize == 0){
				fprintf(stderr, "connection closed by foreign host.\n");
				break;
			}

			if((sendMsgSize = send(clitSock, recvBuffer, recvMsgSize, 0)) < 0){
				perror("send() failed.");
				exit(EXIT_FAILURE);
			} else if(sendMsgSize == 0){
				fprintf(stderr, "connection closed by foreign host.\n");
				break;
			}
		}

		close(clitSock);
	}

	close(servSock);

	return EXIT_SUCCESS;
}

Zeilen 61-77

Ab der 60. Zeile ist das gleiche wie das Serverprogramm vom letzten Mal.

Die im Client-Programm eingeführten recv () und send () sind wie oben beschrieben. Das Serverprogramm wird implementiert, um die vom Client-Programm empfangene Nachricht unverändert an den Client zurückzugeben.

Sowohl send () als auch recv () werden verarbeitet, wenn der Rückgabewert 0 ist. Auf diese Weise wird accept () erneut verwendet, selbst wenn die Verbindung von der Client-Software unbeabsichtigt getrennt wird. Sie können einen Status erstellen, der auf eine neue Verbindung wartet, und Sie können das Serverprogramm weiter ausführen.

Das aktuelle Serverprogramm kann nicht gleichzeitig auf Anforderungen von mehreren Clients antworten, sodass die Verwendung als Serverprogramm nicht einfach ist.

Wenn Sie zwei oder mehr dieser Client-Programme ausführen, wartet das später gestartete Programm weiterhin auf die Verarbeitung, wenn Sie Zeichen mit fgets eingeben und send () ausführen, und das zuerst verarbeitete Client-Programm wird dies tun Wenn es fertig ist, beginnt es mit der Verarbeitung und so weiter.

Obwohl ständig Leute hereinkommen, gibt es nur einen Geldautomaten, so dass es den Anschein hat, als würden Leute, die später hereinkommen, warten.

Um diese Unannehmlichkeiten zu bewältigen, werden wir in Zukunft diejenigen einführen, die auf Multi-Prozess, Multi-Thread, denselben Prozess, Mehrfachverarbeitung pro Thread usw. erweitert wurden.

In Bezug auf das nächste Mal habe ich neulich den folgenden Artikel über UDP gesehen, daher werde ich einmal den Mechanismus der Datenübertragung und des Datenempfangs über UDP und den Unterschied zu TCP überprüfen. Googles QUIC-Protokoll: Migrieren Sie das Web von TCP nach UDP

Nachschlagewerk

Recommended Posts

Einführung in die Socket-API in C-Sprache 3. TCP-Server / Client Nr. 1
Einführung in die in C-Sprache gelernte Socket-API 4. UDP Server / Client Edition Nr. 1
Einführung in die Socket-API in C Language 2nd Client Edition
Einführung in die in C Language Part 1 Server Edition erlernte Socket-API
Eine Einführung in die moderne Socket-API zum Erlernen in C.
Beispiel-API-Server, der JSON in Golang empfängt
Einführung in PyQt4 Teil 1
So erstellen Sie ein einfaches TCP-Server / Client-Skript
C-API in Python 3
[Einführung in cx_Oracle] (13.) Verbindung über Verbindungspool (Client-Seite)
Gehen Sie in die Sprache, um Teil 7 C in der Sprache GO zu sehen und sich daran zu erinnern
Einführung in Ansible Teil In'Inventory '
Einführung in Ansible Teil ④'Variable '
Lösen der Einführung von AOJ in Algorithmen und Datenstrukturen in Python -Part1-
Zusammenfassung von Kapitel 2 der Einführung in Entwurfsmuster, die in Java gelernt wurden
Übersicht über das Erstellen eines Server-Sockets und das Einrichten eines Client-Sockets
Kapitel 4 Zusammenfassung der Einführung in Entwurfsmuster, die in Java gelernt wurden
Zusammenfassung von Kapitel 3 der Einführung in Entwurfsmuster, die in Java gelernt wurden
Lösen der Einführung von AOJ in Algorithmen und Datenstrukturen in Python -Part2-
Lösen der Einführung von AOJ in Algorithmen und Datenstrukturen in Python -Part4-
Klicken Sie in Python auf die New Relic-API, um den Status des Servers abzurufen
Lösen der Einführung von AOJ in Algorithmen und Datenstrukturen in Python -Part3-
Einführung in Ansible Teil 2 'Grundlegende Grammatik'
Einführung in Python Hands On Teil 1
Einführung in Protobuf-c (C-Sprache ⇔ Python)
So verpacken Sie C in Python
Mit OR-Tools Part0 erlernte Optimierung [Einführung]
Einführung in Ansible Teil 1'Hallo Welt !! '