Duplex named pipe hangs on a certain write

放肆的年华 提交于 2021-01-20 20:22:12

问题


I have a C++ pipe server app and a C# pipe client app communicating via Windows named pipe (duplex, message mode, wait/blocking in separate read thread).

It all works fine (both sending and receiving data via the pipe) until I try and write to the pipe from the client in response to a forms 'textchanged' event. When I do this, the client hangs on the pipe write call (or flush call if autoflush is off). Breaking into the server app reveals it's also waiting on the pipe ReadFile call and not returning. I tried running the client write on another thread -- same result.

Suspect some sort of deadlock or race condition but can't see where... don't think I'm writing to the pipe simultaneously.

Update1: tried pipes in byte mode instead of message mode - same lockup.

Update2: Strangely, if (and only if) I pump lots of data from the server to the client, it cures the lockup!?

Server code:

DWORD ReadMsg(char* aBuff, int aBuffLen, int& aBytesRead)
{
    DWORD byteCount;
    if (ReadFile(mPipe, aBuff, aBuffLen, &byteCount, NULL))
    {
        aBytesRead = (int)byteCount;
        aBuff[byteCount] = 0;
        return ERROR_SUCCESS;
    }

    return GetLastError();  
}

DWORD SendMsg(const char* aBuff, unsigned int aBuffLen)
{
    DWORD byteCount;
    if (WriteFile(mPipe, aBuff, aBuffLen, &byteCount, NULL))
    {
        return ERROR_SUCCESS;
    }

    mClientConnected = false;
    return GetLastError();  
}

DWORD CommsThread()
{
    while (1)
    {
        std::string fullPipeName = std::string("\\\\.\\pipe\\") + mPipeName;
        mPipe = CreateNamedPipeA(fullPipeName.c_str(),
                                PIPE_ACCESS_DUPLEX,
                                PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
                                PIPE_UNLIMITED_INSTANCES,
                                KTxBuffSize, // output buffer size
                                KRxBuffSize, // input buffer size
                                5000, // client time-out ms
                                NULL); // no security attribute 

        if (mPipe == INVALID_HANDLE_VALUE)
            return 1;

        mClientConnected = ConnectNamedPipe(mPipe, NULL) ? TRUE : (GetLastError() == ERROR_PIPE_CONNECTED);
        if (!mClientConnected)
            return 1;

        char rxBuff[KRxBuffSize+1];
        DWORD error=0;
        while (mClientConnected)
        {
            Sleep(1);

            int bytesRead = 0;
            error = ReadMsg(rxBuff, KRxBuffSize, bytesRead);
            if (error == ERROR_SUCCESS)
            {
                rxBuff[bytesRead] = 0;  // terminate string.
                if (mMsgCallback && bytesRead>0)
                    mMsgCallback(rxBuff, bytesRead, mCallbackContext);
            }
            else
            {
                mClientConnected = false;
            }
        }

        Close();
        Sleep(1000);
    }

    return 0;
}

client code:

public void Start(string aPipeName)
{
    mPipeName = aPipeName;

    mPipeStream = new NamedPipeClientStream(".", mPipeName, PipeDirection.InOut, PipeOptions.None);

    Console.Write("Attempting to connect to pipe...");
    mPipeStream.Connect();
    Console.WriteLine("Connected to pipe '{0}' ({1} server instances open)", mPipeName, mPipeStream.NumberOfServerInstances);

    mPipeStream.ReadMode = PipeTransmissionMode.Message;
    mPipeWriter = new StreamWriter(mPipeStream);
    mPipeWriter.AutoFlush = true;

    mReadThread = new Thread(new ThreadStart(ReadThread));
    mReadThread.IsBackground = true;
    mReadThread.Start();

    if (mConnectionEventCallback != null)
    {
        mConnectionEventCallback(true);
    }
}

private void ReadThread()
{
    byte[] buffer = new byte[1024 * 400];

    while (true)
    {
        int len = 0;
        do
        {
            len += mPipeStream.Read(buffer, len, buffer.Length);
        } while (len>0 && !mPipeStream.IsMessageComplete);

        if (len==0)
        {
            OnPipeBroken();
            return;
        }

        if (mMessageCallback != null)
        {
            mMessageCallback(buffer, len);
        }

        Thread.Sleep(1);
    }
}

public void Write(string aMsg)
{
    try
    {
        mPipeWriter.Write(aMsg);
        mPipeWriter.Flush();
    }
    catch (Exception)
    {
        OnPipeBroken();
    }
}

回答1:


If you are using separate threads you will be unable to read from the pipe at the same time you write to it. For example, if you are doing a blocking read from the pipe then a subsequent blocking write (from a different thread) then the write call will wait/block until the read call has completed and in many cases if this is unexpected behavior your program will become deadlocked.

I have not tested overlapped I/O, but it MAY be able to resolve this issue. However, if you are determined to use synchronous calls then the following models below may help you to solve the problem.

Master/Slave

You could implement a master/slave model in which the client or the server is the master and the other end only responds which is generally what you will find the MSDN examples to be.

In some cases you may find this problematic in the event the slave periodically needs to send data to the master. You must either use an external signaling mechanism (outside of the pipe) or have the master periodically query/poll the slave or you can swap the roles where the client is the master and the server is the slave.

Writer/Reader

You could use a writer/reader model where you use two different pipes. However, you must associate those two pipes somehow if you have multiple clients since each pipe will have a different handle. You could do this by having the client send a unique identifier value on connection to each pipe which would then let the server associate the two pipes. This number could be the current system time or even a unique identifier that is global or local.

Threads

If you are determined to use the synchronous API you can use threads with the master/slave model if you do not want to be blocked while waiting for a message on the slave side. You will however want to lock the reader after it reads a message (or encounters the end of a series of message) then write the response (as the slave should) and finally unlock the reader. You can lock and unlock the reader using locking mechanisms that put the thread to sleep as these would be most efficient.

Security Problem With TCP

The loss going with TCP instead of named pipes is also the biggest possible problem. A TCP stream does not contain any security natively. So if security is a concern you will have to implement that and you have the possibility of creating a security hole since you would have to handle authentication yourself. The named pipe can provide security if you properly set the parameters. Also, to note again more clearly: security is no simple matter and generally you will want to use existing facilities that have been designed to provide it.




回答2:


I think you may be running into problems with named pipes message mode. In this mode, each write to the kernel pipe handle constitutes a message. This doesn't necessarily correspond with what your application regards a Message to be, and a message may be bigger than your read buffer.

This means that your pipe reading code needs two loops, the inner reading until the current [named pipe] message has been completely received, and the outer looping until your [application level] message has been received.

Your C# client code does have a correct inner loop, reading again if IsMessageComplete is false:

do
{
    len += mPipeStream.Read(buffer, len, buffer.Length);
} while (len>0 && !mPipeStream.IsMessageComplete);

Your C++ server code doesn't have such a loop - the equivalent at the Win32 API level is testing for the return code ERROR_MORE_DATA.

My guess is that somehow this is leading to the client waiting for the server to read on one pipe instance, whilst the server is waiting for the client to write on another pipe instance.




回答3:


It seems to me that what you are trying to do will rather not work as expected. Some time ago I was trying to do something that looked like your code and got similar results, the pipe just hanged and it was difficult to establish what had gone wrong.

I would rather suggest to use client in very simple way:

  1. CreateFile
  2. Write request
  3. Read answer
  4. Close pipe.

If you want to have two way communication with clients which are also able to receive unrequested data from server you should rather implement two servers. This was the workaround I used: here you can find sources.



来源:https://stackoverflow.com/questions/8069172/duplex-named-pipe-hangs-on-a-certain-write

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!