Named pipes server read timeout

2020-08-24 05:25发布

问题:

When using C# NamedPipeServerStream, in case a client doesn't send any message-end-pattern (like \r\n when server reads with ReadLine()) NamedPipeServerStream Read methods will wait forever and no Abort() or Interupt() methods will work on that thread.

Since:
1) Stream.ReadTimeout not supported for NamedPipeServerStream
2) Abort() or Interupt() doesn't work on thread
3) NamedPipeServerStream.Disconnect() nether work
It is unclear, how to setup timeout on NamedPipeServerStream read operations?


Let me introduce an example. The specification of IPC we have require an exchange of \0-terminated strings. A client sends message, the server processes the message and as 'a must' sends a response. If the client doesn't send \0 in the end (client is not ours so we can't guarantee correctness of its working), the Read method will wait forever and client (since we don't control it) may wait forever for a response too.

Next is a simplified example of an implementation:

    public void RestartServer()
    {
        _pipeServerThread.Interrupt();  //doesn't affect Read wait
        _pipeServerThread.Abort();      //doesn't affect Read wait
    }

    private void PipeServerRun(object o) //runs on _pipeServerThread
    {
        _pipeServer = new NamedPipeServerStream(_pipeName, InOut, 100,
                      PipeTransmissionMode.Message, PipeOptions.WriteThrough);
        //_pipeServer.ReadTimeout = 100; //System.InvalidOperationException: Timeouts are not supporte d on this stream.

        // Wait for a client to connect
        while (true)
        {
            _pipeServer.WaitForConnection();
            string request = ReadPipeString();
            //... process request, send response and disconnect
        }
    }

    /// <summary>
    /// Read a \0 terminated string from the pipe
    /// </summary>
    private string ReadPipeString()
    {
        StringBuilder builder = new StringBuilder();
        var streamReader = new StreamReader(_pipeServer);

        while (true)
        {
            //read next byte 
            char[] chars = new char[1];
            streamReader.Read(chars, 0, 1); // <- This will wait forever if no \0 and no more data from client

            if (chars[0] == '\0') return builder.ToString();
            builder.Append(chars[0]);
        }
    }

So how to set timeout on NamedPipeServerStream read operations?

回答1:

Since you are running the pipe in message mode, you should first read the whole message into a byte[] buffer or a memory stream and then decide whether it's valid and decode it. Pipe messages have a definite length. It cannot be retrieved explicitly, but it shows up when you are reading from a message-mode pipe. Win32 ReadFile fails with ERROR_MORE_DATA if there still are unread bytes in the message, then it returns TRUE to indicate that the message is over. After this, a call to ReadFile will block until a new message is available. StreamReader naturally doesn't know any of this and blocks your thread.

Update: to implement timeouts, use asynchronous I/O (Stream.BeginRead). StreamReader does not support this directly. If you absolutely must use it, write a wrapper stream which will implement Read in terms of BeginRead on the underlying stream and support timeouts, cancellation etc.



回答2:

Try setting NamedPipeServerStream.ReadMode and/or .TransmissionMode to Byte. Regardless of these you should use the available BeginRead / EndRead methods with NamedPipeServerStream. This way you can implement the timeout logic yourself.