【问题标题】:How to make a named pipe not busy after client has disconnected?客户端断开连接后如何使命名管道不忙?
【发布时间】:2009-07-28 10:04:20
【问题描述】:

我使用命名管道,并且我想在服务器上重复使用相同的管道,以便在原始客户端断开连接后连接另一个客户端。我要做的是:

  • 服务器使用CreateNamedPipe创建管道
  • 服务器使用WriteFile写入数据,只要返回错误ERROR_PIPE_LISTENING(在任何客户端连接之前)就会重试。
  • 客户端使用CreateFile连接
  • 客户端读取数据
  • 客户端使用CloseHandle关闭管道句柄
  • 此时服务器尝试写入更多数据时出现错误ERROR_NO_DATA
  • 服务器使用DisconnectNamedPipe断开管道,我希望它可以再次免费
  • 服务器尝试写入数据,得到错误ERROR_PIPE_NOT_CONNECTED,它会重试直到没有错误
  • 但是,当新客户端连接并在管道上尝试CreateFile 时,它会得到ERROR_PIPE_BUSY

因此,我的问题是:我还需要执行哪些其他步骤才能正确断开客户端与管道的连接,以便新客户端可以连接?

【问题讨论】:

    标签: winapi named-pipes


    【解决方案1】:

    问题在于您遗漏了 ConnectNamedPipe(),它应该始终在 CreateNamedPipe() 或 DisconnectNamedPipe() 之后调用,但 尝试任何 I/哦。

    如果您不想在等待客户端连接时阻塞,您可以在异步 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会阻塞服务器线程无限等待客户端。

      其他解决方案可能是在服务器端完全关闭句柄并再次打开它。

      【讨论】:

      • 此行为未记录在案。最好等到 ConnectNamedPipe() 实际成功,通过轮询或使用异步 I/O。 (见我的回答。)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-07-17
      • 1970-01-01
      相关资源
      最近更新 更多