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

发布于 2024-12-14 15:53:58 字数 3926 浏览 0 评论 0原文

我有一个 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 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();
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

花开浅夏 2024-12-21 15:53:58

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

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

主/从

您可以实现一个主/从模型,其中客户端或服务器是主设备,另一端仅响应,这通常是您在 MSDN 示例中找到的内容。

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

写入器/读取器

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

线程

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

TCP的安全问题

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

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.

自由如风 2024-12-21 15:53:58

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

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

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

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

您的 C++ 服务器代码没有这样的循环 - Win32 API 级别的等效内容是测试返回代码错误_更多_数据。

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

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.

何以笙箫默 2024-12-21 15:53:58

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

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

  1. CreateFile
  2. 写请求
  3. 读取答案
  4. 关闭管道。

如果您想与客户端进行两种方式的通信,并且也能够从服务器接收未请求的数据,您应该
而是实施两个服务器。这是我使用的解决方法: 在这里您可以找到来源

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文