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

Posted

技术标签:

【中文标题】客户端断开连接后如何使命名管道不忙?【英文标题】: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

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

【问题讨论】:

【参考方案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。 (见我的回答。)

以上是关于客户端断开连接后如何使命名管道不忙?的主要内容,如果未能解决你的问题,请参考以下文章

检测命名管道与 I/O 完成断开连接

命名管道:服务器端如何知道客户端已断开连接?

在断开连接之前等待读取命名管道

当客户端除了快速连接-断开之外啥都不做时,命名管道会失败

客户端断开连接时套接字服务器崩溃

如何在WebSocket的服务器侧检测客户端的断开连接