簡體   English   中英

雙工命名管道在某個寫入時掛起

[英]Duplex named pipe hangs on a certain write

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

如果您使用單獨的線程,則在寫入管道的同時將無法從管道讀取。 例如,如果您正在從管道執行阻塞讀取,然后執行后續阻塞寫入(來自不同的線程),則寫入調用將等待/阻塞,直到讀取調用完成,並且在許多情況下,如果這是意外行為,您的程序將陷入僵局。

我沒有測試重疊的I / O,但它可以解決這個問題。 但是,如果您決定使用同步調用,則以下模型可幫助您解決問題。

主從

您可以實現主/從模型,其中客戶端或服務器是主服務器,另一端只響應,這通常是您將找到MSDN示例。

在某些情況下,如果從站需要定期向主站發送數據,您可能會發現此問題。 您必須使用外部信號機制(管道外部)或讓主設備定期查詢/輪詢從設備,或者您可以交換客戶端為主設備且服務器為從設備的角色。

寫入/讀取器

您可以使用編寫器/閱讀器模型,其中您使用兩個不同的管道。 但是,如果您有多個客戶端,則必須以某種方式關聯這兩個管道,因為每個管道將具有不同的句柄。 您可以通過讓客戶端在連接到每個管道時發送唯一標識符值來執行此操作,然后讓服務器關聯這兩個管道。 此數字可以是當前系統時間,甚至可以是全局或本地的唯一標識符。

主題

如果您決定使用同步API,則可以在主機/從機模型中使用線程,如果您不希望在從機側等待消息時被阻止。 但是,您需要在讀取消息后(或遇到一系列消息的結束)鎖定讀取器,然后寫入響應(作為從屬應該)並最終解鎖讀取器。 您可以使用鎖定機制來鎖定和解鎖讀取器,這使得線程處於睡眠狀態,因為這些最有效。

TCP的安全問題

TCP而不是命名管道的損失也是最大的問題。 TCP流本身不包含任何安全性。 因此,如果安全性是一個問題,您將不得不實現它,並且您可能會創建一個安全漏洞,因為您必須自己處理身份驗證。 如果正確設置參數,命名管道可以提供安全性。 此外,請再次注意:安全性並非易事,通常您會希望使用旨在提供安全性的現有設施。

在我看來,你想要做的事情將不會按預期工作。 前段時間我試圖做一些看起來像你的代碼並得到類似結果的東西,管道剛掛了,很難確定出了什么問題。

我寧願建議以非常簡單的方式使用客戶端:

  1. 的CreateFile
  2. 寫請求
  3. 閱讀答案
  4. 關閉管道。

如果您希望與能夠從服務器接收未請求數據的客戶端進行雙向通信,則應該實現兩個服務器。 這是我使用的解決方法: 在這里你可以找到來源

我認為您可能遇到命名管道消息模式的問題。 在此模式下,每次寫入內核管道句柄都構成一條消息。 這不一定與您的應用程序認為的消息相對應,並且消息可能比您的讀取緩沖區大。

這意味着您的管道讀取代碼需要兩個循環,內部讀取直到當前[命名管道]消息已完全接收,外部循環直到收到[應用程序級別]消息。

您的C#客戶端代碼確實具有正確的內部循環,如果IsMessageComplete為false,則再次讀取:

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

您的C ++服務器代碼沒有這樣的循環 - Win32 API級別的等效項是測試返回代碼ERROR_MORE_DATA。

我的猜測是,這導致客戶端等待服務器讀取一個管道實例,而服務器正在等待客戶端在另一個管道實例上寫入。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM