客户端断开连接后如何使命名管道不忙?
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。 (见我的回答。)以上是关于客户端断开连接后如何使命名管道不忙?的主要内容,如果未能解决你的问题,请参考以下文章