Search code examples
c#.netipcnamed-pipes

.NET NamedPipes client/server troubles. Server can't reply back to client - "Cannot access closed pipe"


I'm trying to create a basic client/server message exchange between two .NET applications. I want the client to connect to the server and then send a message. The server will then receive this message, and reply back with a result. I'm intending to implement a basic API.

Server code looks like this:

// PipeSecurity ps = new PipeSecurity() and ACL rules here...

NamedPipeServerStream pipeServer = NamedPipeServerStreamAcl.Create("my_pipe", PipeDirection.InOut, 10, PipeTransmissionMode.Message, PipeOptions.Asynchronous, default, default, ps);

StreamReader sr = new StreamReader(pipeServer);
StreamWriter sw = new StreamWriter(pipeServer);

while (true)
{
    try 
    {
        // Wait for a connection
        pipeServer.WaitForConnection();

        // Read the message from the client
        string messageFromClient = sr.ReadLine();
        Trace.WriteLine($"IPC Message Reveived: {messageFromClient}", "info");

        // Reply back to the client
        sw.WriteLine("PONG!");
        sw.Flush();

        pipeServer.WaitForPipeDrain();

        pipeServer.Close();


    } catch (Exception ex) {

        Trace.WriteLine($"ipc_server error: {ex}", "error");

    } finally {

        pipeServer.WaitForPipeDrain();

        if (pipeServer.IsConnected) { 

            pipeServer.Disconnect(); 

        }
    }

and then on the client, i have the following;

var pipeClient = new NamedPipeClientStream(".",
  "my_pipe", PipeDirection.InOut, PipeOptions.None);

try
{
    // Connect to pipe
    if (pipeClient.IsConnected != true) { pipeClient.Connect(); }

    StreamReader sr = new StreamReader(pipeClient);
    StreamWriter sw = new StreamWriter(pipeClient);

    // Send a message
    sw.WriteLine("PING!");
    sw.Flush();

    pipeClient.WaitForPipeDrain();

    // Perhaps my client is closing or not waiting long enough here
    // for the server to reply??

    // Receive a message
    string message_from_server = sr.ReadLine();
    Console.WriteLine(message_from_server);

    pipeClient.Close();

} catch (Exception ex) {

    Console.WriteLine($"Error: {ex}");
}

Im then getting the following error on the server. No errors are thrown on the client and the client does not see the pong.:

info: IPC Message Reveived: PING!
Exception thrown: 'System.ObjectDisposedException' in System.IO.Pipes.dll
An unhandled exception of type 'System.ObjectDisposedException' occurred in System.IO.Pipes.dll
Cannot access a closed pipe.

Im thinking its something to do with the client closing the pipe before the server replies, but im struggling to work out if thats the issue and how to resolve it.

Any help welcome! Would also welcome feedback on my error handling and how to get the server non-blocking as well, thats my next job.


Solution

  • There are multiple issues that I recognize just at first glance:

    1. You are in a while(true) loop in the server and you Close the Pipe at the end of the first iteration. Then at the next iteration you will call pipeServer.WaitForConnection(); guaranteed to get ObjectDisposedException.

    2. Even in the first iteration you will get that because you execute methods on a closed pipe in the finally block of the server.

       ...
       pipeServer.WaitForPipeDrain();
      
       pipeServer.Close(); // this closes -> initiates Disposing
      
       } finally {
       // pipeServer is Closed here...aka Disposed/Disposing 
       pipeServer.WaitForPipeDrain(); // probably throws here
      
       if (pipeServer.IsConnected) { 
      
           pipeServer.Disconnect(); 
      
       } 
      

    For an okay intro-example to achieve what you want, I've modified your initial example with this functionality.

    • The server echoes back what the client sent in upper case.
    • The client reads user input from the console and sends the input to the server and outputs the server's response. If you write "EXIT" the client exits.

    At this point you can connect another client etc. When you are done with server Ctrl+C in the server console window. Not bullet-proof or anything but good enough to experiment more.

    Server (example Security...use whatever works for you)

    static void Main(string[] args) {
        PipeSecurity ps = new PipeSecurity();
        var id = new SecurityIdentifier(WellKnownSidType.AuthenticatedUserSid, null);
        ps.SetAccessRule(new PipeAccessRule(id, PipeAccessRights.ReadWrite, AccessControlType.Allow));
    
        NamedPipeServerStream pipeServer = NamedPipeServerStreamAcl
        .Create("my_pipe", PipeDirection.InOut, 10,
        PipeTransmissionMode.Message, PipeOptions.Asynchronous, default, default, ps);
    
    
        Console.CancelKeyPress += (o, e) => pipeServer.Close();
        Console.WriteLine("Named Pipe Server started. Press Ctrl+C to exit");
        while (true) { // this is for the next client
            try {
                pipeServer.WaitForConnection();
                Console.WriteLine("Client connected.");
                StreamReader sr = new StreamReader(pipeServer);
                StreamWriter sw = new StreamWriter(pipeServer);
    
                sw.WriteLine("Hello. Will ECHO until you write EXIT");
                sw.Flush();
                pipeServer.WaitForPipeDrain();
    
    
                while (true) { // message loop with this client
    
                    string messageFromClient = sr.ReadLine();
                    Console.WriteLine(messageFromClient);
    
                    if (messageFromClient == "EXIT") {
                        break;
                    }
    
                    var replyMessage = "Server ECHO: " + messageFromClient.ToUpper();
                    sw.WriteLine(replyMessage);
                    sw.Flush();
                    pipeServer.WaitForPipeDrain();
    
                }
    
                if (pipeServer.IsConnected) {
                    pipeServer.Disconnect();
                }
            }
            catch (Exception ex) {
                Console.WriteLine($"Error: {ex}");
                throw;
            }
        }
    }
    

    Client

    static void Main(string[] args) {
        using var pipeClient = new NamedPipeClientStream(".",
              "my_pipe", PipeDirection.InOut, PipeOptions.None);
        try {
            pipeClient.Connect();
            Console.WriteLine("Connected to server");
        } catch (Exception ex) {
            Console.WriteLine(ex.Message);
            throw;
        }
    
        StreamReader sr = new StreamReader(pipeClient);
        StreamWriter sw = new StreamWriter(pipeClient);
    
        string introMessageServer = sr.ReadLine();
        Console.WriteLine(introMessageServer);
    
        while (true) { // client loop 
            try {
                var message = Console.ReadLine();
                sw.WriteLine(message);
                sw.Flush();
                pipeClient.WaitForPipeDrain();
    
                if (message == "EXIT") {
                    break;
                }
    
                string message_from_server = sr.ReadLine();
                Console.WriteLine(message_from_server);
    
            } catch (Exception ex) {
                Console.WriteLine(ex.Message);
                throw;
            }
        }
    }