2013-11-21 63 views
0

我正在研究一種在C#和C++之間使用命名管道的解決方案,我在某種程度上正在努力。服務器是C#,客戶端是C++。我的問題是,我不能總是重新連接到服務。客戶端需要能夠連接,斷開,連接,斷開......。第一個連接沒有問題,但有時重新連接失敗,客戶端上的ERROR_FILE_NOT_FOUND。通過NamedPipe將C++客戶端重新連接到C#服務器

我附上示例代碼。我的測試已在兩臺不同的計算機上進行。第一個,我必須徹底刪除客戶端中的Sleep()以獲得失敗狀態。在第二臺計算機上,失敗情況通常在第二次或第三次通過外部while循環時達到。請注意,這是我的問題的一個例子或模擬。我在實際工作中的規模更大。具有非常大的發送緩衝區。我現在的猜測是,在某些情況下,文件清理速度不夠快,但我真的不確定如何解決問題。我正在關閉我所知道的一切。

服務器:

public class NamedPipeServer 
{ 
[DllImport("kernel32.dll", SetLastError = true)] 
public static extern SafeFileHandle CreateNamedPipe(String pipeName, 
                 uint dwOpenMode, 
                 uint dwPipeMode, 
                 uint nMaxInstances, 
                 uint nOutBufferSize, 
                 uint nInBufferSize, 
                 uint nDefaultTimeOut, 
                 IntPtr lpSecurityAttributes); 

[DllImport("kernel32.dll", SetLastError = true)] 
public static extern int ConnectNamedPipe(SafeFileHandle hNamedPipe, 
              IntPtr lpOverlapped); 

[DllImport("kernel32.dll", SetLastError = true)] 
public static extern int DisconnectNamedPipe(SafeFileHandle hNamedPipe); 

[DllImport("kernel32.dll", SetLastError = true)] 
public static extern int GetLastError(); 

public const uint INBOUND = (0x00000001); 
public const uint FILE_FLAG_OVERLAPPED = (0x40000000); 
public const uint REJECT_REMOTE_CLIENTS = (0x00000008); 
public const uint READMODE_BYTE = (0x00000000); 

public bool NamePipeProcessing = false; 

private const int BUFFER_SIZE = 100; 
private SafeFileHandle _pipeHandle; 
private Client _clientInfo; 
private Thread _listenThread; 
private Thread _receiveThread; 
private string _pipeName; 

public class Client 
{ 
    public SafeFileHandle handle; 
    public FileStream stream; 
} 

public NamedPipeServer(string pipeName) 
{ 
    _pipeName = pipeName; 
    _clientInfo = new Client(); 
    _listenThread = new Thread(new ThreadStart(ConnectionManager)); 
    _listenThread.Start(); 
} 

private void ConnectionManager() 
{ 
    while (NamePipeProcessing) 
    { 
    _pipeHandle = CreateNamedPipe(_pipeName, INBOUND|FILE_FLAG_OVERLAPPED, 
            REJECT_REMOTE_CLIENTS|READMODE_BYTE, 
            1, 0, BUFFER_SIZE, 0, IntPtr.Zero); 
    // could not create namedPipe 
    if (_pipeHandle.IsInvalid) 
     return; 
    int errorCode = GetLastError(); 

    Console.WriteLine("pipe created "+ _pipeName + " ErrorCode:" + errorCode); 

    //THIS IS A BLOCKING CALL 
    int success = ConnectNamedPipe(_pipeHandle , IntPtr.Zero); 

    // could not connect to client 
    if (success == 0) 
    { 
     return; 
    } 

    _clientInfo.handle = _pipeHandle; 
    _clientInfo.stream = new FileStream(_clientInfo.handle, FileAccess.Read, BUFFER_SIZE, true); 

    _receiveThread = new Thread(new ThreadStart(Receiver)); 
    _receiveThread.Start(); 
    _receiveThread.Join(); 
    } 
} 

private void Receiver() 
{ 
    int bytesReceived = 0; 
    byte[] buffer = null; 
    while (NamePipeProcessing) 
    { 
    bytesReceived = 0; 

    // Attempt to received data from pipe 
    try 
    { 
     buffer = new byte[BUFFER_SIZE]; 
     //THIS IS A BLOCKING CALL 
     bytesReceived = _clientInfo.stream.Read(buffer, 0, BUFFER_SIZE); 
    } 
    catch (Exception ex) 
    { 
     Console.WriteLine(ex.ToString()); 
     break; 
    } 

    // client has disconnected 
    if (bytesReceived == 0) 
    { 
     Console.WriteLine(" No bytes Received"); 
     break; 
    } 
    // if data was received 
    if (bytesReceived > 0) 
    { 
     // handle message 
     Console.WriteLine("Received: " + bytesReceived.ToString()); 
    } 
    } 
    _clientInfo.stream.Close(); 
    _clientInfo.handle.Close(); 
    _clientInfo.stream.Dispose(); 
    _clientInfo.handle.Dispose(); 
} 

public void StopServer() 
{ 
    try 
    { 

    NamePipeProcessing = false; 
    DisconnectNamedPipe(_pipeHandle); 
    _listenThread.Abort(); 

    } 
    catch (Exception ex) 
    { 
    Console.WriteLine(ex.ToString()); 
    } 
} 

} 
Class Program 
{ 
static void Main(string[] args) 
{ 
    NamedPipeServer PServer = new NamedPipeServer(@"\\.\pipe\myNamedPipe"); 
    NamedPipeServer PS  string Ms="Start"; 
    PServer.NamePipeProcessing = true; 
    do 
    { 
    //Console.WriteLine("Enter quit to exit server"); 
    Ms = Console.ReadLine(); 
    //PServer2.SendMessage(Ms, PServer2.clientse); 
    } while (Ms != "quit"); 

    PServer.StopServer(); 
    Console.WriteLine("Press any key to stop"); 
    Console.ReadKey(); 

} 

客戶:

int _tmain(int argc, _TCHAR* argv[]) 
{ 

    BYTE* byteArray = (BYTE*) malloc(BUFFER_SIZE); 
    DWORD cbWritten = (DWORD)strlen((const char*)byteArray);; 

    int count = 0; 
    int count2 = 0; 
    int value = 10; 
    while(count2 < 5) 
    { 

    LPTSTR lpszPipename = TEXT("\\\\.\\pipe\\myNamedPipe"); 
    hPipe=CreateFile(lpszPipename, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); 
    if ((hPipe == NULL || hPipe == INVALID_HANDLE_VALUE)) 
    { 
     printf("Could not open the pipe - (error %ld)\n",GetLastError()); 
    } 
    else 
    { 
     printf("Send data\n"); 
     while(count < 3) 
     { 

     byteArray[0] = value; 
     byteArray[1] = value + 10; 
     byteArray[2] = value + 1; 
     byteArray[3] = value + 20; 
     byteArray[4] = value - 5; 
     byteArray[5] = value + 30; 

     value = 10; 
     WriteFile(hPipe, byteArray, 10, &cbWritten, NULL); 
     Sleep(1); 
     if(count != 3) 
      count +=1; 
     } 
    }  
    CloseHandle(hPipe); 
    count = 0 ; 
    count2 += 1; 
    Sleep(1); 
    printf("Done Sending\n"); 
    } 
    free(byteArray); 
    printf("Press any key to exit..."); fflush(0); 
    _getch(); 

    return 0; 
} 
+0

進程互操作永遠是脆弱的,你只是不知道在其他進程中發生了什麼。有明顯的失敗模式,比如嘗試連接過早(等待)或嘗試重新連接到不存在的進程(如此放棄)。 –

回答

0

在您的服務器中存在創建命名管道實例的ConnectionManager循環與關閉句柄的Receiver線程之間的競爭條件。只要Receiver關閉實例句柄,它就會停止對客戶端可見,直到下一次調用CreateNamedPipe。這將導致ERROR_FILE_NOT_FOUND錯誤。

我認爲有兩種可能的解決方案,具體取決於您是否希望一次能夠服務多個客戶端。

服務於一個客戶端在時間

如果你知道在你的應用程序只需要在一個時間爲一個客戶,那麼就真的沒有必要爲一個單獨的線程Receiver。只需使用CreateNamedPipe一次性創建命名管道實例,當ConnectNamedPipe返回時處理內聯客戶端請求,然後在客戶端請求完成時(而不是關閉命名管道實例句柄)調用DisconnectNamedPipe。如果您致電DisconnectNamedPipe,那麼您可以使用相同的實例句柄調用ConnectNamedPipe,並且一切都很好。

private void ConnectionManager() 
{ 
    _pipeHandle = CreateNamedPipe(_pipeName, INBOUND|FILE_FLAG_OVERLAPPED, 
            REJECT_REMOTE_CLIENTS|READMODE_BYTE, 
            1, 0, BUFFER_SIZE, 0, IntPtr.Zero); 
    while (NamePipeProcessing) 
    { 
     //THIS IS A BLOCKING CALL 
     int success = ConnectNamedPipe(_pipeHandle , IntPtr.Zero); 

     // handle the request here with CreateFile(), etc 

     DisconnectNamedPipe(_pipeHandle); 
    } 

N.B.在此單線程方案中,客戶端在嘗試連接到管道時可能會收到ERROR_PIPE_BUSY錯誤。如果發生這種情況,只需使用WaitNamedPipe,這將等待服務器再次調用ConnectNamedPipe

在多線程服務多個客戶現在

,如果你希望能夠同時爲多個線程,最簡單的辦法是之前創建一個新的管道實例處理每個到來的客戶端請求。例如,只要ConnectNamedPipe返回一個新的客戶端連接,呼叫CreateNamedPipe在開始處理傳入客戶端請求的線程之前創建一個新的管道實例。這可以確保您永遠不會有一個窗口,在這個窗口期間服務器沒有可用的實例。

// create the first named pipe instance 
_pipeHandle = CreateNamedPipe(...) 

while (NamePipeProcessing) 
    { 

    // wait for new client connection 
    ConnectNamedPipe(_pipeHandle , IntPtr.Zero); 

    // ...error checking omitted 

    // save off instance handle for use by Receiver thread - it will CloseHandle it 
    _clientInfo.handle = _pipeHandle; 

    // create a new named pipe instance before processing this request 
    _pipeHandle = CreateNamedPipe(...) 

    // spawn Receiver thread to handle request 
    _receiveThread = new Thread(new ThreadStart(Receiver)); 
} 
+0

謝謝你的幫助。在我試圖停止/關閉服務器之前,單個客戶端在某種時間方法下,這將是我的首選選項,它的工作效果很好。此時服務器掛起並需要進程終止。對於多個線程上的多客戶端選項,我在_clientInfo.Handle的Receiver線程中收到一個ObjectDisposedException。這隻發生在部分時間。 – TaterTot

0

您可能需要在客戶端等待存在的管:

if (WaitNamedPipe(THE_PIPE, NMPWAIT_WAIT_FOREVER) == 0) { 
    printf("WaitNamedPipe failed. error=%d\n", GetLastError()); 
    return; 
} 

使用此代碼段呼叫前到CreateFile()。

+0

添加上面的內容,我現在得到錯誤ERROR_FILE_NOT_FOUND。如果我爲這個錯誤添加一個catch然後處理,那麼客戶端仍然不會連接,當嘗試進行時,獲取ERROR_FILE_NOT_FOUND。它會通過循環再次處理,但服務器在任何時候都不會收到任何數據,儘管客戶端有時會指示數據已發送。 – TaterTot

相關問題