Search code examples
c#named-pipes.netnative-methods

Send multiple messages between a native named pipe and a System.IO named pipe


I need to send multiple messages between a native named pipe and a System.IO named pipe. I got the code for both ends of this communication from the All-In-One Code Framework (IPC and RPC).

Server:

SafePipeHandle hNamedPipe = null;

try { SECURITY_ATTRIBUTES sa = null;
sa = CreateNativePipeSecurity();

// Create the named pipe.
hNamedPipe = NativeMethod.CreateNamedPipe(
    Constants.FullPipeName,             // The unique pipe name.
    PipeOpenMode.PIPE_ACCESS_DUPLEX,    // The pipe is duplex
    PipeMode.PIPE_TYPE_MESSAGE |        // Message type pipe 
    PipeMode.PIPE_READMODE_MESSAGE |    // Message-read mode 
    PipeMode.PIPE_WAIT,                 // Blocking mode is on
    PIPE_UNLIMITED_INSTANCES,           // Max server instances
    1024,                 // Output buffer size
    1024,                 // Input buffer size
    NMPWAIT_USE_DEFAULT_WAIT,           // Time-out interval
    sa                                  // Pipe security attributes
);

if (hNamedPipe.IsInvalid)
{
    throw new Win32Exception();
}

Console.WriteLine("The named pipe ({0}) is created.", Constants.FullPipeName);

// Wait for the client to connect.
Console.WriteLine("Waiting for the client's connection...");
if (!NativeMethod.ConnectNamedPipe(hNamedPipe, IntPtr.Zero))
{
    if (Marshal.GetLastWin32Error() != ERROR_PIPE_CONNECTED)
    {
        throw new Win32Exception();
    }
}
Console.WriteLine("Client is connected.");

// 
// Receive a request from client.
// 

string message;
bool finishRead = false;
do
{
    byte[] bRequest = new byte[1024];
    int cbRequest = bRequest.Length, cbRead;

    finishRead = NativeMethod.ReadFile(
        hNamedPipe,             // Handle of the pipe
        bRequest,               // Buffer to receive data
        cbRequest,              // Size of buffer in bytes
        out cbRead,             // Number of bytes read 
        IntPtr.Zero             // Not overlapped 
        );

    if (!finishRead &&
        Marshal.GetLastWin32Error() != ERROR_MORE_DATA)
    {
        throw new Win32Exception();
    }

    // Unicode-encode the received byte array and trim all the 
    // '\0' characters at the end.
    message = Encoding.Unicode.GetString(bRequest).TrimEnd('\0');
    Console.WriteLine("Receive {0} bytes from client: \"{1}\"", cbRead, message);
}
while (!finishRead);  // Repeat loop if ERROR_MORE_DATA

// 
// Send a response from server to client.
// 

message = "Goodbye\0";
byte[] bResponse = Encoding.Unicode.GetBytes(message);
int cbResponse = bResponse.Length, cbWritten;

if (!NativeMethod.WriteFile(
    hNamedPipe,                 // Handle of the pipe
    bResponse,                  // Message to be written
    cbResponse,                 // Number of bytes to write
    out cbWritten,              // Number of bytes written
    IntPtr.Zero                 // Not overlapped
    ))
{
    throw new Win32Exception();
}

Console.WriteLine("Send {0} bytes to client: \"{1}\"",
    cbWritten, message.TrimEnd('\0'));

// Flush the pipe to allow the client to read the pipe's contents 
// before disconnecting. Then disconnect the client's connection.
NativeMethod.FlushFileBuffers(hNamedPipe);
NativeMethod.DisconnectNamedPipe(hNamedPipe); 

} catch (Exception ex) { Console.WriteLine("The server throws the error: {0}", ex.Message); } finally { if (hNamedPipe != null) { hNamedPipe.Close(); hNamedPipe = null; } }

Client:

            NamedPipeClientStream pipeClient = null;

        try
        {
            // Try to open the named pipe identified by the pipe name.

            pipeClient = new NamedPipeClientStream(
                ".",         // The server name
                Constants.PipeName,           // The unique pipe name
                PipeDirection.InOut,        // The pipe is duplex
                PipeOptions.None            // No additional parameters
            );

            pipeClient.Connect(5000);
            MessageBox.Show(
                string.Format( "The named pipe ({0}) is connected.", Constants.PipeName )
            );

            pipeClient.ReadMode = PipeTransmissionMode.Message;

            // 
            // Send a request from client to server
            // 

            for ( int i = 0; i < 2; i++ ) {

                string message = "hello my pipe dream\0";
                byte[] bRequest = Encoding.Unicode.GetBytes( message );
                int cbRequest = bRequest.Length;

                pipeClient.Write( bRequest, 0, cbRequest );

                MessageBox.Show(
                    string.Format( "Send {0} bytes to server: \"{1}\"", cbRequest, message.TrimEnd( '\0' ) )
                );
            }

            //
            // Receive a response from server.
            // 

            do
            {
                byte[] bResponse = new byte[1024];
                int cbResponse = bResponse.Length, cbRead;

                cbRead = pipeClient.Read(bResponse, 0, cbResponse);

                // Unicode-encode the received byte array and trim all the 
                // '\0' characters at the end.
                string message = Encoding.Unicode.GetString(bResponse).TrimEnd('\0');
                Console.WriteLine("Receive {0} bytes from server: \"{1}\"",
                    cbRead, message);
            }
            while (!pipeClient.IsMessageComplete);

        }
        catch (Exception ex)
        {
            new ErrorDialog( ex ).ShowDialog();
        }
        finally
        {
            // Close the pipe.
            if (pipeClient != null)
            {
                pipeClient.Close();
                pipeClient = null;
            }
        }
    }

As you can see from the for loop in the "Send a request from client to server" section above, I'm trying to figure out how to send multiple messages to the server. I see that the server code loops through until the NativeMethod.ReadFile() method returns true. My problem is that it is always returning true after the first message is read, and ignoring the second message So my question, specifically, is what do i need to do in the client code so that this method returns false so then it will go get the second message.


Solution

  • Thank you, Chris, for pointing me to the documentation. I up-voted your answer since the quote you included led me to the answer I was looking for.

    Turns out I was just confused by the do/while loop in the server code's "Receive a request from client" section. That is, it looked to me like it was retrieving multiple messages from the client. But in fact, it was retrieving consecutive parts of the single message. I updated that code section as follows.

    // Receive a request from client.
    
    string message = string.Empty;
    bool finishRead = false;
    do
    {
        byte[] bRequest = new byte[1024];
        int cbRequest = bRequest.Length, cbRead;
    
        finishRead = NativeMethod.ReadFile(
            hNamedPipe,             // Handle of the pipe
            bRequest,               // Buffer to receive data
            cbRequest,              // Size of buffer in bytes
            out cbRead,             // Number of bytes read 
            IntPtr.Zero             // Not overlapped 
            );
    
        if (!finishRead &&
            Marshal.GetLastWin32Error() != ERROR_MORE_DATA)
        {
            throw new Win32Exception();
        }
    
        // Unicode-encode the received byte array and trim all the 
        // '\0' characters at the end.
        message += Encoding.Unicode.GetString(bRequest).TrimEnd('\0');
    }
    while (!finishRead);  // Repeat loop if ERROR_MORE_DATA
    
    Console.WriteLine( "Message received from client: \"{0}\"", message );
    

    As for delimiting the multiple "messages" within the client's request to the server, I'll probably just use newline characters.