如何在客户端断开连接后使命名管道不再忙碌?

3
我使用命名管道,并希望在原始客户端断开连接后重用服务器上的同一管道,以允许连接另一个客户端。我的做法是:
  • 服务器使用CreateNamedPipe创建管道
  • 服务器使用WriteFile写入数据,并在返回错误ERROR_PIPE_LISTENING(在任何客户端连接之前)时进行重试
  • 客户端使用CreateFile连接
  • 客户端读取数据
  • 客户端使用CloseHandle关闭管道句柄
  • 此时服务器在尝试写入更多数据时会出现错误ERROR_NO_DATA
  • 服务器使用DisconnectNamedPipe从管道中断开连接,我希望这应该可以使其再次可用
  • 服务器尝试写入数据,出现错误ERROR_PIPE_NOT_CONNECTED,直到没有错误为止
  • 但是,当新客户端连接并尝试在管道上创建文件时,它会得到ERROR_PIPE_BUSY
因此,我的问题是:我需要执行哪些其他步骤才能正确地从管道中断开客户端,以便新客户端可以连接?
2个回答

4
问题在于您省略了ConnectNamedPipe(),该函数应始终在CreateNamedPipe()或DisconnectNamedPipe()之后调用,但在尝试任何I/O之前都要调用它。
如果您不想在等待客户端连接时阻塞,可以以异步I/O模式创建管道,在这种情况下,对ConnectNamedPipe()的调用需要一个事件对象,当客户端连接时将设置该对象。或者,您可以设置PIPE_NOWAIT并定期调用ConnectNamedPipe(),直到成功为止,但此功能是遗留功能,不建议使用。(在大多数情况下,使用事件对象也比轮询更有效率。)
正如您已经发现的那样,Windows确实允许您在没有调用ConnectNamedPipe()的情况下运行,但由于这种行为未记录在文档中,因此应该避免使用。同样,未记录在文档中的是,调用ConnectNamedPipe()而不等待其成功会重置管道的连接状态,因此不应该依赖它。
按照要求,这里有一些实际代码来演示管道的服务器端的使用。此代码来自GUI应用程序,因此使用异步I/O,但应注意它只与一个客户端通信。 (但是,它可以通过仅进行轻微修改就在多个线程中运行。)
void wait_for_object(HANDLE object)
{
  DWORD dw;
  MSG msg;

  for (;;) 
  {
    dw = MsgWaitForMultipleObjectsEx(1, &object, INFINITE, QS_ALLINPUT, 0);

    if (dw == WAIT_OBJECT_0) break;
    if (dw == WAIT_OBJECT_0 + 1) 
    {
      while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) DispatchMessage(&msg);
      continue;
    }
    srvfail(L"sleep() messageloop", GetLastError());
  }
}

HANDLE server_pipe;
HANDLE io_event;

void pipe_connection(void)
{
    OVERLAPPED overlapped;
    DWORD dw, err;

    SecureZeroMemory(&overlapped, sizeof(overlapped));
    overlapped.hEvent = io_event;

    if (!ReadFile(server_pipe, input_buffer, sizeof(input_buffer) - 1, NULL, &overlapped))
    {
        err = GetLastError();
        if (err == ERROR_IO_PENDING)
        {
            wait_for_object(io_event);
            if (!GetOverlappedResult(server_pipe, &overlapped, &dw, FALSE)) 
            {
                srvfail(L"Read from pipe failed asynchronously.", GetLastError());
            }
        }
        else
        {
            srvfail(L"Read from pipe failed synchronously.", GetLastError());
        }
    }
    else
    {
        if (!GetOverlappedResult(server_pipe, &overlapped, &dw, FALSE)) 
        {
            srvfail(L"GetOverlappedResult failed reading from pipe.", GetLastError());
        }
    }

    input_buffer[dw] = '\0';

    process_command();

    if (!WriteFile(server_pipe, &output_struct, 
        ((char *)&output_struct.output_string - (char *)&output_struct) + output_struct.string_length, 
        NULL, &overlapped))
    {
        err = GetLastError();
        if (err == ERROR_IO_PENDING)
        {
            wait_for_object(io_event);
            if (!GetOverlappedResult(server_pipe, &overlapped, &dw, FALSE)) 
            {
                srvfail(L"Write to pipe failed asynchronously.", GetLastError());
            }
        }
        else
        {
            srvfail(L"Write to pipe failed synchronously.", GetLastError());
        }
    }
    else
    {
        if (!GetOverlappedResult(server_pipe, &overlapped, &dw, FALSE)) 
        {
            srvfail(L"GetOverlappedResult failed writing to pipe.", GetLastError());
        }
    }

    if (!FlushFileBuffers(server_pipe)) srvfail(L"FlushFileBuffers failed.", GetLastError());
    if (!DisconnectNamedPipe(server_pipe)) srvfail(L"DisconnectNamedPipe failed.", GetLastError());
}

void server(void)
{
    OVERLAPPED overlapped;
    DWORD err, dw; 

    // Create the named pipe

    server_pipe = CreateNamedPipe(pipe_name, PIPE_ACCESS_DUPLEX | FILE_FLAG_FIRST_PIPE_INSTANCE | FILE_FLAG_OVERLAPPED, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE, 1, buffer_size, buffer_size, 0, NULL);
    if (server_pipe == INVALID_HANDLE_VALUE) srvfail(L"CreateNamedPipe failed.", GetLastError());

    // Wait for connections

    io_event = CreateEvent(NULL, FALSE, FALSE, NULL);
    if (io_event == NULL) srvfail(L"CreateEvent(io_event) failed.", GetLastError());

    for (;;)
    {
        SecureZeroMemory(&overlapped, sizeof(overlapped));
        overlapped.hEvent = io_event;

        if (!ConnectNamedPipe(server_pipe, &overlapped))
        {
            err = GetLastError();
            if (err == ERROR_PIPE_CONNECTED)
            {
                pipe_connection();
            }
            else if (err == ERROR_IO_PENDING)
            {
                wait_for_object(io_event);
                if (!GetOverlappedResult(server_pipe, &overlapped, &dw, FALSE)) 
                {
                    srvfail(L"Pipe connection failed asynchronously.", GetLastError());
                }
                pipe_connection();
            }
            else
            {
                srvfail(L"Pipe connection failed synchronously.", GetLastError());
            }
        }
        else
        {
            if (!GetOverlappedResult(server_pipe, &overlapped, &dw, FALSE)) 
            {
                srvfail(L"GetOverlappedResult failed connecting pipe.", GetLastError());
            }
            pipe_connection();
        }
    }
}

这段代码已经从原始版本中编辑过,以删除多余的逻辑。我没有尝试编译编辑后的版本,因此可能会有一些小问题。


2

通过尝试不同的调用方式,我发现以下方式可以正常工作:

针对“ERROR_PIPE_NOT_CONNECTED”错误,服务器应执行以下操作:

  // allow connecting, no wait
  DWORD mode = PIPE_NOWAIT;
  SetNamedPipeHandleState(_callstackPipe,&mode,NULL,NULL);
  ConnectNamedPipe(_callstackPipe,NULL);
  mode = PIPE_WAIT;
  SetNamedPipeHandleState(_callstackPipe,&mode,NULL,NULL);

ConnectNamedPipe 使管道重新连接(不忙)。

注意:管道状态暂时更改为 PIPE_NOWAIT,否则 ConnectNamedPipe 将无限期地阻塞服务器线程等待客户端。

另一种解决方法可能是在服务器端完全关闭句柄并重新打开它。


1
这种行为是未记录的。最好等待ConnectNamedPipe()实际成功,可以通过轮询或使用异步I/O来实现。(请参见我的答案。) - Harry Johnston

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接