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



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

这一切都工作得很好(通过管道发送和接收数据),直到我尝试从客户端写入管道以响应表单的'textchanged'事件。当我这样做时,客户端挂起管道写调用(如果自动刷新关闭,则挂起管道刷新调用)。闯入服务器应用程序,发现它也在等待管道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();
    }
}

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

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

主/从

你可以实现一个主/从模型,其中客户端或服务器是主,而另一端只响应,这通常是你会发现MSDN的例子。

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

作者/读者

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

如果你决定使用同步API,如果你不想在从端等待消息时被阻塞,你可以使用带有主/从模型的线程。然而,您将希望在读取消息(或遇到一系列消息的结尾)之后锁定阅读器,然后写入响应(正如slave应该的那样)并最终解锁阅读器。你可以使用使线程处于睡眠状态的锁机制来锁定和解锁阅读器,因为这将是最有效的。

TCP的安全问题

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

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

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

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

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

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

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

在我看来,你正在努力做的事情将不如预期的那样工作。前一段时间,我试图做一些看起来像你的代码,并得到类似的结果,管道只是挂起而且很难确定哪里出了问题。

我建议用一种非常简单的方式使用client:

  1. 它仅仅
  2. <
  3. 写请求/gh>
  4. 阅读答案
  5. 关闭管。

如果你想与客户端有双向通信,也可以从服务器接收未请求的数据,你应该而是实现两个服务器。这是我使用的变通方法:在这里你可以找到源代码。

最新更新