通过命名管道将C ++客户端连接到C#服务器

时间:2013-11-21 22:08:53

标签: c# c++ named-pipes

我正在研究一种在C#和C ++之间使用命名管道的解决方案,我已经在某种程度上工作了。服务器是C#,客户端是C ++。我的问题是我不能总是重新连接到服务。客户端需要能够连接,断开连接,断开连接,....第一个连接没问题,但有时重新连接失败,客户端上有ERROR_FILE_NOT_FOUND。

我附上了示例代码。我的测试是在两台不同的计算机上进行的第一个,我必须完全删除客户端中的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;
}

2 个答案:

答案 0 :(得分:0)

您可能需要在客户端等待管道存在:

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

在调用CreateFile()之前使用此代码段。

答案 1 :(得分: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));
 }