【问题标题】:Duplex named pipe hangs on a certain write双工命名管道在某个写入时挂起
【发布时间】:2011-12-25 12:46:25
【问题描述】:

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

一切正常(通过管道发送和接收数据),直到我尝试从客户端写入管道以响应表单“textchanged”事件。当我这样做时,客户端挂起管道写入调用(或者如果 autoflush 关闭,则刷新调用)。闯入服务器应用程序表明它也在等待管道 ReadFile 调用并且没有返回。 我尝试在另一个线程上运行客户端写入——结果相同。

怀疑某种死锁或竞争条件,但看不到在哪里...不要认为我正在同时写入管道。

更新 1:尝试使用字节模式而不是消息模式的管道 - 相同的锁定。

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

服务器代码:

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

【问题讨论】:

  • 您在服务器端对读取错误的处理看起来有点狡猾。作为一个诊断措施,我建议你临时改变服务器,让它在发生读取错误时退出,这样你就可以确定客户端的写入和服务器端的读取是在同一个管道上的。
  • 尝试通过更改服务器使其不向管道写入任何数据来简化情况也可能会有所帮助,从而可以使客户端成为单线程。如果这不能消除问题,您至少可以确定这不是某种线程问题。
  • Chris:添加代码 :) @Harry:它顽固地位于 ReadFile fn 中,甚至没有返回错误。是否建议使用多个管道...见下文。尝试没有服务器写入:相同的结果。还在寻找... :-/
  • Win32 管道的设计目的不是为了有用...我在这里也遇到了一个问题,代码必须采取大量奇怪的怪癖才能工作。
  • @ActiveTrayPrntrTagDataStrDrvr 愿意分享这些怪癖吗?

标签: windows multithreading named-pipes


【解决方案1】:

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

我没有测试过重叠的 I/O,但它或许能够解决这个问题。但是,如果您确定要使用同步调用,那么下面的模型可能会帮助您解决问题。

主/从

您可以实现主/从模型,其中客户端或服务器为主,另一端仅响应,这通常是您会发现的 MSDN 示例。

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

作家/读者

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

线程

如果您决定使用同步 API,如果您不想在从属端等待消息时被阻塞,则可以使用具有主/从模型的线程。但是,您将希望在阅读器读取消息(或遇到一系列消息的结尾)后锁定阅读器,然后写入响应(作为从站应该)并最终解锁阅读器。您可以使用使线程进入睡眠状态的锁定机制来锁定和解锁阅读器,因为这些机制效率最高。

TCP 的安全问题

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

【讨论】:

    【解决方案2】:

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

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

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

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

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

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

    【讨论】:

    • 不过,我明白你的意思: - 我尝试了一个非常大的缓冲区(400k!),我正在发送一个 40 字符的字符串。 - 在我从客户端发送消息之前和之后,服务器正在读取 fn 中等待 - 它没有让步。 - 同意这将指向两个管道实例,但之前的通讯工作正常,并且没有断点来关闭/重新打开任一管道。
    • 在字节模式下尝试管道,还尝试将客户端发送延迟到更新循环中。同样的问题。
    【解决方案3】:

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

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

    1. 创建文件
    2. 写请求
    3. 阅读答案
    4. 关闭管道。

    如果您想与客户端进行两种方式的通信,这些客户端也能够从服务器接收未请求的数据,您应该 而是实现两台服务器。这是我使用的解决方法:here you can find sources

    【讨论】:

    • 感谢您的回复。我实际上把它换成使用 TCP 而不是命名管道,效果很好。
    猜你喜欢
    • 2013-01-22
    • 1970-01-01
    • 1970-01-01
    • 2012-07-31
    • 2015-05-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-02-28
    相关资源
    最近更新 更多