Click here to Skip to main content
15,885,546 members
Articles / Desktop Programming / Win32

Asynchronous Named Pipe Server Using Overlapped I/O in C#

Rate me:
Please Sign up or sign in to vote.
3.13/5 (7 votes)
20 Jul 2008CPOL3 min read 50.3K   1.2K   13   8
Asynchronous named pipe server in C#.

Introduction

I recently read CLR via C# by J. Richter dedicated to the internals of .NET 2.0 and the C# peculiarities deriving from it. I’ve decided to practice a little and write my new application in C#. The part of this application I present here. It’s an asynchronous named pipe server using overlapped I/O plus a synchronous (or they also say blocking) named pipe client.

Problems encountered

The first problem encountered was the fact that the only example by John Korres of using overlapped structure written in VB.NET provides asynchronous calls for two Win32 API functions ConnectNamedPipe and DisconnectNamedPipe. Read and write from and to a named pipe server implemented in a blocking mode using the System.IO.FileStream .NET class. I tried to use the BeginRead and BeginWrite methods but it remained unclear how these two interact with the NativeOverlapped structure if they do interact at all. So eventually, I adopted this sample code written in C from MSDN.

And that’s how the second problem came up. After the code was re-written, I found out that the GetOverlappedResult Win32 API function works incorrectly due to the fact that the pointer to the overlapped structure is not fixed, i.e., you have to work with the unmanaged memory of the process to make it work like in plain old C. Investigating farther, I understood that all buffers for the ReadFile and WriteFile Win32 API functions must also use unmanaged memory. I decided to use pointers and compile the application with the unsafe option; that upset me a bit because my code looked like almost plain old C to me now. And that was not just the case. Byte* to IntPtr cast did not work correctly either. So I switched to using IntPtr instead for the overlapped structure and buffers that allowed me to remove this odious unsafe parameter. And I was rewarded. Now the adopted code worked exactly like the sample from MSDN.

The last but not the least problem which I have to mention here is permissions for the named pipe server. Using the default permissions won’t allow interprocess communication between two computers, so look up buildSecurityAttributes in NamedPipeServer.cs. I won’t dwell on it here since it’s not the main point of the article.

Using the code

The following code extract shows the main server loop and exposes the peculiarities of asynchronous programming. The actual overlapped magic happens when the ReadFile Win32 API inside of the selectOperation method returns FALSE and GetLastError returns ERROR_IO_PENDING which is not an error but a signal that a ReadFile asynchronous call has not succeeded to complete reading so we have to call the GetOverlappedResult Win32 API to find out if the Operating System has put cbRet bytes into mPipeServer[i].Pipe.chRequest. If it hasn’t, we call the DisconnectAndReconnect method.

Another scenario happens if the application runs under Visual Studio 2005, no matter if it’s the release or debug configuration. To actually witness the difference, uncomment MessageBox.Show(“GetOverlappedResult:...”) altogether with the line above and run the executable. Afterwards, run it under Visual Studio in the debug configuration. You might not encounter it though.

C#
while (mListenning)
{
    i = (UInt32)WaitHandle.WaitAny(hEvents);
    // determines which pipe instance

    if (i < 0 || i > (mNumOfInstances - 1))
    {
        ThrowWin32Exception ("Index out of range.");
        return;
    }

    Debug.WriteLine("Instance -> " + i.ToString());

    #region ERROR_IO_PENDING == TRUE
    
    if (mPipeServer[i].Pipe.fPendingIO)
    {
        fSuccess = NativeMethods.GetOverlappedResult(
            mPipeServer[i].Pipe.hPipeInst,
            mPipeServer[i].Pipe.OverlappedPtr,
            out cbRet,
            false);
           //if (cbRet > 0)
           //     MessageBox.Show("GetOverlappedResult: " +
           //     Marshal.PtrToStringAnsi(mPipeServer[i].Pipe.chRequest));

        switch (mPipeServer[i].Pipe.dwState)
        {
            // Pending connect operation 
            case CONNECTING_STATE:
                Debug.WriteLine("CONNECTING_STATE");
                if (!fSuccess)
                    ThrowWin32Exception("CONNECTING_STATE_ERROR");
                //SetEventAsync(CONNECTING_STATE, mPipeServer[i].Pipe.chRequest,
                //              mPipeServer[i].Pipe.cbRead);
                //STEP #0
                mPipeServer[i].Pipe.dwState = READING_STATE;
                break;
            // Pending read operation 
            case READING_STATE:
                Debug.WriteLine("READING_STATE " + cbRet.ToString());
                if (!fSuccess || cbRet == 0)
                {
                    DebugWin32Exception("READING_STATE");
                    DisconnectAndReconnect(i);
                    continue;
                }
                //STEP #1
                //NB! If we got here and cbRet > 0
                //    the mPipeServer[i].Pipe.chRequest already has client data 
                mPipeServer[i].Pipe.dwState = WRITING_STATE;
                break;
            // Pending write operation 
            case WRITING_STATE:
                Debug.WriteLine("WRITING_STATE " + cbRet.ToString());
                if (!fSuccess || cbRet != mPipeServer[i].Pipe.cbToWrite)
                {
                    DebugWin32Exception("WRITING_STATE");
                    DisconnectAndReconnect(i);
                    continue;
                }
                mPipeServer[i].Pipe.dwState = READING_STATE;
                break;
            default:
                ThrowWin32Exception("runInstance - INVALID PIPE STATE");
                break;
        }

    } //IO_PENDING
    #endregion

    if (!selectOperation((Int32) i) )
    //does ReadFile or WriteFile depending on the pipe instance state
    {
        // An error occurred; disconnect from the client. 
        DisconnectAndReconnect(i);
    }
}

Points of interest

While testing the application, it turned out that it behaves differently when run under Visual Studio 2005.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Russian Federation Russian Federation
Golang, Perl, C/C++ developer

Comments and Discussions

 
QuestionException occurs when starting pipe server. Pin
Duojiao Yan7-Dec-11 0:52
Duojiao Yan7-Dec-11 0:52 
GeneralThere is a bug with the encoding of the message. Pin
consolejoker6-Feb-10 5:06
consolejoker6-Feb-10 5:06 
GeneralProcess Hang impossible to detach or even Stop [modified] Pin
AndyHo24-Jan-10 6:11
professionalAndyHo24-Jan-10 6:11 
GeneralFirst Read is missing?! Pin
Dirar H.21-Aug-09 8:44
Dirar H.21-Aug-09 8:44 
GeneralRe: First Read is missing?! Pin
Boris Kolesnikov21-Aug-09 15:14
Boris Kolesnikov21-Aug-09 15:14 
Hi, Dirar,
I’ve read your first post as well. Been busy to reply, my apologies.
It’s been a year now since I posted this article so it took me awhile to figure out some hints for you that might be applicable. I hope though you are quite past this bit about the end-of-file problem?

As to what’s happening now – as I judge from the code the first read on a pipe instance (one of many by the way) occurs right after connection? I mean the state of the pipe instance changes to READ but we’ve a loop here. And it might work not quite as you expect it to in real time mode (when you run your program without debugging or even compiled as release!). So you’ve got several clients connected. A few pipe instances (in my example there are four of them) serve these clients in ONE THREAD ASYNCHRONOUSLY. Let’s look at this situation this way, your client #0 connected, set the state to READ, went to selectOperation where it got !fSuccess && (dwErr == NamedPipeServer.ERROR_IO_PENDING)
The pipe instance is not yet ready to serve your client. So at this iteration of the loop your read did not occure. Next iteration might be
either a different instance or a different client or both. So where is your first read was saved in the meantime? It may come handy when i = (UInt32)WaitHandle.WaitAny(hEvents) returns the number of your pipe instance and it happens so (good luck really!) that it continues to serve your client which had its package stored somewhere in the meantime. Check the state of the pipe instance. Is it in read mode?

So in order to debug this application you have to understand which of each pipe instance serves your client where the package loss occurs. Try to implement a log file but do not write to the log file in the loop, just put the information in memory and after the program exists dump it to the log file.

Keep in mind that you’re dealing here with a Russian roulette. This (UInt32)WaitHandle.WaitAny(hEvents) is a call to the Win32 API function. It waits on ANY free instance and you don’t know which one might be unlocked. And remember that NativeMethods.GetOverlappedResult returns immediately. It does not wait on anything. That’s why the when you debug it actually HAS TIME to return TRUE while in real time it JUST HASN’T.
GeneralThank you!! Pin
Dirar H.19-Aug-09 11:14
Dirar H.19-Aug-09 11:14 
QuestionServer --&gt; Client communication? Pin
Greg Cadmes10-Sep-08 9:49
Greg Cadmes10-Sep-08 9:49 
AnswerRe: Server --&gt; Client communication? Pin
Boris Kolesnikov10-Sep-08 21:17
Boris Kolesnikov10-Sep-08 21:17 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.