[LINUX] Einführung in die in C-Sprache gelernte Socket-API 4. UDP Server / Client Edition Nr. 1

Dies ist die 4. Einführung in die Socket-API zum Lernen in C-Sprache, die ich persönlich zu einer Serie gemacht habe. Dieses Mal werden wir den Mechanismus der Datenübertragung / des Datenempfangs für UDP und den Unterschied zu TCP überprüfen.

Mit den folgenden Bewegungen in Bezug auf UDP kann gesagt werden, dass es sich um ein Kommunikationsprotokoll handelt, das erneut Aufmerksamkeit erregt. Googles QUIC-Protokoll: Migrieren Sie das Web von TCP nach UDP

Funktionen von UDP

UDP ist eine Erweiterung von IP, einem Host-zu-Host-Protokoll, auf Anwendungsebene. UDP verwendet eine Portnummer für die Adressierung und behält im Gegensatz zu TCP die Grenzen jeder Nachricht bei.

Darüber hinaus verwirft UDP das Datagramm so, wie es ist, wenn die Daten beschädigt sind. Im Gegensatz zu TCP handelt es sich jedoch um ein Protokoll zur Bereitstellung des Datagrammdienstes vom Best-Effort-Typ und ist zuverlässig, z. B. bei der erneuten Datenübertragungsverarbeitung. Führt keine verwandte Verarbeitung durch.

Auf den ersten Blick scheint es TCP unterlegen zu sein, aber es gibt viele gute Punkte wie einen schnelleren Betrieb und einen einfacheren Empfang von Nachrichten als TCP. Der obige Artikel geht auch auf die Eigenschaften und die Güte von UDP ein, daher ist es eine gute Idee, ihn zu lesen.

Und im Gegensatz zu TCP ist UDP ein verbindungsloses Protokoll, das verwendet werden kann, ohne eine Verbindung herzustellen. Dies wird auch im folgenden Quellcode gezeigt.

Ausführungsumgebung

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

Ich werde sowohl Server-Software als auch Client-Software einführen, aber da Server-Software einfacher ist, werde ich sie zuerst einführen.

Quellcode der Serversoftware

udps.c


#include <stdio.h> //printf(), fprintf(), perror(), getc()
#include <sys/socket.h> //socket(), bind(), sendto(), recvfrom()
#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 MSG_FAILURE -1

#define MAX_MSGSIZE 1024
#define MAX_BUFSIZE (MAX_MSGSIZE + 1)

int  get_socket(const char *);
void sockaddr_init (const char *, unsigned short, struct sockaddr *);
int  udp_send(int, const char *, int, struct sockaddr *);
int  udp_receive(int, char *, int, struct sockaddr *);
void socket_close(int);

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

	const char *address = "";
	unsigned short port = (unsigned short)atoi(argv[1]);
	struct sockaddr servSockAddr, clitSockAddr;
	char recvBuffer[MAX_BUFSIZE];

	int server_sock = get_socket("udp");
	sockaddr_init(address, port, &servSockAddr);

	if (bind(server_sock, &servSockAddr, sizeof(servSockAddr)) < 0) {
		perror("bind() failed.");
		exit(EXIT_FAILURE);
	}
	
	while(1) {
		int recvMsgSize = udp_receive(server_sock, recvBuffer, MAX_BUFSIZE, &clitSockAddr);
		if (recvMsgSize == MSG_FAILURE) continue;

		printf("message received from %s.\n", inet_ntoa(((struct sockaddr_in *)&clitSockAddr)->sin_addr));

		int sendMsgSize = udp_send(server_sock, recvBuffer, recvMsgSize, &clitSockAddr);
		if (sendMsgSize == MSG_FAILURE) continue;
	}
}

int get_socket(const char *type) {
    int sock;

	if (strcmp(type, "udp") == 0) {
		sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
	} else if(strcmp(type, "tcp") == 0) {
		sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
	}

    if (sock < 0){
        perror("socket() failed.");
        exit(EXIT_FAILURE);
    }

    return sock;
}

void sockaddr_init (const char *address, unsigned short port, struct sockaddr *sockaddr) {

    struct sockaddr_in sockaddr_in;
    sockaddr_in.sin_family = AF_INET;

    if (inet_aton(address, &sockaddr_in.sin_addr) == 0) {
		if (strcmp(address, "") == 0 ) {
			sockaddr_in.sin_addr.s_addr = htonl(INADDR_ANY);
		} else {
			fprintf(stderr, "Invalid IP Address.\n");
			exit(EXIT_FAILURE);
		}
    }

    if (port == 0) {
        fprintf(stderr, "invalid port number.\n");
        exit(EXIT_FAILURE);
    }
    sockaddr_in.sin_port = htons(port);

    *sockaddr = *((struct sockaddr *)&sockaddr_in);
}

int udp_send(int sock, const char *data, int size, struct sockaddr *sockaddr) {
	int sendSize;
    sendSize = sendto(sock, data, size, 0, sockaddr, sizeof(*sockaddr));
    if (sendSize != size) {
        perror("sendto() failed.");
		return MSG_FAILURE;
    }
	return sendSize;
}

int udp_receive(int sock, char *buffer, int size, struct sockaddr *sockaddr) {
    unsigned int sockaddrLen = sizeof(*sockaddr);
    int receivedSize = recvfrom(sock, buffer, MAX_BUFSIZE, 0, sockaddr, &sockaddrLen);
	if (receivedSize < 0) {
        perror("recvfrom() failed.");
		return MSG_FAILURE;
	}
	
	return receivedSize;
}

void socket_close(int server) {
    if (close(server) < 0) {
        perror("close() failed.");
        exit(EXIT_FAILURE);
    }
}

Zeilen 26-27

Der Socket-Erstellungsprozess und der Adressstruktur-Initialisierungsprozess sind in Funktionen mit den Namen get_socket () bzw. sockaddr_init () unterteilt.

Sockaddr_init () trennt lediglich den vorherigen Adresseinstellungsprozess von der Hauptroutine.

In get_socket () verwenden wir den Systemaufruf socket () wie bei TCP. Der Unterschied zu TCP besteht darin, dass das zweite Argument SOCK_DGRAM zum Erstellen eines Sockets vom Typ Datagramm und das dritte Argument IPPROTO_UDP angibt, bei dem es sich um ein End-to-End-Protokoll vom Typ Datagramm handelt. Geben Sie auch hier nicht 0 an und geben Sie es an.

29. bis 32. Zeile

Wie bei TCP wird der Systemaufruf bind () verwendet, um die Adresse mit dem Socket zu verknüpfen. Im Gegensatz zu TCP werden Nachrichten jedoch so gesendet und empfangen, wie sie sind, ohne listen () oder accept () aufzurufen.

In diesem Zustand, wie im Beispiel, wenn Sie mit "netstat" prüfen, können Sie sehen, dass "udp 0 0 0.0.0.0:8080 0.0.0.0: *" angezeigt wird.

In TCP wird zum Herstellen einer Verbindung für jeden Client ein neuer Socket-Deskriptor durch den Prozess von accept () erfasst und Nachrichten werden gesendet und empfangen. Im Fall von UDP wird bind () ausgeführt, da die Verbindung nicht hergestellt wird. Senden und Empfangen von Nachrichten über den Socket.

Außerdem verwenden wir für den Client normalerweise nicht den Systemaufruf connect (). Ich benutze es normalerweise nicht, weil es benutzt werden kann.

Unter dem Namen connect () könnte man denken, dass es sich um einen reinen TCP-Systemaufruf handelt, der die Verarbeitung zum Herstellen einer Verbindung durchführt. Da connect () jedoch eine Rolle bei der Bestimmung der Adresse des Verbindungsziels beim Senden und Empfangen von Nachrichten spielt, Es kann auch für UDP verwendet werden.

Natürlich wird im Fall von TCP das Paket der SYN-Verbindungsanforderung tatsächlich an den anderen Server gesendet, so dass der Prozess zum Bestimmen der Adresse des Verbindungsziels und der Prozess zum Herstellen der Verbindung ausgeführt werden, aber im Fall von UDP Pakete für solche Verbindungsverbindungen werden nicht gesendet, und die Kommunikation über diesen Socket ist auf die Kommunikation mit dem Socket beschränkt, der der bestimmten Portnummer des Hosts mit einer bestimmten IP-Adresse zugeordnet ist.

35. bis 36. Zeile

Es ist in eine Funktion namens udp_receive () unterteilt, aber es ist der Prozess des Empfangens einer Nachricht vom Client mit dem Systemaufruf recvfrom ().

In diesem Fall blockiert es wie recv () die Programmausführung, bis das Datagramm eintrifft. Das Standardverhalten ist wie beim Systemaufruf recv () beschrieben.

Das Argument ist fast dasselbe wie der Systemaufruf recv (), aber da die Informationen des Clients, der die Quelle ist, nur beim Empfang des Datagramms abgerufen werden, wird der Zeiger auf die sockaddr-Struktur, in der die Quellinformationen gespeichert sind, als Argument im fünften Argument verwendet. Übergeben an.

Dann übergebe ich einen Zeiger, der die Länge des 6. Arguments angibt, aber beim Übergeben wird die Größe der sockaddr-Struktur gespeichert, und wenn sie vom Steuerelement von recvfrom () zurückkehrt, die tatsächlich gespeicherte Größe Beachten Sie, dass gespeichert ist.

Was den Empfangsprozess zum Benutzerprozess betrifft, werden Daten aus der Empfangswarteschlange des Socket-Moduls auf die gleiche Weise wie TCP übertragen. Da jedoch die Grenzen beibehalten und die Nachrichten einzeln gruppiert werden, erfolgt dies fälschlicherweise. Sie erhalten nicht mehrere Nachrichten.

Wenn mit recvfrom () eine Nutzlast gesendet wird, die größer als die angegebene Empfangspuffergröße ist, werden die Byte-String-Daten nach der angegebenen Größe verworfen.

Daher ist es in UDP erforderlich, die Größe des durch Recevfrom () gesicherten Empfangspuffers im Voraus entsprechend den Anwendungsanforderungen ausreichend groß zu machen. Sie benötigen mindestens eine Größe, die größer ist als die von sendto () gesendete Größe.

Übrigens beträgt die maximale Größe der Nutzdaten des UDP-Datagramms 65507 (65535 (IP-Paketlänge) -20 (IP-Header-Länge) -8 (UDP-Header-Länge)) Bytes, da IP ein niedrigeres Protokoll ist. Wenn es jedoch im Netzwerk gesendet wird, wird das IP-Datagramm aufgrund der Einschränkung der übertragbaren Nutzlastgröße der Datenverbindung (1500 Byte bei MTU * Ethernet) geteilt und fließt durch das Netzwerk. ..

Wenn beim Kopieren einer Nachricht in den Empfangspuffer kein Fehler auftritt, wird die Verarbeitung unverändert fortgesetzt.

Zeile 38

Zeigt die Quell-IP-Adresse aus den in clitSockAddr gespeicherten Quellinformationen an.

40. bis 41. Zeile

Es ist in eine Funktion namens udp_send () unterteilt, aber der Systemaufruf sendto () sendet eine Nachricht vom Client.

Von recvfrom () hat sendto () eine ähnliche Spezifikation wie der ähnliche send () -Systemaufruf und soll zusätzlich die Adressinformationen des Zielhosts gemeinsam weitergeben. Die Adressinformationen des Zielhosts verwenden die von recvfrom () erhaltene Struktur.

Und im Gegensatz zu TCP wird es aufgrund eines Fehlers nicht erneut gesendet, sodass die zu sendenden Daten nicht im Puffer aufbewahrt werden müssen. Wenn die Steuerung von sendto () zurückgegeben wird, wurden die Daten daher bereits an das untere Modul übergeben.

Darüber hinaus wird der Maximalwert des UDP-Sendepuffers häufig vom System festgelegt. Wenn Sie also einen großen Puffer senden möchten, müssen Sie einen separaten Systemaufruf verwenden, um die Socket-Einstellungen zu ändern. Ich werde diesen Systemaufruf noch einmal einführen.

Sie müssen nicht wiederholt Daten senden und empfangen, bis die Verbindung wie in TCP geschlossen und die Datagrammnachrichten begrenzt sind. Sendto () und recvfrom () werden also beide einmal gesendet, wenn sie nicht verworfen werden. Es ist möglich, die entsprechende Nachricht zu erhalten, indem Sie sie einzeln ausführen. Die Bestellung ist jedoch nicht garantiert.

Wenn Sie connect () verwenden, können Sie übrigens die Systemaufrufe send () und recv () wie bei TCP verwenden. Natürlich unterscheidet sich das Sende- / Empfangsprinzip von TCP.

Und da UDP ein Dienst auf der besten Seite ist, ist es nicht immer möglich, Nachrichten zu empfangen. Daher fügt der folgende Clientcode einen einfachen Neuübertragungsprozess auf Anwendungsebene hinzu.

Quellcode der Client-Software

udpc.c


#include <stdio.h> //printf(), fprintf(), perror(), getc()
#include <sys/socket.h> //socket(), sendto(), recvfrom()
#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()
#include <signal.h> //sigatcion()
#include <errno.h> //erron, EINTR

#define MSG_FAILURE -1
#define MAX_MSGSIZE 1024
#define MAX_BUFSIZE (MAX_MSGSIZE + 1)
#define MAX_TRIES 5
#define TIMEOUT_SECOND 2

int  get_socket(const char *);
void sockaddr_init (const char *, unsigned short, struct sockaddr *);
int  udp_send(int, const char *, int, struct sockaddr *);
int  udp_receive(int, char *, int, struct sockaddr *);
void socket_close(int);
int  input(char *, int);
void remove_lf(char *, int);
void sigaction_init(struct sigaction *, void (*)(int));
void catchAlarm(int);
int  udp_try_receive (int, struct sockaddr *, struct sockaddr *, char *, int, char *);
int  check_correct_server (struct sockaddr *, struct sockaddr *);

int intTries = 0;

int main(int argc, char* argv[]) {
    if (argc != 3) {
        fprintf(stderr, "argument count mismatch error.\n");
        exit(EXIT_FAILURE);
    }

    const char *address = argv[1];
    unsigned short port = (unsigned short)atoi(argv[2]);
    struct sockaddr servSockAddr, clitSockAddr;
	struct sigaction action;

    int server_sock = get_socket("udp");
	sockaddr_init(address, port, &servSockAddr);

	sigaction_init(&action, catchAlarm);
	if (sigaction(SIGALRM, &action, NULL) < 0) {
		perror("sigaction() failure");
		exit(EXIT_FAILURE);
	}

    while(1){
        char sendBuffer[MAX_BUFSIZE];
        char receiveBuffer[MAX_BUFSIZE];
        int inputSize = input(sendBuffer, MAX_BUFSIZE);

        if (strcmp(sendBuffer, "quit\n") == 0) {
            socket_close(server_sock);
			break;
        }

		int receivedSize = udp_try_receive(server_sock, &servSockAddr, &clitSockAddr, sendBuffer, inputSize, receiveBuffer);

		if (check_correct_server(&servSockAddr, &clitSockAddr) == -1) {

			continue;
		}

		remove_lf(receiveBuffer, receivedSize);

        printf("server return: %s\n", receiveBuffer);
    }

	return EXIT_SUCCESS;
}

int get_socket(const char *type) {
    int sock;

	if (strcmp(type, "udp") == 0) {
		sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
	} else if(strcmp(type, "tcp") == 0) {
		sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
	}

    if (sock < 0){
        perror("socket() failed.");
        exit(EXIT_FAILURE);
    }

    return sock;
}

void sockaddr_init (const char *address, unsigned short port, struct sockaddr *sockaddr) {

    struct sockaddr_in sockaddr_in;
    sockaddr_in.sin_family = AF_INET;

    if (inet_aton(address, &sockaddr_in.sin_addr) == 0) {
		if (strcmp(address, "") == 0 ) {
			sockaddr_in.sin_addr.s_addr = htonl(INADDR_ANY);
		} else {
			fprintf(stderr, "Invalid IP Address.\n");
			exit(EXIT_FAILURE);
		}
    }

    if (port == 0) {
        fprintf(stderr, "invalid port number.\n");
        exit(EXIT_FAILURE);
    }
    sockaddr_in.sin_port = htons(port);

    *sockaddr = *((struct sockaddr *)&sockaddr_in);
}

int udp_send(int sock, const char *data, int size, struct sockaddr *sockaddr) {
	int sendSize;
    sendSize = sendto(sock, data, size, 0, sockaddr, sizeof(*sockaddr));
    if (sendSize != size) {
        perror("sendto() failed.");
		return MSG_FAILURE;
    }
	return sendSize;
}

int udp_receive(int sock, char *buffer, int size, struct sockaddr *sockaddr) {
    unsigned int sockaddrLen = sizeof(*sockaddr);
    int receivedSize = recvfrom(sock, buffer, MAX_BUFSIZE, 0, sockaddr, &sockaddrLen);
	if (receivedSize < 0) {
        perror("recvfrom() failed.");
		return MSG_FAILURE;
	}
	
	return receivedSize;
}

void socket_close(int server) {
    if (close(server) < 0) {
        perror("close() failed.");
        exit(EXIT_FAILURE);
    }
}

int input(char *buffer, int size) {
    printf("please enter the characters:");

    if (fgets(buffer, size, stdin) == NULL){
        fprintf(stderr, "invalid input string.\n");
        exit(EXIT_FAILURE);
    }

	//flush the stdin buffer
	if (buffer[strlen(buffer)-1] != '\n') {
		int c;
		while((c = getc(stdin) != '\n') && (c != EOF)){}
	}
	
    return strlen(buffer);
}

void remove_lf(char *buffer, int bufferSize) {
	buffer[bufferSize-1] = '\0';
}

void catchAlarm (int ignored) {
	intTries += 1;
}

void sigaction_init(struct sigaction *action, void (*handler)(int) ) {
	action->sa_handler = handler;
	if (sigfillset(&(action->sa_mask)) < 0) {
		perror("sigfillset() failure");
		exit(EXIT_FAILURE);
	}
	action->sa_flags = 0;
}

int udp_try_receive (int sock, struct sockaddr *servSockAddr, struct sockaddr *clitSockAddr,  char *sendBuffer, int sendSize, char *receiveBuffer) {

    int sendedSize = udp_send(sock, sendBuffer, sendSize, servSockAddr);
	int receivedSize;
	while (1) {
		alarm(TIMEOUT_SECOND);
		receivedSize = udp_receive(sock, receiveBuffer, MAX_BUFSIZE, clitSockAddr);
		if (receivedSize == MSG_FAILURE) {
			if (errno == EINTR) {
				if (intTries <= MAX_TRIES) {
					printf("timed out %d.\n", intTries);
					sendedSize = udp_send(sock, sendBuffer, sendSize, servSockAddr);
					if (sendedSize == MSG_FAILURE) break;
					alarm(TIMEOUT_SECOND);
					continue;
				} else {
					printf("total timed out %d.\n", MAX_TRIES);
					exit(EXIT_FAILURE);
				}
			} else {
				exit(EXIT_FAILURE);
			}
		}
		break;
	}
	alarm(0);

	return receivedSize;
}

int check_correct_server (struct sockaddr *sockaddr_1, struct sockaddr *sockaddr_2) {
	if( ((struct sockaddr_in *)sockaddr_1)->sin_addr.s_addr != ((struct sockaddr_in *)sockaddr_2)->sin_addr.s_addr ) {
		fprintf(stderr, "reveiceid from unknown server.\n");
	} else if (ntohs(((struct sockaddr_in *)sockaddr_1)->sin_port) != ntohs(((struct sockaddr_in *)sockaddr_2)->sin_port)) {
		fprintf(stderr, "reveiceid from unknown port.\n");
	} else {
		return EXIT_SUCCESS;
	}

	return MSG_FAILURE;
}

Zeilen 41-42

Erstellen Sie wie beim Server einen Socket und initialisieren Sie die Adressstruktur. Normalerweise müssen Sie bind () aus den gleichen Gründen nicht aufrufen wie für TCP-Clients.

Zeilen 44-47

Dies ist einer der Punkte dieses UDP-Programms, und wir bereiten uns darauf vor, das Zeitlimit der UDP-Übertragung durch das Signal zu erkennen. Die Verarbeitung ist in eine Funktion namens sigaction_init () unterteilt, der Verarbeitungsinhalt besteht jedoch darin, eine Sigaktionsstruktur zu erstellen.

Übergeben Sie einen Zeiger auf diese Struktur an den Systemaufruf sigaction (), um die Standardaktion zu ändern, wenn SIGALRM an den Prozess benachrichtigt wird. Wenn dieser Änderungsprozess weggelassen wird und der Standardprozess ausgeführt wird, endet das Programm bei diesem Programm in dem Moment, in dem SIGALRM dem Prozess benachrichtigt wird.

Um kurz zu erklären, was passiert, wenn SIGALRM vom Betriebssystem an einen Prozess benachrichtigt wird, wird die Funktion catchAlarm () ausgeführt und blockiert während dieser Zeit andere Signale. Während die Funktion catchAlarm () ausgeführt wird, wird der Vorgang beispielsweise durch Drücken von Strg + C (SIGINT) nicht beendet.

Obwohl Signale eng mit der Netzwerkprogrammierung verbunden sind, möchte ich einen weiteren Artikel über Signale selbst veröffentlichen.

Zeilen 50 bis 70

Ich habe früher im Fall des Clients über sendto () und recvfrom () geschrieben, aber die Funktion udp_try_receive () in der 60. Zeile ist eine Reihe von Unterroutinen zum Senden und Empfangen der Verarbeitung, die die Timeout-Verarbeitung erkennen.

Verwenden Sie zunächst den Systemaufruf alarm (), damit das Betriebssystem nach 2 Sekunden SIGALRM sendet. Wenn recvfrom () die Nachricht nicht empfangen kann, wartet es standardmäßig auf die Verarbeitung des Programms. Diesmal wird SIGALRM jedoch nach ca. 2 Sekunden an den Prozess gesendet und gibt -1 zurück, was einen Fehler bedeutet. Wenn Sie die Socket-API durch Kombinieren von Signalen verwenden möchten, müssen Sie Ihr Verständnis für die Reihe von Prozessen vertiefen.

Und da EINTR als errno gesetzt ist, was einen Fehlercode bedeutet, wird die Verarbeitung basierend auf dieser Bedingung durchgeführt und die Verarbeitung der erneuten Übertragung wird durchgeführt, bis MAX_TRIES erreicht ist. Wir garantieren nicht die Zuverlässigkeit, aber die Anwendung führt so etwas wie eine TCP-Neuübertragungsverarbeitung durch.

Wenn dies nicht funktioniert, wird das Programm einmal beendet, vorausgesetzt, dass etwas nicht stimmt. Dadurch wird verhindert, dass das Programm endlos blockiert wird.

Wenn Sie die Nachricht ohne Wartezeit empfangen können, geben Sie 0 für das Argument alarm () an, um den Alarm auszuschalten.

Zeile 62

Wir haben eine Routine hinzugefügt, um zu überprüfen, ob die Quelle der Funktion check_correct_server () korrekt ist.

Dadurch wird sichergestellt, dass der Client von dem Host und der Anwendung antwortet, die das Paket gesendet haben. Es ist sehr unwahrscheinlich, dass ein Paket von einem anderen Host stammt, aber es sollte enthalten sein, solange dies praktisch möglich ist.

Zeile 67

Da das Ende der Nachricht ein Zeilenumbruch sein sollte, haben wir einen Prozess hinzugefügt, um den Zeilenumbruch in ein Nullzeichen umzuwandeln.

Das nächste Mal möchte ich eine Methode betrachten, die neben TCP-Sockets auch die Mehrprozessverarbeitung kombiniert. Statt der Socket-Programmierung wird das Konzept der Mehrprozessverarbeitung jedoch zu einem wichtigen Bestandteil, also Multi Werfen wir einen Blick auf den Prozess.

Nachschlagewerk

Recommended Posts

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-Sprache 3. TCP-Server / Client 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
Richten Sie einen UDP-Server in der Sprache C ein
Beispiel-API-Server, der JSON in Golang empfängt
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
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 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 !! '