命名管道服务器,如何中断或超时等待客户端连接和传入数据

Named Pipes server, how to interrupt or timeout the wait for client connection and for incoming data

我正在为 Windows 编写一个简单的命名管道服务器,调用 Windows API(在 Java 中使用 JNA,但这不相关)。

我想弄清楚如何避免服务器永远卡住,等待客户端连接或数据来自客户端。

服务器代码执行以下操作:

1) 它通过调用 CreateNamedPipe 创建管道,在 dwPipeMode 参数中使用 PIPE_WAIT

2) 它调用 ConnectNamedPipe 直到客户端连接后才会 return。

3) 它进入一个循环,在这个循环中它通过调用 ReadFile 重复从客户端读取消息,直到数据被读取才 return,并且对于每个接收到的消息它发送一条消息通过调用 WriteFile.

返回给客户端作为响应

4) 经过多次此类对话后,客户端和服务器将断开与管道的连接。

我只是希望能够在第 2 步等待 ConnectNamedPipe 和第 3 步等待 ReadFile 时设置超时,但我看不到在哪里设置超时。 CreateNamedPipe 函数中有 nDefaultTimeOut 参数,但听起来并不真正用于此目的; API 文档说:

默认超时值,以毫秒为单位,如果WaitNamedPipe函数指定NMPWAIT_USE_DEFAULT_WAIT

所以 CreateNamedPipe 中的 nDefaultTimeOut arg 听起来像是 将连接到管道的客户端 用于其操作的默认超时如果他们调用 WaitNamedPipe 函数。事实上,在我的测试中,0 或 1000 的值没有区别,对 ConnectNamedPipe 的调用永远不会 returns(除非客户端连接)。我正在寻找的是服务器超时,而不是在调用 ConnectNamedPipeReadFile.

正如 CreateNamedPipe 的文档,对于带有 PIPE_WAITdwPipeMode 参数,Blocking mode is enabled. When the pipe handle is specified in the ReadFile, WriteFile, or ConnectNamedPipe function, the operations are not completed until there is data to read, all data is written, or a client is connected. Use of this mode can mean waiting indefinitely in some situations for a client process to perform an action.

所以也许实现这种超时的方法是以非阻塞模式创建管道(使用 PIPE_NOWAIT 而不是 PIPE_WAIT)以便调用 ReadFileWriteFileConnectNamedPipe return ,然后以某种方式在循环中监视自己的事件(客户端连接或接收到数据),并在循环中检查自己是否超时或发生另一个中断事件(就像用户单击取消按钮一样)?

ADDED:对于 ReadFile 调用,我可能可以立即使用 PeekNamedPipe 来检查 return如果有数据要读,才调用ReadFile。我会试试的。但是对于 ConnectNamedPipe.

的调用我仍然有同样的问题

ADDED:正如我所怀疑的那样,答案也得到了证实,作为管道的新手,我从某种角度来看它们,超时的需要似乎大于确实如此。

F.ex。想要超时调用 ReadFile 背后的原因是,如果我(服务器)在其中从客户端读取数据并且客户端突然关闭,有时我可能最终会卡在 ReadFile 中。但现在我知道,如果 ReadFile 正在从管道读取并且客户端关闭,则 ReadFilealways 出错,因此执行不会被困在里面。

我建议您设置 FILE_FLAG_OVERLAPPED 并使用事件 check/wait 完成。

虽然这最初是为异步 IO 而设计的,但您可以改为将事件计时到您预定义的生存时间。

如果你想取消I/O操作,你可以使用CancelIo()函数。如果您只是想做一些工作然后继续等待,您也可以这样做 - 等待超时不会自动取消 I/O,因此您不需要再次调用 ConnectNamedPipe。

您也可以按照您自己的建议设置 PIPE_NOWAIT 并轮询连接直到成功,在这个用例中,任何一种方法都应该产生相同的结果。但请注意,这是遗留功能,Microsoft 不鼓励使用此选项。

在 GUI 应用程序中演示管道服务器端异步使用的一些实际代码:

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] = '[=10=]';

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

(这​​段代码在原来的基础上进行了删减,去掉了多余的逻辑,修改后的版本我没有试过编译,所以可能会有一些小问题。还要注意全局变量的使用,在我的情况是因为应用程序非常小,但应该通常 避免。)

使用 MsgWaitForMultipleObjectsEx() 允许在您等待 I/O 完成时处理 window 消息。如果你也在等待其他事情发生,你可以传递一个句柄数组而不是一个句柄 - 例如,如果你想监视一个子进程并在它退出时做一些事情,你可以传递一个包含io_event 和进程句柄。如果您只是 不得不 定期做一些其他工作,您可以设置等待超时,或使用 window 计时器。