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

发布于 2024-07-29 20:59:50 字数 646 浏览 3 评论 0原文

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

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

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

I use a named pipe and I want to reuse the same pipe on the server to allow connecting another client once the original client has disconnected. What I do is:

  • server creates a pipe using CreateNamedPipe
  • server writes data using WriteFile, and retries doing so as long as error ERROR_PIPE_LISTENING is returned (which is before any client is connected)
  • clients connects using CreateFile
  • client reads data
  • client close pipe handle using CloseHandle
  • at this point server gets error ERROR_NO_DATA when it attemps to write more data
  • server disconnects the pipe using DisconnectNamedPipe, which I hoped should make it free again
  • server tries writing data, gets error ERROR_PIPE_NOT_CONNECTED, it retries doing so until there is no error
  • however, when new client connects, and attempts CreateFile on the pipe, it gets ERROR_PIPE_BUSY

Hence, my question is: what other steps I need to do to disconnect client from the pipe properly so that a new client can connect?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

2024-08-05 20:59:50

问题是您遗漏了 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();
        }
    }
}

(此代码已从原始代码中进行了编辑,以删除无关的逻辑。我还没有尝试编译编辑后的版本,因此可能存在一些小问题。)

The problem is that you've left out ConnectNamedPipe(), which should be always be called after CreateNamedPipe() or DisconnectNamedPipe() but before attempting any I/O.

If you don't want to block while waiting for a client to connect, you can create the pipe in asynchronous I/O mode, in which case the call to ConnectNamedPipe() requires an event object which will be set when a client connects. Alternatively, you can set PIPE_NOWAIT and call ConnectNamedPipe() periodically until it succeeds, but this is a legacy feature and its use is discouraged. (In most situations using an event object will also be significantly more efficient than polling.)

As you've discovered, Windows does allow you to get away without the call to ConnectNamedPipe() but since this behaviour is undocumented it should probably be avoided. Similarly, the fact that calling ConnectNamedPipe() without waiting for it to succeed resets the connection state of the pipe is undocumented and should not be depended upon.


As requested, here's some real-world code to demonstrate the use of the server end of a pipe. This code was taken from a GUI application, so it uses asynchronous I/O, but it should be noted that it only talks to one client at a time. (It could however be run in multiple threads with only minor modifications.)

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

(This code has been edited down from the original to remove extraneous logic. I haven't tried compiling the edited version, so there may be some minor problems.)

温柔少女心 2024-08-05 20:59:50

尝试各种调用,我发现以下工作正常:

作为对 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 会阻塞服务器线程无限等待客户端。

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

Experimenting with various calls, I have found following to work fine:

In reaction to ERROR_PIPE_NOT_CONNECTED, server should perform:

  // 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 makes the pipe connectable (not busy) again.

Note: pipe state is changed temporarily to PIPE_NOWAIT, as otherwise ConnectNamedPipe blocks the server thread waiting for the client infinitely.

Other solution could probably be to close the handle completely on the server side and open it again.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文