LIPC, Unix Domain Sockets

Teil 6 von 7 der Serie Sockets

Wenn sich Server und Client auf dem selben Host befinden, haben Unix Domain Sockets die höchste Performance im Vergleich zu anderen IPC Techniken. Andere Bezeichnungen sind IPC Socket oder in POSIX LIPC.

Im folgenden Code wird die Socket-Verbindung über das Loopback-Interface (localhost) getestet. Der Server wartet auf eingehende Verbindungen. Es wird ein kurzer Text übertragen und die Verbindung beendet. Der Client stresst den Server eine Sekunde lang mit Verbindungen, ließt jeweils den übertragenen Text und zählt wie oft Verbindungen zustande gekommen sind.

#!/usr/bin/env python
# IP-Server

import socket

port = 6400
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(('127.0.0.1', port))
s.listen(0)

print 'Server lauscht auf ' + str(port)

while True:
    c, _ = sock.accept()
    c.send('Welcher Tag kommt nach Freitag?')
    c.close()
#!/usr/bin/env python
# IP-Client

import socket
import time

ende = time.time() + 1
msg = 0

print 'Client läuft...'

while True:
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect(('127.0.0.1', 4711))
    data = s.recv(1024)
    msg += 1
    s.close()
    if time.time() > ende:
        break

print '{} Nachrichten empfangen'.format(msg)

Die selbe Funktionalität mit Unix-Domain-Sockets zeigt deutlich mehr Durchsatz. Der wesentliche, entscheidende Unterschied ist die Adress-Familie AF_UNIX statt AF_INET und dass eben eine IPC Socket-Datei statt einer IP-Adresse mit Port verwendet wird.

#!/usr/bin/env python
# UDS-Server

import os
import socket

server = 'server.sock'

os.remove(server) if os.path.exists(server) else None

s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
s.bind(server)
s.listen(0)

print 'Server lauscht auf ' + server;

while True:
    conn, _ = s.accept()
    conn.send('Welcher Tag kommt nach Freitag?')
    conn.close()
#!/usr/bin/env python
# UDS-Client

import socket
import time

end = time.time() + 1
count = 0

print 'UDS-Client laeuft...'

while True:
    s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
    s.connect('server.sock')
    msg = s.recv(1024)
    count += 1
    s.close()
    if time.time() > end:
        break

print '{} Nachrichten empfangen'.format(count)

Wow! 65000 vs 15000 – Das ist doch mal ne Hausnummer. Doch das ist noch nicht das Ende der Fahnenstange. Wenn man sich aufraffen kann und sich das ganze in C/C++ anschaut, gibt es nochmal richtig einen drauf.

// UDS Server
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/un.h>
#include <sys/socket.h>

int main(void)
{
    char buffer[256];
    memset(buffer, 0, 256);

    const char* ipc_socket_file = "server.sock";

    const char* data = "Hallo, wie spät ist es bitte?";
    int datalen = strlen(data);

    int server = socket(AF_UNIX, SOCK_STREAM, 0); // Erzeuge UDS

    // Socket konfigurieren
    struct sockaddr_un server_sockaddr;
    int len = sizeof(server_sockaddr);
    memset(&server_sockaddr, 0, len);
    server_sockaddr.sun_family = AF_UNIX;
    strcpy(server_sockaddr.sun_path, ipc_socket_file);

    unlink(ipc_socket_file); // Vorhandenen IPC-Socket loeschen

    bind(server, (struct sockaddr *) &server_sockaddr, len);
    listen(server, 5);

    fprintf(stdout, "Server listening on %s...\n", ipc_socket_file);

    while( true )
    {
        struct sockaddr_un client_sockaddr;
        memset(&client_sockaddr, 0, sizeof(struct sockaddr_un));
        int client = accept(server, (struct sockaddr *) &client_sockaddr, (socklen_t*) &len);

        if (client == -1) // Bei mir noch nie vorgekommen
        {
            fprintf(stderr, "Fehler: %s\n", strerror(errno));
            close(server);
            close(client);
            exit(1);
        }

        // Sende ein paar Daten
        int rc = send(client, data, datalen, 0);
        if (rc == -1) // -1 ist fatal
        {
            fprintf(stderr, "ERROR: %s\n", strerror(errno));
            close(server);
            close(client);
            exit(1);
        }
        close(client);
    }

    // Hier kommen wir nur im Fehlerfall her, aber was soll's
    close(server);
    return 0;
}
// UDS Client
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <chrono>

int main(int, char**)
{
    char buffer[256];

    struct sockaddr_un server_sockaddr;

    int len = sizeof(server_sockaddr);
    int count = 0;
    int empfangen = 0;
    int readlen = 0;

    auto start = std::chrono::high_resolution_clock::now();
    auto now = start;
    fprintf(stdout, "Client beginnt den Server zu stressen...\n");
    while(++count)
    {
        int client = socket(AF_UNIX, SOCK_STREAM, 0);

        // Baue UDS zum Server auf
        memset(&server_sockaddr, 0, sizeof(struct sockaddr_un));
        server_sockaddr.sun_family = AF_UNIX;
        strcpy(server_sockaddr.sun_path, "server.sock");
        if ( connect(client, (struct sockaddr *) &server_sockaddr, len) < 0)
        {
            fprintf(stderr, "%s\n", strerror(errno));
            close(client);
            exit(1);
        }

        // Lese Daten vom Server
        memset(buffer, 0, sizeof(buffer));
        if( (readlen = read(client, buffer, sizeof(buffer))) < 0 )
        {
            fprintf(stderr, "%s\n", strerror(errno));
            close(client);
            exit(1);
        }
        empfangen += readlen;
        close(client);

        if( 1000000000 < std::chrono::duration_cast<std::chrono::nanoseconds>(
            std::chrono::high_resolution_clock::now()-start).count() )
        {
            break;
        }
    }

    fprintf(stdout, "%.1lfMB in %d Nachrichten gelesen\n", empfangen/1024.0/1024.0, count);
    return 0;
}

Hier werden satte 115.000 Aufrufe ausgeführt! Python hat eben doch noch etwas mehr Overhead.

Die C/C++ Version mit dem Socket über das Loopback-Interface liegt mit ca. 25.000 Aufrufen zwar deutlich über der Python-Version, aber immer noch Hausnummern von der Kommunikation mit Unix-Domain-Sockets entfernt.

// IP-Server
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <string.h>

int main(void)
{
    char buffer[256];
    memset(buffer, 0, 256);

    int port = 12345;
    int server = socket(AF_INET, SOCK_STREAM, 0); 

    struct sockaddr_in server_addr;
    int len = sizeof(server_addr);

    memset(&server_addr, 0, len);
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(port);


    const char* data = "Hallo, wie spät ist es bitte?";
    int datalen = strlen(data);

    struct sockaddr_in serv_addr;

    bind(server, (struct sockaddr *) &server_addr, len);
    listen(server, 5);

    fprintf(stdout, "Server listening on Port %d...\n", port);

    while( true )
    {
        struct sockaddr_in client_addr;
        memset(&client_addr, 0, len);
        int client = accept(server, (struct sockaddr *) &client_addr, (socklen_t*) &len);

        if (client == -1) // Ist mir noch nie passiert, aber wenns passiert, dann ist es fatal
        {
            fprintf(stderr, "Fehler: %s\n", strerror(errno));
            close(server);
            close(client);
            exit(1);
        }

        // Sende ein paar Daten
        int rc = send(client, data, datalen, 0);
        if (rc == -1) // -1 ist fatal
        {
            fprintf(stderr, "ERROR: %s\n", strerror(errno));
            close(server);
            close(client);
            exit(1);
        }
        close(client);
    }

    // hier kommen wir wg. while(true) nicht her
    close(server);
    return 0;
}
// IP-Client
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <chrono>
#include <arpa/inet.h>

int main(int, char**)
{

    const char* loopback = "127.0.0.1";
    int port = htons(12345);

    struct sockaddr_in server_addr;
    int len = sizeof(sockaddr_in);
    
    unsigned char sin_addr[sizeof(struct in_addr)];
    inet_pton(AF_INET, loopback, &sin_addr);
    
    int count = 0;
    int empfangen = 0;
    int readlen = 0;
    
    char buffer[256];
    auto start = std::chrono::high_resolution_clock::now();
    auto now = start;
    fprintf(stdout, "Client beginnt den Server zu stressen...\n");
    
    while(++count)
    {
        memset(&server_addr, 0, len);
        server_addr.sin_family = AF_INET;
        server_addr.sin_port = port;
        inet_pton(AF_INET, loopback, &server_addr.sin_addr);
     
        int s = socket(AF_INET, SOCK_STREAM, 0);
        if(connect(s, (struct sockaddr*)&server_addr, len) < 0)
        {
            fprintf(stderr, "connect: %s\n", strerror(errno));
            close(s);
            exit(1); 
        }
 
        if((readlen = read( s , buffer, sizeof(buffer)-1)) < 0)
        {   
            fprintf(stderr, "read: %s\n", strerror(errno));
            close(s);
            exit(1);
        }
        empfangen += readlen;
        close(s);

        if( 1000000000 < std::chrono::duration_cast<std::chrono::nanoseconds>(
            std::chrono::high_resolution_clock::now() - start).count() )
        {   
            break;
        }
            
    }   
        
    fprintf(stdout, "%.1lfMB in %d Nachrichten gelesen\n", empfangen/1024.0/1024.0, count);
    return 0;
}

Um den C/C++ Code zu übersetzen kann man das folgende Makefile verwenden. Und wie immer der Hinweis, nach den Targets nicht Blanks, sondern \t einfügen. Im vi geht das mit CTRL-i.

CC=g++
   
APPLICATIONS=uds_server uds_client ip_server ip_client
 
LDFLAGS=
CFLAGS=-O3 -DASIO_STANDALONE -I../..
 
DEBUG=
 
.SUFFIXES: .cpp
    
.cpp.o:
    $(CC) $(CFLAGS) -c -o $@ $<

all: $(APPLICATIONS)
    
uds_server: uds_server.o
    $(CC) $(DEBUG) -o $@ $@.o $(LDFLAGS)

uds_client: uds_client.o
    $(CC) $(DEBUG) -o $@ $@.o  $(LDFLAGS)
    
ip_server: ip_server.o
    $(CC) $(DEBUG) -o $@ $@.o $(LDFLAGS)

ip_client: ip_client.o
    $(CC) $(DEBUG) -o $@ $@.o  $(LDFLAGS)

clean:
    rm -f uds_server.o uds_client.o ip_server.o ip_client.o
    rm -f $(APPLICATIONS)
    rm -f server.sock
Series Navigation<< MulticastUPD, Datagram Sockets >>