Search code examples
windowswinapiipcnamed-pipesduplex

Pipe clients don't read back answer from pipe server (Windows)


Gist of the program: The server creates n client processes. On the client user enters a character string that is sent to the server. On server the string is processed as follows : the frequency is counted the occurence of vowels and numbers in the entered string. Further, this information is sent to the client, which prints answer

Remarks:

  1. Client is disposable
  2. I know about bug: \n reading in client
  3. Answer output from server should not exist, it exists for debugging only

Gist of the bug: After the server successfully writes answer to the named pipe, the client refuses to read the answer from the pipe.

SERVER:

#include <windows.h> 
#include <iostream>
#include <tchar.h>
#include <strsafe.h>
#include <limits>
#include <string.h>
#include <sstream>

#pragma warning(disable : 4996)

using namespace std;

#ifdef max
#undef max
#endif


#define PIPE_TIMEOUT 5000
#define BUFSIZE 4096





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

    cout << "Server is lauched\n"
        "It will be terminated when all clients exits\n";


    if (argc > 2 || (argc == 2 && argv[argc - 1] != "--help")) {
        cout << "Program should be lauched this way:\n"
            "name_of_program --help\n"
            "Note: --help is optional\n";
        return EXIT_FAILURE;
    }
    if (argc == 2 && argv[argc - 1] == "--help") {
        cout << "The server creates n client processes. On the client user\n"
            "enters a character string that is sent to the server.On server\n"
            "the string is processed as follows : the frequency is counted\n"
            "the appearance of vowels and numbers in the entered string.Further,\n"
            "this information is sent to the client.\n";
        return EXIT_SUCCESS;
    }

    int n = 0;
    cout << "Enter number of clients: ";
    if (!(cin >> n) || (n < 0))
    {
        cout << "Invalid input\n";
        return EXIT_FAILURE;
    }
    cin.ignore(numeric_limits<streamsize>::max(), '\n'); // cleaning buffer



    // ***CREATING PROCESSES***

    cout << "Generating " << n << " clients...\n";
    // struct _STARTUPINFO: Specifies the window station, desktop, standard handles, and appearance of the main window for a process at creation time.
    STARTUPINFO* si_arr = new STARTUPINFO[n];
    // struct _PROCESS_INFORMATION: Contains information about a newly created process and its primary thread. It is used with the CreateProcess() (and other).
    PROCESS_INFORMATION* pi_arr = new PROCESS_INFORMATION[n];

    for (int i = 0; i < n; ++i) {
        // ZeroMemory macro: Fills a block of memory with zeros.
        /*
        void ZeroMemory(
        [in] PVOID  Destination,
        [in] SIZE_T Length
        );
        */
        ZeroMemory(&si_arr[i], sizeof(si_arr[i]));
        // DWORD STARTUPINFO.cb: The size of the structure, in bytes.
        si_arr[i].cb = sizeof(si_arr[i]);
        ZeroMemory(&pi_arr[i], sizeof(pi_arr[i]));

        if (!CreateProcess(
        
            TEXT("C:\\Users\\andre\\source\\repos\\pipe_client\\Debug\\pipe_client.exe"),   // name of program (like in cmd)
            NULL,        // arguments for program (like in cmd after name of program)
            NULL,           // Process handle not inheritable
            NULL,           // Thread handle not inheritable
            FALSE,          // Set handle inheritance to FALSE
            CREATE_NEW_CONSOLE, // dwCreationFlags - The new process gets a new console instead of inheriting the parent one 
            NULL,           // Use parent's environment block
            NULL,           // Use parent's starting directory 
            &si_arr[i],            // Pointer to STARTUPINFO structure
            &pi_arr[i])           // Pointer to PROCESS_INFORMATION structure
            )
        {
            printf("CreateProcess failed (%d).\n", GetLastError());
            return EXIT_FAILURE;
        }
    }
    cout << "All processes (pipe clients) created\n";


    // ***CREATING PIPE INSTANCES***

    HANDLE* pipe_instances = new HANDLE[n];

    for (int i = 0; i < n; i++)
    {
        pipe_instances[i] = CreateNamedPipe(
            TEXT("\\\\.\\pipe\\os_lab4_pipe"),            // pipe name 
            PIPE_ACCESS_DUPLEX,      // read/write access 
            PIPE_TYPE_MESSAGE |      // message-type pipe 
            PIPE_READMODE_MESSAGE |  // message-read mode 
            PIPE_WAIT,               // blocking mode 
            n,               // number of instances 
            1024,   // output buffer size 
            1024,   // input buffer size 
            PIPE_TIMEOUT,            // client time-out 
            NULL);                   // default security attributes 

        if (pipe_instances[i] == INVALID_HANDLE_VALUE)
        {
            printf("CreateNamedPipe failed with %d.\n", GetLastError());
            return EXIT_FAILURE;
        }
    }
    cout << "All pipe instances created\n";

    // ***CONNECTING PIPE INSTANCES***

    for (int i = 0; i < n; i++)
    {
        if (!ConnectNamedPipe(pipe_instances[i], NULL))
        {
            printf("ConnectNamedPipe failed with %d.\n", GetLastError());
            return EXIT_FAILURE;
        }
    }
    cout << "All pipe instances connected to clients\n";

    // ***PROCESSING***

    char buf[1024];
    DWORD read_bytes;
    DWORD written_bytes;

    for (int i = 0; i < n; i++)
    {
        if (ReadFile(pipe_instances[i], buf, 1024, &read_bytes, NULL))
        {
            char* str = new char[read_bytes];
            strncpy(str, buf, read_bytes);
            //char answer[1024]; // ready c-str to WriteFile

            int total_amount = 0;

            int vowel_amount = 0;
            int digit_amount = 0;
            double vowel_frequency = 0.0;
            double digit_frequency = 0.0;


            char vowels[] = "aeiouy";
            char digits[] = "0123456789";


            total_amount = strlen(str);
            printf("Total: %i\n", total_amount);

            // vowel
            char* vowel_search;
            vowel_search = strpbrk(str, vowels); // check for first occurence of vovel in str
            while (vowel_search != NULL) { // while vovels not end up in str
                vowel_amount++;
                vowel_search = strpbrk(vowel_search + 1, vowels);
            }
        
            vowel_frequency = (double)vowel_amount / (double)total_amount * 100.0;
        

            // digit
            char* digit_search;
            digit_search = strpbrk(str, digits); // check for first occurence of digit in str
            while (digit_search != NULL) { // while digits not end up in str
                digit_amount++;
                digit_search = strpbrk(digit_search + 1, digits);
            }
        
            digit_frequency = (double)digit_amount / (double)total_amount * 100.0;
        
            string pre_str;
            pre_str = "Total: " + to_string(total_amount) + "\n"
                "Vowels: " + to_string(vowel_amount) + "\n"
                "Frequency: " + to_string(vowel_frequency) + "\n"
                "Digits: " + to_string(digit_amount) + "\n"
                "Frequency:" + to_string(digit_frequency) + "\n";
        

            cout << pre_str;

            const char* answer = pre_str.c_str();
   
            if (!WriteFile(pipe_instances[i], answer, 1024, &written_bytes, NULL)) {
                printf("WriteFile failed with %d.\n", GetLastError());
                return EXIT_FAILURE;
            }
        }
        else {
            printf("ReadFile failed with %d.\n", GetLastError());
            return EXIT_FAILURE;
        }
    }
    cout << "Reading, processing and writting was successful\n";

    // ***CLOSING PIPE INSTANCES***

    for (int i = 0; i < n; i++)
    {
        CloseHandle(pipe_instances[i]);
    }

    // !? - WaitMultipleObjects()


    delete[] pipe_instances;

    cout << "All pipe instances closed\n";

    // ***CLOSING PROCESSES***
    // The code written below is needed in order for the server to shutdown not earlier than the clients

    HANDLE* ev_hndl_arr = new HANDLE[n];
    for (int i = 0; i < n; i++) {
        ev_hndl_arr[i] = pi_arr[i].hProcess;
    }

    // Wait until EACH child process exits.
    WaitForMultipleObjects(n, ev_hndl_arr, TRUE, INFINITE);

    // Close process and thread handles.
    for (int i = 0; i < n; i++) {
        CloseHandle(pi_arr[i].hProcess);
        CloseHandle(pi_arr[i].hThread);
    }

    delete[] si_arr;
    delete[] pi_arr;
    delete[] ev_hndl_arr;

    cout << "All processes (pipe clients) closed\n";

    cout << "This is the end of server execution\n";
    system("pause");
    return EXIT_SUCCESS;
}

CLIENT:

#include <iostream>
#include <stdio.h> // fgets()
#include <string.h> // strpbrk()

#include <Windows.h>

using namespace std;

int main() {
    cout << "Client is launched\n";

    HANDLE hndlNP = CreateFile(
        TEXT("\\\\.\\pipe\\os_lab4_pipe"),
        GENERIC_READ | GENERIC_WRITE,
        NULL, // Prevents other processes from opening a file or device
        NULL, // cannot be inherited by any child processes
        OPEN_EXISTING,
        NULL, // no attributes
        NULL // no template
        );
    if (hndlNP == INVALID_HANDLE_VALUE) {
        cout << "CreateFile error\n";
        return EXIT_FAILURE;
    }
    cout << "Pipe connection established\n";

    char text[1024];


    printf("Enter string (max 1023 symbols): ");
    fgets(text, 1024, stdin);


    char answer[1024];
    DWORD read_bytes;
    DWORD written_bytes;

    if (WriteFile(hndlNP, text, 1024, &written_bytes, NULL)) {
   
        if (ReadFile(hndlNP, answer, 1024, &read_bytes, NULL)) {
            printf("ReadFile failed with %d.\n", GetLastError());
            system("pause"); // TEMPORARY
            return EXIT_FAILURE;
        }
    }
    else {
        printf("WriteFile failed with %d.\n", GetLastError());
        system("pause"); // TEMPORARY
        return EXIT_FAILURE;
    }
    cout << "Writting and reading was successful\n";

    cout << answer;

    // ***CLOSING PIPE CONNECTION***
    CloseHandle(hndlNP);

    system("pause");

    return 0;
}

EXECUTION EXAMPLE:

Server:

Server is lauched
It will be terminated when all clients exits
Enter number of clients : 2
Generating 2 clients...
===Two clients appears===
All processes(pipe clients) created
All pipe instances created
All pipe instances connected to clients
Total : 8
Total : 8
Vowels : 0
Frequency : 0.000000
Digits : 6
Frequency : 75.000000
Total : 6
Total : 6
Vowels : 0
Frequency : 0.000000
Digits : 5
Frequency : 83.333333
Reading, processingand writting was successful
All pipe instances closed
========================== (waiting until clients exit)
This is the end of server execution
Press any key to continue . . .
(key pressed, server closed)

Client1:

Client is launched
Pipe connection established
Enter string(max 1023 symbols) : 124345s
ReadFile failed with 0.
Press any key to continue . . .
==========================
(key pressed, client closed)

Client2:

Client is launched
Pipe connection established
Enter string(max 1023 symbols) : 12234
ReadFile failed with 0.
Press any key to continue . . .
==========================
(key pressed, client closed)

Solution

  • According to the documentation:

    If the function succeeds, the return value is nonzero (TRUE).

    If the function fails, or is completing asynchronously, the return value is zero (FALSE). To get extended error information, call the GetLastError function.

    So when ReadFile returns non-zero, the function is executed successfully, and your processing is wrong, you should modify it to:

    if (!ReadFile(hndlNP, answer, 1024, &read_bytes, NULL)) {
        printf("ReadFile failed with %d.\n", GetLastError());
        system("pause"); // TEMPORARY
        return EXIT_FAILURE;
    }