2011-11-09 9 views
4

私はC++パイプサーバーアプリケーションとWindowsの名前付きパイプ(デュプレックス、メッセージモード、別の読み取りスレッドでの待機/ブロック)を介して通信するC#パイプクライアントアプリケーションを持っています。特定の書き込みで二重の名前付きパイプがハングする

フォーム 'textchanged'イベントに応答して、クライアントからパイプへの書き込みを試みるまで、すべてがうまく動作します(パイプ経由でデータを送受信しています)。これを行うと、クライアントはパイプ書き込み呼び出し(またはautoflushがオフの場合はフラッシュ)でハングします。サーバのアプリケーションに侵入すると、パイプの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

私はあなたが名前付きパイプメッセージモードの問題に実行しているかもしれないと思います。このモードでは、カーネルパイプハンドルへの各書き込みはメッセージを構成します。これは必ずしもあなたのアプリケーションがMessageとみなしているものに対応しておらず、メッセージはあなたの読み取りバッファよりも大きいかもしれません。

これは、現在の[名前付きパイプ]メッセージが完全に受信されるまで内側の読み取り値と[アプリケーションレベル]メッセージが受信されるまで外側のループが2回ループする必要があることを意味します。 - のWin32 APIレベルでの等価テストです

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

あなたのC++サーバ・コードは、このようなループを持っていません。

あなたのC#クライアントコードはIsMessageCompleteがfalseの場合は、再度読んで、正しい内部ループを持っています戻りコードはERROR_MORE_DATAです。

私は、どうにかして、サーバーがクライアントが別のパイプインスタンスに書き込むのを待っている間に、サーバーが1つのパイプインスタンスで読み取るのを待っているクライアントにつながっていると思います。

+0

私はあなたが何を意味するかを見ています: - 私は本当に大きなバッファ(400k!)を試して、40文字の文字列を送ります。 - クライアントからmsgを送信する前後に、サーバーが読み取りfnで待機しています。これは変更されません。 - これは2つのパイプインスタンスを指していると同意しますが、以前の通信は正常に機能し、パイプを閉じる/再開するためにブレークポイントがヒットしませんでした。 – MGB

+0

パイプをバイトモードで試行し、クライアントの送信を更新ループに延期しようとしました。まだ同じ問題があります。 – MGB

0

あなたがしようとしていることは、むしろ期待どおりに機能しないようです。 しばらく前に、私はあなたのコードのように見えるものをしようとしていて、同様の結果を得ました。パイプはちょうど をぶら下げていました。

私はむしろ非常に単純な方法でクライアントを使用することをお勧め:

  1. のCreateFile
  2. 書き込み要求
  3. 読むの答え
  4. 閉じるパイプ。

あなたはまた、あなたが むしろ二つのサーバを実装する必要があり、サーバから要求されていないデータを受信することが可能である顧客との双方向通信をしたい場合。これは私が使用した回避策でした:here you can find sources

+0

お返事ありがとうございます。私は実際に名前付きパイプの代わりにTCPを使うようにこれを入れ替えました。うまくいきました。 – MGB

4

別々のスレッドを使用している場合は、パイプに書き込むと同時にパイプから読み取ることができなくなります。たとえば、パイプからのブロッキングの読み込みを行ってから別のスレッドからのブロッキング書き込みを行っている場合、書き込み呼び出しは読み込み呼び出しが完了するまで待機/ブロックし、多くの場合、プログラムがデッドロック状態になる

重複したI/Oはテストしていませんが、この問題を解決できる可能性があります。ただし、同期呼び出しを使用することが決定された場合は、以下のモデルを使用して問題を解決することができます。あなたは、クライアントまたはサーバがマスターのみが応答するもう一方の端であるマスタ/スレーブモデルを実装することができ

マスター/スレーブは、あなたがするMSDNの例がありますどのような一般的です。

スレーブが周期的にデータをマスタに送信する必要がある場合に、この問題が発生することがあります。外部のシグナリングメカニズム(パイプの外側)を使用するか、マスターが定期的にスレーブに問い合わせ/ポーリングするか、またはクライアントがマスターでありサーバーがスレーブであるロールをスワップする必要があります。

ライター/リーダー

あなたは二つの異なるパイプを使用するライター/リーダーモデルを使用することができます。ただし、各パイプに異なるハンドルがあるため、複数のクライアントがある場合は、これらの2つのパイプを何らかの方法で関連付ける必要があります。これを行うには、クライアントが各パイプへの接続時に一意の識別子値を送信し、サーバーが2つのパイプを関連付けることができるようにします。この番号は、現在のシステム時刻でも、グローバルまたはローカルの一意の識別子でもかまいません。

スレッドあなたはスレーブ側のメッセージを待っている間にブロックされたくない場合は、マスター/スレーブモデルでスレッドを使用することができ、同期APIを使用することが決定された場合

。しかし、読者がメッセージを読んだら(あるいは一連のメッセージの終わりに遭遇した後)、読者をロックして、スレーブのようにレスポンスを書いて、最後に読者のロックを解除したいと思うでしょう。最も効率的なスレッドをスリープ状態にするロック機構を使用して、リーダーをロックおよびロック解除できます。

セキュリティ問題にTCPは、名前付きパイプの代わりにTCPと一緒に行く

損失も最大の可能な問題です。 TCPストリームにはネイティブセキュリティは含まれていません。したがって、セキュリティが重要な場合は、それを実装しなければならず、自分で認証を処理する必要があるため、セキュリティホールを作成する可能性があります。名前付きパイプは、パラメータを適切に設定するとセキュリティを提供できます。また、もう一度明記すると、セキュリティは単純なものではなく、一般的にセキュリティを提供するように設計された既存の施設を使用することが望まれます。

関連する問題