0

I'm trying to use one NamedPipe for bi-direction IPC. In my mind (and I can't find more information on MSDN), one full-duplex pipe would be sufficient. Here's my code.

//Compiled with these commands during my test:
//g++ -DCLIENT -o client.exe xxx.cpp
//g++ -DSERVER -o server.exe xxx.cpp

#include <iostream>
#include <windows.h>
using namespace std;

DWORD WINAPI ReadingThread(LPVOID a)
{
    HANDLE pipe = (HANDLE)a;
    BOOL result;
    char buffer[256];
    DWORD numBytesRead;
    while (true)
    {
        result = ReadFile(pipe, buffer, sizeof(buffer) - 1, &numBytesRead, NULL);

        if (result)
        {
            buffer[numBytesRead] = 0;
            cout << "[Thread] Number of bytes read: " << numBytesRead << endl;
            cout << "[Thread] Message: " << endl
                 << buffer << endl
                 << endl;
        }
        else
        {
            cout << "[Thread] Failed to read data from the pipe. err=" << GetLastError() << endl;
            break;
        }
    }
    return 0;
}

int main(int argc, const char **argv)
{
#ifdef CLIENT
    cout << "[Main] Connecting to pipe..." << endl;
    HANDLE pipe = CreateFileA("\\\\.\\pipe\\PipeTest", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
#else
    cout << "[Main] Creating an instance of a named pipe..." << endl;
    HANDLE pipe = CreateNamedPipeA("\\\\.\\pipe\\PipeTest", PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE, 1, 0, 0, 0, NULL);
#endif

    if (pipe == NULL || pipe == INVALID_HANDLE_VALUE)
    {
        cout << "[Main] Failed to acquire pipe handle." << endl;
        return 1;
    }

#ifdef CLIENT
#else
    cout << "[Server] Waiting for a client to connect to the pipe..." << endl;

    BOOL result = ConnectNamedPipe(pipe, NULL);
    if (!result)
    {
        cout << "[Server] Failed to make connection on named pipe." << endl;
        CloseHandle(pipe);
        return 1;
    }
    cout << "[Server] Client is here!" << endl;
    {
        const char *buf = "Hello pipe!\n";
        WriteFile(pipe, buf, strnlen(buf, 30), 0, 0);
    }
#endif

    CreateThread(0, 0, ReadingThread, pipe, 0, 0);
    cout << "[Main] Ready to send data." << endl;

    while (true)
    {
        char buffer[128];
        DWORD numBytesWritten = 0;
        BOOL result;

        cin >> buffer;
        if (!strcmp(buffer, "q"))
        {
            break;
        }
        cout << "[Main] Writing data to pipe..." << endl;
        result = WriteFile(pipe, buffer, strnlen(buffer, _countof(buffer)), &numBytesWritten, 0);
        if (result)
        {
            cout << "[Main] Written " << numBytesWritten << " bytes to the pipe." << endl;
        }
        else
        {
            cout << "[Main] Failed to write data to the pipe. err=" << GetLastError() << endl;
        }
    }
    CloseHandle(pipe);
    cout << "[Main] Done." << endl;
    return 0;
}

I can get the "Hello pipe!" message from server-side to client-side. And I'm expecting to type some string on either program's terminal and press enter, and see it on the other side.

However after the hello message, both program will stuck on the WriteFile call. Meanwhile the thread is stuck at the ReadFile call. How can I make it work, or did I left something out?

Ben
  • 410
  • 3
  • 11
  • you need use asynchronous pipe – RbMm Apr 01 '19 at 11:54
  • @RbMm I want to use multi-thread model, rather than async model. Are there any reason that I must use asynchronous pipe? – Ben Apr 01 '19 at 13:22
  • 1
    because here multi-thread model useless here - how you view this lead to hung. on not asynchronous handle - all io operations is serealized - this mean that new io not begin until all not finished. if you call `ReadFile` and then `WriteFile` (from another thread) - `WriteFile` not begin execute until `ReadFile` not full complete. as result all your 4 threads is hung. this and show useless multi-thread here. you need use exactly asynchronous io – RbMm Apr 01 '19 at 13:25
  • multi-thread used for execute several operations in concurrent. but you can not do several io operations in concurrent on synchronous file handle because all io operations is serialized by design – RbMm Apr 01 '19 at 13:32
  • @RbMm I see. Removing blocked ReadFile calls do allow WriteFile to complete. However I'm used to doing so on WSA sockets, with one thread blocked on recv while another calling send at any time. Do you know where does the difference sit? – Ben Apr 01 '19 at 13:43
  • And could you please paste an answer so that I can accept it? – Ben Apr 01 '19 at 13:44
  • but you sure create **asynchronous** socket. because this and worked – RbMm Apr 01 '19 at 13:57

2 Answers2

1

when file created for synchronous I/O (flag FO_SYNCHRONOUS_IO present in FILE_OBJECT ) all I/O operations on file is serialized - new operation will be wait in I/O manager before passed to driver, until current(if exist) not complete. in concurrent can execute only single I/O request. if we do blocked read in dedicated thread - all another I/O request on this file will be blocked until read not complete. this related not only to write. even query file name/attributes will block here. as result render reading in separate not help here - we block on first write attemp. solution here use asynchronous files - this let any count of I/O operation execute in concurrent.

RbMm
  • 31,280
  • 3
  • 35
  • 56
  • Check RbMm's another enlightening answer under https://stackoverflow.com/questions/43939424/writefile-with-windows-sockets-returns-invalid-parameter-error for details with WSA sockets. – Ben Apr 02 '19 at 09:19
  • @Ben probably you create socket with `socket` call - [*The socket that is created will have the overlapped attribute as a default.*](https://learn.microsoft.com/ru-ru/windows/desktop/api/winsock2/nf-winsock2-socket) – RbMm Apr 02 '19 at 09:53
  • Yes it's from `socket` func. So I referenced the answer of you. It's very helpful for one who has little knowledge on WSA internals like me. Thank you again. – Ben Apr 03 '19 at 13:09
  • can someone show me an example? I have full duplex server. I can read from client. But having an overlapped read pending an overlapped write returned BROKEN_PIPE... And I have not seen 1 working example anywhere. – Dan May 21 '20 at 03:31
  • @Dan - `ERROR_BROKEN_PIPE` (ie `STATUS_PIPE_BROKEN`) mean that *The pipe operation has failed because the other end of the pipe has been closed.* i of course have working code/examples with asynchronous pipes. i use self class [library](https://github.com/rbmm/LIB/blob/master/ASIO/pipe.h) for this. example say [this](https://github.com/rbmm/cmd/blob/master/cmd_client/CmdServer.cpp#L69) – RbMm May 21 '20 at 07:58
  • I have found without a doubt that Named Pipes in Windows 10 are NOT full duplex. They are Half duplex. Any documentation on MSDN that indicates they are FULL DUPLEX is WRONG. You can have 1 and only 1 in flight overlaped io at a time.. The second one will ALWAYAS break the PIPE. – Dan May 22 '20 at 15:50
  • @Dan - you mistake. my code fine work in asynchronous full duplex mode – RbMm May 22 '20 at 16:03
1

Named Pipes in Windows are HALF DUPLEX. As demonstrated on Windows 10. The MSDN Documentation is Wrong. A request has been submitted to Microsoft to correct their documentation.

While a pipe can be opened on the client to be "Generic Read | Generic Write" you can NOT do both at the same time.

And Overlapped IO submitted after the First Overlapped IO will break the pipe.

You can submit overlapped io. Then Wait for it to finish. Then submit the next overlapped io. You can not simultaneously Submit overlapped Reads AND overlapped Writes.

This is by definition, "Half Duplex".

Dan
  • 2,460
  • 2
  • 18
  • 12
  • no, this of course wrong. we can read and write at once on same pipe – RbMm May 22 '20 at 16:03
  • "Overlapped IO will break the pipe." - it break only wrong written code. but not pipe ) – RbMm May 22 '20 at 16:05
  • no, this is you mistake. i many time write this code - asynchronous read from pipe after connect and write data . and all fine work. nothing broken. only your code is broken because errors in it – RbMm May 23 '20 at 21:54
  • 1
    also this very primitive code - https://pastebin.com/MjMtAUfW special for you :) here exist asynchronous read and then asynchronous write (before read complete) – RbMm May 24 '20 at 00:55
  • @RbMm, so logically thinking there could be multiple OVERLAPPED structures, different for reader and writer, and with separate buffers. This way it should be full duplex then Your code is everything but primitive :), and from my not very skilled viewpoint it is a very interesting techniq to delete object by ref in it's own method. – Павел Nov 03 '21 at 04:47