Socket-Programmierung: Netzwerkkommunikation zwischen Client und Server basierend auf dem TCP-Protokoll

1. Servererstellungsprozess

  1. Rufen Sie die Socket-Funktion auf, um einen Listening-Socket zu erstellen.
  2. Rufen Sie die Bindungsfunktion auf, um den Socket an ein Zwei-Tupel bestehend aus einer IP-Adresse und einer Portnummer zu binden.
  3. Rufen Sie die Listen-Funktion auf, um mit dem Zuhören zu beginnen.
  4. Wenn eine Client-Verbindungsanforderung vorliegt, rufen Sie die Accept-Funktion auf, um die Verbindung zu akzeptieren und einen neuen Socket (Socket für die Kommunikation mit dem Client) zu generieren.
  5. Rufen Sie die Sende- oder Empfangsfunktion basierend auf dem neu generierten Socket auf, um den Datenaustausch mit dem Client zu starten.
  6. Rufen Sie nach Abschluss der Kommunikation die Schließfunktion auf, um den Socket zu schließen.

2. Client-Erstellungsprozess

  1. Rufen Sie die Socket-Funktion auf, um einen Client-Socket zu erstellen.
  2. Rufen Sie die Verbindungsfunktion auf, um zu versuchen, eine Verbindung zum Server herzustellen.
  3. Rufen Sie nach erfolgreicher Verbindung die Funktion send oder recv auf, um mit dem Server zu kommunizieren.
  4. Rufen Sie nach Abschluss der Kommunikation die Schließfunktion auf, um den Socket zu schließen.

3. Gemeinsame APIs

struct sockaddr und struct sockaddr_in

Bei beiden Strukturen handelt es sich um Adressen, die zur Abwicklung der Netzwerkkommunikation verwendet werden .

/*
 *此数据结构用做bind、connect、recvfrom、sendto等函数的参数,指明地址信息
 *note:
 *  目标地址和端口信息在一起
 */
#include <sys/socket.h>

struct sockaddr
{
    
    
    // 地址家族,一般“AF_xxx”的形式,通常使用AF_INET
    unsigned short sa_family;
    // 14字节协议地址,目标地址和端口信息
    char           sa_data[14];
}
#include <netinet/in.h>

struct  sockaddr_in 
{
    
    
    short int       sin_family;       //协议族
    unsigned short  int  sin_port;    //端口号(使用网络字节顺序)
    struct in_addr  sin_addr;         //IP地址        
    unsigned char   sin_zero[8];      //sockaddr与sockaddr_in 保持大小相同而保留的空字节
};

struct  in_addr 
{
    
    
    unsigned  long  s_addr;
};

typedef struct in_addr 
{
    
    
    union
    {
    
    
        struct
        {
    
    
            unsigned char s_b1,
                          s_b2,
                          s_b3,
                          s_b4;
        } S_un_b;

        struct 
        {
    
    
            unsigned short s_w1,
                           s_w2;
        } S_un_w;

        unsigned long S_addr;
    } S_un;
} IN_ADDR;

sockaddr_in und sockaddr sind parallele Strukturen, und der Zeiger auf die Struktur sockaddr_in kann auch auf die Struktur sockraddr verweisen und diese ersetzen.

struct sockaddr_in mysock;

bind(sockfd, (struct sockaddr *)&mysock, sizeof(struct sockaddr); /* bind的时候进行转化 */

net.core.somaxconn

net.core.somaxconn ist ein Kernel-Parameter in Linux, der die Obergrenze des Rückstands der Socket-Überwachung (Listen) angibt. Das Backlog ist die Abhörwarteschlange des Sockets. Wenn eine Anfrage (Anfrage) nicht verarbeitet oder eingerichtet wurde, wird sie in das Backlog eingetragen. Der Socket-Server kann alle Anforderungen im Backlog gleichzeitig verarbeiten und die verarbeiteten Anforderungen befinden sich nicht mehr in der Überwachungswarteschlange. Wenn der Server Anfragen so langsam verarbeitet, dass die Überwachungswarteschlange gefüllt ist, werden neue eingehende Anfragen abgelehnt.

In Hadoop 1.0 steuert der Parameter ipc.server.listen.queue.size die Länge der Überwachungswarteschlange des Server-Sockets, dh die Backlog-Länge, und der Standardwert ist 128. Der Standardwert des Linux-Parameters net.core.somaxconn ist ebenfalls 128. Wenn der Server ausgelastet ist, z. B. NameNode oder JobTracker, reicht 128 nicht aus. Auf diese Weise muss der Rückstand erhöht werden. Beispielsweise setzt unser 3000-Einheiten-Cluster ipc.server.listen.queue.size auf 32768. Damit der gesamte Parameter den erwarteten Effekt erzielt, muss er auch festgelegt werden der Kernel-Parameter net.core.somaxconn auf einen A-Wert größer oder gleich 32768.

Unter Linux können mit dem Tool syctl alle Kernel-Parameter dynamisch angepasst werden. Die sogenannte dynamische Anpassung soll unmittelbar nach der Änderung des Kernel-Parameterwerts wirksam werden. Dies wird jedoch nur auf Betriebssystemebene wirksam. Für Hadoop muss die Anwendung neu gestartet werden, um wirksam zu werden.

Zeigt alle Kernel-Parameter und -Werte an

sysctl -a

Syntax zum Ändern von Parameterwerten

sysctl -w net.core.somaxconn=32768

Der obige Befehl ändert den Wert des Kernel-Parameters net.core.somaxconn in 32768. Obwohl solche Änderungen sofort wirksam werden können, werden die Standardwerte nach einem Neustart der Maschine wiederhergestellt. Um die Änderungen dauerhaft zu behalten, müssen Sie in /etc/sysctl.conf eine Zeile mit vi hinzufügen

net.core.somaxconn=4000

Führen Sie dann den Befehl aus

sysctl -p

int socket(int-Familie, int-Typ, int-Protokoll);

  • Rolle: Erstellen Sie einen Socket-Deskriptor. In einem Linux-System ist alles eine Datei. Um geöffnete Dateien darzustellen und zu unterscheiden, weist Unix/Linux der Datei eine ID zu, die eine Ganzzahl ist und als Dateideskriptor bezeichnet wird. Daher ist eine Netzwerkverbindung auch eine Datei, die auch über Dateideskriptoren verfügt. Erstellen Sie eine Netzwerkverbindung oder öffnen Sie eine Netzwerkdatei über die Funktion socket(). Der Rückgabewert der Funktion socket() ist ein Dateideskriptor. Über diesen Dateideskriptor können wir normale Dateioperationen zum Übertragen von Daten verwenden.

  • Parameter:

    Familie: Gibt die Protokollfamilie/-domäne an, normalerweise AF_INET, AF_INET6, AF_LOCAL usw.;
    Typ: Socket-Typ, hauptsächlich SOCK_STREAM, SOCK_DGRAM, SOCK_RAW usw.;
    Protokoll: im Allgemeinen auf 0 gesetzt.

  • Rückgabewert: ok (nicht negativ), Fehler (-1). Bei Erfolg wird ein kleiner, nicht negativer ganzzahliger Wert zurückgegeben, ähnlich einem Dateideskriptor.

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

  • Funktion: Der Server bindet die für die Kommunikation verwendete Adresse und den Port an den Socket.

  • Parameter:

    sockfd: stellt den Socket dar, der gebunden werden muss, dh den Dateideskriptor, der beim Erstellen des Sockets zurückgegeben wird;
    addr: speichert die Adresse und den Port, die der Server für die Kommunikation verwendet;
    addrlen: stellt die Größe der addr-Struktur dar.

  • Rückgabewert: Wenn die Bindefunktion 0 zurückgibt, ist die Bindung korrekt; wenn sie -1 zurückgibt, bedeutet dies, dass die Bindung fehlgeschlagen ist.

int listen(int sockfd, int backlog);

  • Funktion: Die Funktion der Listen-Funktion besteht nicht darin, auf die Ankunft einer neuen Verbindung zu warten, es ist die Accept-Funktion, die wirklich auf die Verbindung wartet. Die Funktionsweise von Listen besteht darin, dass die Verbindungsverbindung in der Warteschlange zwischengespeichert wird, wenn mehr Clients eine Verbindung initiieren und der Server die Verbindungsanforderung nicht rechtzeitig verarbeiten kann. Die Länge dieser Warteschlange wird durch den Backlog-Parameter in Listen festgelegt.

  • Parameter:

    sockfd: Der vom vorherigen Socket erstellte Dateideskriptor;
    Rückstand: Bezieht sich auf die maximale Anzahl von Verbindungen, die der Server zwischenspeichern kann, dh auf die Länge der Warteschlange.

  • Rückgabewert: Wenn Listen erfolgreich ausgeführt wird, wird 0 zurückgegeben. Wenn die Ausführung fehlschlägt, wird -1 zurückgegeben.

int akzeptieren(int sockfd, struct sockaddr *client_addr, socklen_t *addrlen);

  • Funktion: Die Accept-Funktion wartet auf die Verbindung des Clients. Wenn kein Client eine Verbindung herstellt, wartet sie ewig. Diese Methode wird als Blockieren bezeichnet. Nachdem Accept auf die Verbindung vom Client gewartet hat, wird ein neuer Socket erstellt. Der Rückgabewert der Funktion ist dieser neue Socket. Der Server verwendet diesen neuen Socket, um Nachrichten mit dem Client zu senden und zu empfangen.

  • Parameter:

    sockfd: Der Socket, der abgehört wurde;
    client_addr: Wird zum Speichern der Adressinformationen des Clients verwendet, einschließlich der Protokollfamilie, der Netzwerkadresse und der Portnummer des Clients. Wenn Sie die Adresse des Clients nicht benötigen, können Sie 0 eingeben;
    addrlen: Wird zum Speichern der Länge des zweiten Parameters (client_addr) verwendet.

  • Rückgabewert: ok (neues fd), Fehler (-1).

int connect(int sock_fd, struct sockaddr *serv_addr, int addrlen);

  • Rolle: Der Client initiiert eine Verbindungsanfrage an den Server.

  • Parameter:

    sock_fd: stellt den von der Funktion socket() zurückgegebenen Dateideskriptor dar;
    serv_addr: stellt die Protokollfamilie, Netzwerkadresse und Portnummer des Zielservers dar, bei dem es sich um einen Zeiger vom Typ sockaddr handelt;
    addrlen: stellt die Größe des zweiten Parameterinhalts dar.

  • Rückgabewert: Wenn der Rückgabewert 0 ist, bedeutet dies, dass die Verbindung erfolgreich ist; wenn der Rückgabewert -1 ist, bedeutet dies, dass die Verbindung fehlschlägt.

int recv(int sockfd, void *buf, int len, int flags);

  • Funktion: Die Recv-Funktion wird zum Empfangen der vom Peer-Socket gesendeten Daten verwendet. Unabhängig davon, ob es sich um einen Client oder einen Server handelt, verwendet die Anwendung die Recv-Funktion, um Daten zu empfangen, die vom anderen Ende der TCP-Verbindung gesendet werden. Wenn der Socket-Peer keine Daten sendet, wartet die Funktion recv. Wenn der Peer Daten sendet, gibt die Funktion die Anzahl der empfangenen Zeichen zurück.

  • Parameter:

    sockfd: stellt den Socket-Deskriptor des empfangenden Endes dar, dh den von der Funktion socket () zurückgegebenen Dateideskriptor;
    buf: die zum Empfangen von Daten verwendete Speicheradresse, bei der es sich um die Adresse einer Basisdatentypvariablen der C-Sprache handeln kann, oder ein Array- oder Strukturkörper, eine Zeichenfolge;
    len: gibt die Anzahl der zu empfangenden Datenbytes an. Die Größe von buf darf nicht überschritten werden, da sonst der Speicher überläuft.
    Flags: Im Allgemeinen auf 0 gesetzt, andere Werte haben keine große Bedeutung.

  • Rückgabewert: Wenn es fehlschlägt, ist der Rückgabewert kleiner als 0; wenn das Zeitlimit überschritten wird oder der Peer aktiv geschlossen wird, ist der Rückgabewert gleich 0; wenn es erfolgreich ist, ist der Rückgabewert die Länge der empfangenen Daten.

int send(int sockfd, const void *buf, int len, int flags);

  • Funktion: Mit der Sendefunktion werden Daten über den Socket an den Peer gesendet. Unabhängig davon, ob es sich um einen Client oder einen Server handelt, verwendet die Anwendung die Sendefunktion, um Daten an das andere Ende der TCP-Verbindung zu senden.

  • Parameter:

    sockfd: stellt den Socket-Deskriptor des sendenden Endes dar, dh den von der Funktion socket () zurückgegebenen Dateideskriptor;
    buf: gibt die Speicheradresse der zu sendenden Daten an, bei der es sich um die Adresse eines Basisdatentyps der C-Sprache handeln kann Variable oder ein Array- oder Strukturkörper, eine Zeichenfolge;
    len: gibt die Anzahl der tatsächlich gesendeten Datenbytes an;
    Flags: im Allgemeinen auf 0 gesetzt, andere Werte haben wenig Bedeutung.

  • Rückgabewert: Wenn es fehlschlägt, ist der Rückgabewert kleiner als 0; wenn es zu einer Zeitüberschreitung kommt oder der Peer aktiv schließt, ist der Rückgabewert gleich 0; wenn es erfolgreich ist, ist der Rückgabewert die Länge der gesendeten Daten.

int close(int sockfd);

  • Funktion: Den Socket schließen und die TCP-Verbindung beenden.

  • Parameter:

    sockfd: Socket-Deskriptor.

  • Rückgabewert: ok(0), Fehler(-1).

int inet_pton(int af, const char *src, void *dst);

  • Funktion: Textadresse in Binäradresse umwandeln.

  • Header-Datei: #include <arpa/inet.h>

  • Parameter:

    af: Familie ansprechen. AF_INET: IPv4-Adresse; AF_INET6: IPv6-Adresse;
    src: Zeiger auf „gepunktete“ IPv4- oder IPv6-Adresse, z. B. „192.168.1.100“;
    dst: Zeiger vom Typ struct in_addr * oder struct in6_addr *.

  • Rückgabewert: Erfolg: 1; Fehler: 0 bedeutet, dass die Adresse nicht mit der Adressfamilie übereinstimmt, -1 bedeutet, dass die Adresse illegal ist.

const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);

  • Funktion: Binäradresse in Textadresse umwandeln.

  • Header-Datei: #include <arpa/inet.h>

  • Parameter:

    af: Familie ansprechen. AF_INET: IPv4-Adresse; AF_INET6: IPv6-Adresse;
    src: Zeiger vom Typ struct in_addr * oder struct in6_addr *;
    dst: Adresspufferzeiger;
    Größe: Adresspuffergröße, mindestens INET_ADDRSTRLEN oder INET6_ADDRSTRLEN Bytes.

  • Rückgabewert: Erfolg: dst; Fehler: NULL.

Funktion zur Konvertierung der Bytereihenfolge

  • Funktionsprototyp:

    uint32_t htonl(uint32_t hostlong);
    uint16_t htons(uint16_t hostshort);
    uint32_t ntohl(uint32_t netlong);
    uint16_t ntohs(uint16_t netshort);

  • Parameter:
    hostlong: lange Integer-Daten in Host-Byte-Reihenfolge;
    hostshort: kurze Integer-Daten in Host-Byte-Reihenfolge;
    netlong: lange Integer-Daten in Netzwerk-Byte-Reihenfolge;
    netshort: kurze Integer-Daten in Netzwerk-Byte-Reihenfolge.

  • Rückgabewert: die entsprechenden Bytereihenfolgedaten.

4. Servercode

#include <iostream>
#include <unistd.h>		// POSIX系统API访问
#include <sys/types.h>	// 基本系统数据类型
#include <arpa/inet.h>	// 网络信息转换
#include <string.h>

using namespace std;

#define SERVER_PORT 9991

int main() {
    
    
    // 创建一个监听socket
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);	

    if (listenfd == -1) {
    
    
        cout << "create listen socket error!" << endl;
        return -1;
    }

    // 初始化服务器地址
    struct sockaddr_in bindaddr;
    bindaddr.sin_family = AF_INET;
    bindaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    bindaddr.sin_port = htons(SERVER_PORT);
	//如果只想在本机上进行访问,bind函数地址可以使用本地回环地址
	//如果只想被局域网的内部机器访问,那么bind函数地址可以使用局域网地址
	//如果希望被公网访问,那么bind函数地址可以使用INADDR_ANY or 0.0.0.0

    // 绑定地址和端口
    if (bind(listenfd, (struct sockaddr *)&bindaddr, sizeof(bindaddr)) == -1) {
    
    
        cout << "bind listen socket error!" << endl;
        return -1;
    }

    // 启动监听
    if (listen(listenfd, SOMAXCONN) == -1) {
    
    
        cout << "listen error!" << endl;
        return -1;
    }
	cout << "start listening..." << endl;

    while (true) {
    
    
        // 创建一个临时的客户端socket
        struct sockaddr_in clientaddr;
        socklen_t clientaddrlen = sizeof(clientaddr);

        // 接受客户端连接
        int clientfd = accept(listenfd, (struct sockaddr *)&clientaddr, &clientaddrlen);
        if (clientfd != -1) {
    
    
            char recvBuf[32] = {
    
    0};
            // 从客户端接受数据
            int ret = recv(clientfd, recvBuf, sizeof(recvBuf), 0);
            if (ret > 0) {
    
    
                cout << "recv data from cilent successfully, data:" << recvBuf << endl;
                // 将接收到的数据原封不动地发给客户端
                ret = send(clientfd, recvBuf, strlen(recvBuf), 0);
                if (ret != strlen(recvBuf)) {
    
    
                    cout << "send data error!" << endl;
                } else {
    
    
                    cout << "send data to client successfully, data:" << recvBuf <<endl;
                }
            } else {
    
    
                cout << "recv data error!" <<endl;
            }
        }
        close(clientfd);
    }

    // 关闭监听socket
    close(listenfd);
    return 0;
}

5. Client-Code

#include <iostream>
#include <unistd.h>		// POSIX系统API访问
#include <sys/types.h>	// 基本系统数据类型
#include <arpa/inet.h>	// 网络信息转换
#include <string.h>

using namespace std;

#define SERVER_ADDRESS "127.0.0.1"
#define SERVER_PORT 9991

int main(int argc, char* argv[]) {
    
    
    char* message;
    if(argc != 2) {
    
    
        fputs("Usage: ./client message\n", stderr); // 向指定的文件写入一个字符串
        exit(1);
    }

    message = argv[1];
    printf("send message: %s\n", message);

    // 创建一个socket
    int clientfd = socket(AF_INET, SOCK_STREAM, 0);
    if (clientfd == -1) {
    
    
        cout << "create client socket error!" << endl;
        return -1;
    }

    // 连接服务器地址
    struct sockaddr_in serveraddr;
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_addr.s_addr = inet_addr(SERVER_ADDRESS);
    serveraddr.sin_port = htons(SERVER_PORT);

    if (connect(clientfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) == -1) {
    
    
        cout << "connect socket error!" << endl;
        return -1;
    }

    // 向服务器发送数据
    
    int ret = send(clientfd, message, strlen(message), 0);
    if (ret != strlen(message)) {
    
    
        cout << "send data error!" << endl;
        return -1;
    } else {
    
    
        cout << "send data to server successfully, data:" << message << endl;
    }

    // 从服务器读取数据
    char recvBuf[32] = {
    
    0};
    ret = recv(clientfd, recvBuf, sizeof(recvBuf), 0);
    if (ret > 0) {
    
    
        cout << "recv data from server successfully, data:" << recvBuf << endl;
    } else {
    
    
        cout << "recv data from server error!" << endl;
    }

    // 关闭socket
    close(clientfd);
    return 0;
}

Guess you like

Origin blog.csdn.net/crossoverpptx/article/details/132237821