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

San*_*ors 6 windows winapi timeout named-pipes

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

我试图弄清楚如何避免服务器永远停留在等待客户端连接或来自客户端的数据。

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

1) 它通过在参数中调用CreateNamedPipe,创建管道。PIPE_WAITdwPipeMode

2)它调用ConnectNamedPipe直到客户端连接才返回。

3)它进入一个循环,它通过调用从客户端重复读取消息,ReadFile直到读取数据才返回,并且对于每个接收到的消息,它通过调用 将消息发送回客户端作为响应WriteFile

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

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

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

因此nDefaultTimeOutarg inCreateNamedPipe听起来像是连接到管道的客户端将用于其操作的默认超时,并且仅当它们调用该WaitNamedPipe函数时。事实上,在我的测试中,0 或 1000 的值没有区别,调用ConnectNamedPipe从不返回(除非客户端连接)。我正在寻找的是在服务器超时,而不是,在调用ConnectNamedPipeReadFile

正如 的 文档CreateNamedPipe,对于 的dwPipeMode论点PIPE_WAIT说,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立即返回,然后以某种方式监视自己循环中的事件(客户端连接或接收数据),并在循环中检查自己是否超时或发生了另一个中断事件(如用户单击取消按钮)?

添加:看起来ReadFile我可以使用的调用会PeekNamedPipe立即返回,以检查是否有数据要读取,然后才调用ReadFile. 我会试试的。但是对于调用ConnectNamedPipe.

补充:正如我怀疑和答案所证实的那样,作为管道的新手,我从某种倾斜的角度看它们,从这个角度来看,对超时的需求似乎比实际更大。

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

小智 5

我建议您设置FILE_FLAG_OVERLAPPED并使用一个事件来检查/等待完成。

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

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

您也可以按照您自己的建议设置PIPE_NOWAIT并轮询连接直到成功,无论哪种方式都应该在此用例中带来相同的结果。但请注意,这是旧功能,Microsoft 不鼓励使用此选项。


Har*_*ton 5

一些实际代码演示了 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] = '\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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

(此代码已从原始代码中进行了编辑,以删除无关的逻辑。我还没有尝试编译编辑后的版本,因此可能存在一些小问题。另请注意全局变量的使用,这在我的情况下是可以的,因为应用程序非常小,但通常应该避免。)

使用 MsgWaitForMultipleObjectsEx() 允许在等待 I/O 完成时处理窗口消息。如果您还在等待其他事情发生,则可以向其传递一个句柄数组,而不仅仅是一个句柄 - 例如,如果您想监视子进程并在其退出时执行某些操作,则可以传递一个包含以下内容的数组:两者io_event和进程句柄。如果您只需要定期执行其他工作,则可以设置等待超时,或使用窗口计时器。