【发布时间】:2012-11-22 18:07:00
【问题描述】:
我正在使用 IO 完成例程通过管道在不同机器上的两个进程之间进行通信。
有时,当调用 WriteFileEx 的完成例程时,完成例程参数 dwErrorCode 为 0(即无错误),GetOverlappedResult 返回 true(即无错误),但 dwNumberOfBytesTransfered 与调用 WriteFileEx 中的 nNumberOfBytesToWrite 不匹配。然而,我只在管道的客户端看到这个。
如果传输的字节数与请求传输的字节数不匹配,怎么能算是成功?
这是客户端对管道的句柄的创建方式:
mHPipe = CreateFile(pipeName, // pipe name
GENERIC_READ | // read and write access
GENERIC_WRITE,
0, // no sharing
NULL, // default security attributes
OPEN_EXISTING, // opens existing pipe
FILE_FLAG_OVERLAPPED | // overlapped
FILE_FLAG_WRITE_THROUGH, // write through mode
NULL); // no template file
// do some checking...
// The pipe connected; change to message-read mode.
DWORD dwMode = PIPE_READMODE_MESSAGE;
BOOL fSuccess = SetNamedPipeHandleState(mHPipe, // pipe handle
&dwMode, // new pipe mode
NULL, // don't set maximum bytes
NULL); // don't set maximum time
谁能明白为什么会发生这种情况?
谢谢
编辑:
相关的WriteFileEx代码如下:
void WINAPI CompletedWriteRoutine(DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered, LPOVERLAPPED lpOverLap)
{
BOOL fWrite = FALSE;
LPPIPEINST lpPipeInst = (LPPIPEINST)lpOverLap;
//
// ! 99.9% of the time, dwNumberOfBytesTransfered == lpPipeInst->cbDataSize
// but 0.1% of the time, they do not match
//
// Some stuff
// Copy next message to send
memcpy_s(lpPipeInst->chData, sizeof(lpPipeInst->chData), pMsg->msg, pMsg->size);
lpPipeInst->cbDataSize = pMsg->size;
// Some other stuff
fWrite = WriteFileEx(lpPipeInst->hPipeInst,
lpPipeInst->chData,
lpPipeInst->cbDataSize,
(LPOVERLAPPED) lpPipeInst,
(LPOVERLAPPED_COMPLETION_ROUTINE)CompletedWriteRoutine);
// Some other, other stuff
}
LPPIPEINST 声明为:
typedef struct
{
OVERLAPPED oOverlap; // must remain first item
HANDLE hPipeInst;
TCHAR chData[BUFSIZE];
DWORD cbDataSize;
} PIPEINST, *LPPIPEINST;
并且对 CompletedWriteRoutine 的初始调用被赋予了这样声明的 lpOverlap 参数:
PIPEINST pipeInstWrite = {0};
pipeInstWrite.hPipeInst = client.getPipeHandle();
pipeInstWrite.oOverlap.hEvent = hEvent[eventWriteComplete];
编辑:
在尝试按照 Harry 的建议重新初始化重叠结构后,我注意到了一些奇怪的东西。
我将memsetOVERLAPPED 结构在每个WriteFileEx 之前为零,大约1/5000 完成例程回调,cbWritten 参数和OVERLAPPED 结构的InternalHigh 成员现在设置为前一个的大小消息,而不是最近的消息。我在完成例程内的管道的客户端和服务器端都添加了一些日志记录到文件中,并且两端发送和接收的数据完全匹配(以及正确的预期数据)。然后揭示了在将数据写入文件所花费的时间里,OVERLAPPED 结构中的InternalHigh 成员现在已更改为现在反映我所期望的消息的大小(cbWritten 仍然是旧的消息大小) .我删除了文件日志记录,现在可以使用此代码重现类似发条的问题:
void WINAPI CompletedWriteRoutine(DWORD dwErr, DWORD cbWritten, LPOVERLAPPED lpOverLap)
{
LPPIPEINST lpPipeInst = (LPPIPEINST)lpOverLap;
// Completion routine says it wrote the amount of data from the previous callback
if (cbWritten != lpPipeInst->cbDataSize)
{
// Roughly 1 in 5000 callbacks ends up in here
OVERLAPPED ovl1 = lpPipeInst->oOverlap; // Contains size of previous message, i.e. cbWritten
Sleep(100);
OVERLAPPED ovl2 = lpPipeInst->oOverlap; // Contains size of most recent message, i.e lpPipeInst->cbDataSize
}
...
}
似乎有时,完成例程在OVERLAPPED 结构之前被调用并且完成例程输入参数被更新。我正在使用 MsgWaitForMultipleObjectsEx(eventLast, hEvent, INFINITE, QS_POSTMESSAGE, MWMO_ALERTABLE); 在 Windows 7 64 位上调用完成例程。
"完成例程调用后系统不使用OVERLAPPED结构,因此完成例程可以释放重叠结构使用的内存。"
...很明显,这段代码可以重现的事情永远不会发生?
这是一个 WINAPI 错误吗?
【问题讨论】:
-
如果是普通网络IO,写的时候需要循环直到写完所有数据,注意刚刚写了多少。您是否正在写入大量数据并且可能会填满管道,以致于读取器无法以足够快的速度将其读取以使写入器将其写入一个块?
-
我在写入少量数据(例如 7 个字节)时看到了这种情况,而完成例程说它写入了 11 个字节(管道缓冲区为 4096 个字节)。管道正在使用直写模式,它应该一次性发送来自 WriteFileEx 的所有数据,并且完成例程应该注意不需要进行任何循环 - 因为它是发送数据时的回调。
-
这个问题与 Windows 和 Windows API 有关,请确保下次标记它。另外,这是 C 还是 C++ 问题?
-
@HarryJohnston:WriteFileEx() 根本不使用 OVERLAPPED::hEvent 字段,因此它甚至不需要指向有效事件。在这种情况下,它对于携带用户定义的数据很有用。
-
Remy 关于 WriteFileEx 不使用事件句柄是正确的,我的错。但我仍然认为您应该在每次调用之前重新初始化 OVERLAPPED 结构。这是一个记录要求,遗憾的是,MS 示例代码并不总是完全正确的。请参阅msdn.microsoft.com/en-us/library/windows/desktop/…“在函数调用中使用该结构之前,该结构的任何未使用成员都应始终初始化为零。”
标签: c++ c windows winapi named-pipes