Domanda

I’m working on a solution that is using named pipes between C# and C++, which I have working to some degree. The server is C# and the client is C++. My problem is that I can not always re-connect to the service. The client needs to be able to connect, disconnect, connect, disconnect, … . The first connection is no problem, but sometimes the re-connect is failing with ERROR_FILE_NOT_FOUND on the client.

I have attach example code. My testing has been on two different computers. The first one, I have to remove the Sleep() in the client completely to get the fail condition. On the second computer, the fail condition is usually reached within the second or third time though the outer while loop. Please note that this is an example or simulation of my problem. I'm working on a much larger scale in actual practice. With very large send buffers. My guess right now is that the file is not cleaning up fast enough, in some cases, but I'm really unsure how to fix the problem. I'm closing everything I know to close.

Server:

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();

}

Client:

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;
}
È stato utile?

Soluzione

There is a race condition in your server between the ConnectionManager loop that creates the named pipe instance, and the Receiver thread that closes the handle. As soon as the Receiver closes the instance handle, it will cease to be visible to the client until the next time CreateNamedPipe is called. This will result in the ERROR_FILE_NOT_FOUND error.

I think there are two potential solutions, depending on whether you want to be able to serve more than one client at a time.

To Serve A Single Client At a Time

If you know that in your application you only need to serve one client at a time, then there is really no need for a separate Receiver thread. Just create the named pipe instance one time with CreateNamedPipe, handle the client request inline when ConnectNamedPipe returns, and then call DisconnectNamedPipe when the client request is done (instead of closing the named pipe instance handle). If you call DisconnectNamedPipe, then you can just call ConnectNamedPipe with the same instance handle and all is good.

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. In this single threaded scenario, the client can receive an ERROR_PIPE_BUSY error when it tries to connect to the pipe. If this happens, just use WaitNamedPipe, which will wait for the server to get around to calling ConnectNamedPipe again.

To Serve Multiple Clients on Multiple Threads

Now, if you want to be able to serve multiple threads simultaneously, the simplest solution is to create a new pipe instance before handling each incoming client request. E.g., as soon as ConnectNamedPipe returns with a new client connection, call CreateNamedPipe to create a new pipe instance before kicking off the thread to handle the incoming client request. This ensures that you will never have a window during which the server doesn't have an instance available.

 // 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));
 }

Altri suggerimenti

You may need to wait in the client for the pipe to exist:

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

Use this code snippet before the call to CreateFile().

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top