双工命名管道在某个写入时挂起

时间:2011-11-09 17:41:45

标签: windows multithreading named-pipes

我有一个C ++管道服务器应用程序和一个C#管道客户端应用程序通过Windows命名管道进行通信(双工,消息模式,在单独的读取线程中等待/阻塞)。

一切正常(通过管道发送和接收数据),直到我尝试从客户端写入管道以响应表单'textchanged'事件。当我这样做时,客户端挂起管道写入调用(如果autoflush关闭,则挂起调用)。打破服务器应用程序显示它还在等待管道ReadFile调用而不返回。 我尝试在另一个线程上运行客户端写 - 结果相同。

怀疑某种死锁或竞争状况,但无法看到......不要以为我在同时写入管道。

Update1:​​在字节模式下尝试管道而不是消息模式 - 相同的锁定。

Update2:奇怪的是,如果(并且仅当)我将大量数据从服务器泵送到客户端,它会解决锁定问题!?

服务器代码:

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;
}

客户代码:

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();
    }
}

3 个答案:

答案 0 :(得分:5)

如果您使用单独的线程,则在写入管道的同时无法从管道读取。例如,如果您正在从管道执行阻塞读取,然后执行后续阻塞写入(来自不同的线程),则写入调用将等待/阻塞,直到读取调用完成,并且在许多情况下,如果这是意外行为,您的程序将陷入僵局。

我没有测试过重叠的I / O,但它可以解决这个问题。但是,如果您决定使用同步调用,则以下模型可帮助您解决问题。

主/从

您可以实现主/从模型,其中客户端或服务器是主服务器,而另一端只响应,这通常是您将找到的MSDN示例。

在某些情况下,如果从站定期需要向主站发送数据,您可能会发现此问题。您必须使用外部信号机制(在管道外部)或让主设备定期查询/轮询从设备,或者您可以交换客户端是主设备且服务器是从设备的角色。

<强>写入器/读取

您可以使用编写器/阅读器模型,使用两个不同的管道。但是,如果您有多个客户端,则必须以某种方式关联这两个管道,因为每个管道将具有不同的句柄。您可以通过让客户端在连接到每个管道时发送唯一标识符值来执行此操作,然后让服务器关联这两个管道。此数字可以是当前系统时间,甚至可以是全局或本地的唯一标识符。

<强>线程

如果您决定使用同步API,则可以在主机/从机模型中使用线程,如果您不希望在从机侧等待消息时被阻止。但是,您需要在读取消息后(或遇到一系列消息的结束)锁定读取器,然后写入响应(作为从属应该)并最终解锁读取器。您可以使用锁定机制来锁定和解锁读取器,这使得线程处于睡眠状态,因为这些最有效。

TCP的安全问题

TCP而不是命名管道的损失也是最大的问题。 TCP流本身不包含任何安全性。因此,如果安全性是一个问题,您将不得不实现它,并且您可能会创建一个安全漏洞,因为您必须自己处理身份验证。如果正确设置参数,命名管道可以提供安全性。此外,请再次注意:安全性并非易事,通常您会希望使用旨在提供安全性的现有设施。

答案 1 :(得分:0)

我认为您可能遇到命名管道消息模式的问题。在此模式下,每次写入内核管道句柄都构成一条消息。这不一定与您的应用程序认为消息的内容相对应,并且消息可能比您的读取缓冲区大。

这意味着您的管道读取代码需要两个循环,内部读取直到当前[命名管道]消息已被完全接收,外部循环直到收到[应用程序级别]消息。

您的C#客户端代码确实有正确的内部循环,如果IsMessageComplete为false,则再次读取:

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

您的C ++服务器代码没有这样的循环 - Win32 API级别的等价物正在测试返回代码ERROR_MORE_DATA。

我的猜测是,这导致客户端等待服务器读取一个管道实例,而服务器正在等待客户端在另一个管道实例上写入。

答案 2 :(得分:0)

在我看来,你想要做的事情将不会按预期工作。 前段时间我试图做一些看起来像你的代码并得到类似结果的东西,管道就被绞死了 并且很难确定出了什么问题。

我宁愿建议以非常简单的方式使用客户端:

  1. 的CreateFile
  2. 写请求
  3. 阅读答案
  4. 关闭管道。
  5. 如果您希望与客户端进行双向通信,这些客户端也可以从服务器接收未请求的数据 而是实现两个服务器。这是我使用的解决方法:here you can find sources