2011-11-09 22 views
4

我有一個C++管道服務器應用程序和一個C#管客戶端應用程序通過Windows命名管道的通信(全雙工,消息模式,等待/在單獨的讀取線程阻塞)。雙工命名管道掛在某些寫

這一切工作正常(發送和通過管道接收數據),直到我嘗試寫來自客戶端的管道響應形式的「框TextChanged」事件。當我這樣做時,客戶端掛在管道寫入呼叫上(或者在自動刷新關閉的情況下刷新呼叫)。打入服務器應用程序顯示它也在等待管道ReadFile調用,而不是返回。 我試圖運行客戶端寫在另一個線程 - 相同的結果。

可疑某種死鎖或競爭條件的,但看不到的地方......別以爲我同時寫入到管道。

Update1:​​嘗試管道在字節模式而不是消息模式 - 相同的鎖定。

UPDATE2:奇怪的是,如果(且僅當)我抽大量的數據從服務器到客戶端,它能治癒死機!?

Server代碼:

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

您在服務器端處理讀取錯誤看起來有點不禮貌。作爲一種診斷手段,我建議你暫時更改服務器,以便在發生讀取錯誤時退出,這樣就可以確定客戶端的寫入和服務器端的讀取與同一個管道相關。 –

+0

通過更改服務器來嘗試簡化情況也許會有所幫助,以便它不會將任何數據寫入管道,以便可以將客戶端設置爲單線程。如果這不能消除問題,至少可以確定它不是某種線程問題。 –

+0

Chris:添加了代碼:) @Harry:它固執地坐在ReadFile fn中,甚至沒有返回錯誤。建議多管道......見下文。嘗試沒有服務器寫入:相同的結果。仍然在尋找...: -/ – MGB

回答

0

我想你可能正在運行與命名管道消息模式的問題。在這種模式下,每次寫入內核管道句柄都會構成一條消息。這不一定與應用程序將消息視爲的內容相對應,並且消息可能比讀緩衝區大。

這意味着你的管式讀取碼需要兩個環路,內讀取,直到當前[命名管道]消息已經被完全接收,並且外循環,直到你的[應用級]消息已被接收。

你的C#的客戶端代碼確實有一個正確的內環,再讀書,如果IsMessageComplete是假的:

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

你的C++服務器代碼沒有這樣一個循環 - 相當於在Win32 API的水平測試爲返回碼ERROR_MORE_DATA。

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

+0

我明白你的意思了,儘管如此: - 我嘗試了一個非常大的緩衝區(400k!),並且我發送了一個40個字符的字符串。 - 服務器在我從客戶端發送消息之前和之後在讀取fn中等待 - 它不會退出。 - 同意這將指向兩個管道實例,但以前的通信工作正常,沒有斷點關閉/重新打開管道任何一方。 – MGB

+0

以字節模式嘗試管道,並試圖推遲客戶端發送到更新循環。同樣的問題還在。 – MGB

0

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

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

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

如果您希望與客戶端進行雙向通信,而客戶端也能夠從服務器接收未請求的數據,您應該使用 而不是實現兩臺服務器。這是我使用的解決方法:here you can find sources

+0

感謝您的回覆。我實際上換了這個使用TCP而不是命名管道,它工作正常。 – MGB

4

如果您使用單獨的線程,您將無法在寫入的同時從管道中讀取數據。例如,如果你正在從管道進行阻塞讀取,然後阻塞寫入(來自不同線程),那麼寫入調用將等待/阻塞,直到讀取調用完成,並且在很多情況下,如果這是意外行爲,程序將陷入僵局。

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

主/從

你可以實現一個主/從模式,即客戶端或服務器是主,另一端只響應通常是什麼,你會發現在MSDN的例子是。

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

寫入/讀取器

你可以使用,你使用兩個不同的管道一個寫入/讀取器模式。但是,如果您有多個客戶端,則必須以某種方式關聯這兩個管道,因爲每個管道都有不同的句柄。你可以通過讓客戶端發送一個唯一的標識符值連接到每個管道,然後讓服務器關聯這兩個管道來做到這一點。這個數字可能是當前的系統時間,甚至是全球或本地的唯一標識符。

線程

如果你有決心使用同步API,你可以使用線程與主/從模式,如果你不想在等待從機側的消息被封鎖做。然而,當讀者讀取一條消息(或者遇到一系列消息的結尾)之後,你會想要鎖定讀寫器,然後寫入響應(如從屬應該)並最終解鎖讀寫器。您可以使用鎖定機制來鎖定和解鎖閱讀器,從而使線程進入休眠狀態,因爲這些機制效率最高。

安全問題TCP

與TCP去代替命名管道的損失也是最大的可能出現的問題。 TCP流本身不包含任何安全性。所以如果安全是一個問題,你將不得不實施這一點,你有可能造成安全漏洞,因爲你必須自己處理驗證。如果您正確設置參數,命名管道可以提供安全性。另外,要更清楚地注意到:安全並非簡單的事情,通常您會希望使用現有的設施來提供它。