Dr. Erhard Henkes, Stand: 19.04.2023

Zurück zum Start


Konsolen-Programme mit C++ (Teil2)

Client-Server mit Named Pipes

Named Pipes sind eine Methode zur Interprozess-Kommunikation (IPC) auf Windows-Betriebssystemen. Sie ermöglichen die Übertragung von Daten zwischen Prozessen auf demselben Computer oder über das Netzwerk. Eine Named Pipe ist ein benanntes, unidirektionales oder bidirektionales Kommunikationskanal zwischen Prozessen. Ein Prozess (z.B. Server) erstellt eine Named Pipe und gibt ihr einen Namen. Andere Prozesse (z.B. Clients) können sich dann mit der Named Pipe verbinden, indem sie ihren Namen angeben.

Named Pipes sind ähnlich wie anonyme Pipes, die ebenfalls zur Interprozesskommunikation verwendet werden können. Der Hauptunterschied besteht darin, dass Named Pipes einen Namen besitzen und von jedem Prozess auf dem System oder im Netzwerk verwendet werden können, während anonyme Pipes nur zwischen verwandten Prozessen verwendet werden können.

Named Pipes unterstützen verschiedene Modi, wie z.B. Byte-Stream-Modus oder Message-Modus, und verschiedene Übertragungsmodi wie z.B. Blocking-Modus oder Non-Blocking-Modus. Sie können auch Sicherheitseinstellungen konfigurieren, um den Zugriff auf die Named Pipe zu steuern.

Auf Windows-Betriebssystemen können Named Pipes mit der Windows-API erstellt und verwendet werden.
Die wichtigsten Funktionen sind CreateNamedPipe, ConnectNamedPipe, CreateFile, ReadFile und WriteFile.

Zunächst erstellen wir das Server-Programm, das die Named Pipe erstellt.
Wir verwenden Multithread-Programmierung, damit sich mehrere Clients anbinden können.

 /*
Hier ist ein Beispiel für die Verwendung von Named Pipes in C++ auf Windows,
das ohne Cygwin oder MinGW in Microsoft Visual Studio ausgeführt werden kann.
Dieses Programm erstellt eine Named Pipe mit dem Namen `\\\\.\\pipe\\MyPipe` und
wartet auf eine Verbindung von einem Client.Wenn ein Client verbunden ist,
sendet das Programm eine Nachricht an den Client und beendet dann die Verbindung.

Dieses Programm erstellt eine Named Pipe und wartet auf Verbindungen von Clients.
Wenn ein Client verbunden ist, erstellt das Programm einen neuen Thread, um die Verbindung zu verarbeiten.
Im Thread wird eine Nachricht an den Client gesendet und dann die Verbindung beendet.

Auf diese Weise kann das Serverprogramm mehrere Clients gleichzeitig bedienen,
indem es für jeden Client einen neuen Thread erstellt.

Du kannst dieses Programm in Microsoft Visual Studio kompilieren und ausführen.
Stelle sicher, dass du das Windows SDK in deinem Projekt eingebunden hast,
damit du auf die Windows - API - Funktionen zugreifen kannst.
*/

#define NOMINMAX // due to conflict with max()

#include <windows.h>
#include <iostream>
#include <limits>

void wait()
{
    std::cin.clear();
    std::cin.ignore((std::numeric_limits<std::streamsize>::max)(), '\n');
    std::cin.get();
}

DWORD WINAPI ClientThread(LPVOID lpParam)
{
    HANDLE pipe = (HANDLE)lpParam;

    // Send a message to the client
    const char* message = "Hello from the server!";
    DWORD bytesWritten;
    BOOL result = WriteFile(
    pipe, // pipe handle
    message, // message
    strlen(message) + 1, // message length
    &bytesWritten, // bytes written
    NULL); // not overlapped

    if (!result)
    {
        std::cerr << "WriteFile failed: " << GetLastError() << std::endl;
    }

    DisconnectNamedPipe(pipe);
    CloseHandle(pipe);

    return 0;
}

int main()
{
    while (true)
    {
        // Create a named pipe
        HANDLE pipe = CreateNamedPipe(
        TEXT("\\\\.\\pipe\\MyPipe"), // pipe name
        PIPE_ACCESS_DUPLEX, // read/write access
        PIPE_TYPE_MESSAGE | // message type pipe
        PIPE_READMODE_MESSAGE | // message-read mode
        PIPE_WAIT, // blocking mode
        PIPE_UNLIMITED_INSTANCES, // max. instances
        1024, // output buffer size
        1024, // input buffer size
        NMPWAIT_USE_DEFAULT_WAIT, // client time-out
        NULL); // default security attribute

        if (pipe == INVALID_HANDLE_VALUE)
        {
            std::cerr << "CreateNamedPipe failed: " << GetLastError() << std::endl;
            return -1;
        }

        std::cout << "Waiting for client to connect..." << std::endl;

        // Wait for the client to connect
        BOOL result = ConnectNamedPipe(pipe, NULL);
        if (!result)
        {
            std::cerr << "ConnectNamedPipe failed: " << GetLastError() << std::endl;
            CloseHandle(pipe);
            return -1;
        }

        std::cout << "Client connected." << std::endl;

        // Create a thread to handle the client connection
        HANDLE thread = CreateThread(
        NULL, // default security attributes
        0, // default stack size
        ClientThread,// thread function name
        (LPVOID)pipe,// argument to thread function
        0, // use default creation flags
        NULL); // returns the thread identifier

        if (thread == NULL)
        {
            std::cerr << "CreateThread failed: " << GetLastError() << std::endl;
            DisconnectNamedPipe(pipe);
            CloseHandle(pipe);
            return -1;
        }
    }

    wait();

    return 0;
}

Nun benötigen wir einen oder mehrere Clients, die die Named Pipe aufrufen.

/*
Hier ist ein Beispiel für einen Named Pipe-Client in C++ auf Windows,
der sich mit einer Named Pipe verbindet und eine Nachricht vom Server empfängt.
Dieses Programm verbindet sich mit einer Named Pipe mit dem Namen \\\\.\\pipe\\MyPipe
und liest eine Nachricht vom Server.
Die Funktion CreateFile() wird verwendet, um eine Verbindung zur Named Pipe herzustellen.
Die Funktion ReadFile() wird verwendet, um Daten von der Pipe zu lesen.
Du kannst dieses Programm in Microsoft Visual Studio kompilieren und ausführen.
Stelle sicher, dass du das Windows SDK in deinem Projekt eingebunden hast,
damit du auf die Windows-API-Funktionen zugreifen kannst.
*/

#define NOMINMAX // due to conflict with max()

#include <windows.h>
#include <iostream>
#include <limits>

void wait()
{
    std::cin.clear();
    std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
    std::cin.get();
}

int main()
{
    // Connect to the named pipe
    HANDLE pipe = CreateFile(
    TEXT("\\\\.\\pipe\\MyPipe"), // pipe name
    GENERIC_READ | // read and write access
    GENERIC_WRITE,
    0, // no sharing
    NULL, // default security attributes
    OPEN_EXISTING, // opens existing pipe
    0, // default attributes
    NULL); // no template file

    if (pipe == INVALID_HANDLE_VALUE)
   
{
        std::cerr << "CreateFile failed: " << GetLastError() << std::endl;
        return -1;
   
}

    std::cout << "Connected to server." << std::endl;

    // Read a message from the server
    char buffer[1024];
    DWORD bytesRead;
    BOOL result = ReadFile(
    pipe, // pipe handle
    buffer, // buffer to receive reply
    sizeof(buffer),// size of buffer
    &bytesRead, // number of bytes read
    NULL); // not overlapped

    if (!result)
   
{
        std::cerr << "ReadFile failed: " << GetLastError() << std::endl;
   
}
    else
   
{
        std::cout << "Received message from server: " << buffer << std::endl;
   
}

    CloseHandle(pipe);

    wait();

    return 0;
}