BUG: 错误使用IOCP导致在多PCI-E网卡无法接收到数据
前言
该BUG是出现在实现GigE-Version CTI Discover模块中出现的,为了可以异步获取连接在上位机不同网卡的多台设备信息,我采用的IOCP方式实现的Discover模块。
1、IOCP简易调用图
如上图所示,我们会为在多网卡情况下我们会为每个网卡都创建一个Socket对象,并将该Socket绑定到IOCP上和接受操作绑定到IOCP上。
之后当有Client向某个网卡的Socket发送数据时,GetQueuedCompletionStatus便会感知到进行后续操作。
2、问题现象
在只有主板网卡时,程序可以正常接收到连接在主板网卡设备的信息;但是当上位机插入多个PCI-E网卡并将设备(GigE设备)插入到这些PCI-E网卡上时,Discover模块就无法检测到设备的存在了,并且GetQueuedCompletionStatus一直是阻塞状态(说明IOCP根本就没有检测到有ACK包进来)。
这里开始怀疑是否是设备没有收到Discover-cmd或者上位机没有收到设备返回的ACK;使用Wireshark监听发现设备接受到了CMD并发送了ACK,上位机已经接收到了ACK,就是IOCP没有反应。
3、问题代码及问题分析
for(网卡 : 多网卡){
// socket socket绑定...
PostIOCPRecvs(discoverSocket, MAX_DISCOVER_BUFFER_SIZE, DEFAULT_PENDING_NUMS); // 问题点2
// ...
}
void GevDiscoverClient::PostIOCPRecvs(SOCKET discoverSock, const size_t recvBufferSize,
const size_t pendingRecvs) {
while (totalBuffersAllocated < pendingRecvs) // 问题点2
{
DWORD receiveBuffersAllocated = 0;
char *pBuffer = AllocateBufferSpace(recvBufferSize, pendingRecvs, receiveBuffersAllocated);
totalBuffersAllocated += receiveBuffersAllocated;
DWORD offset = 0;
const DWORD recvFlags = 0;
EXTENDED_OVERLAPPED *pBufs = new EXTENDED_OVERLAPPED[receiveBuffersAllocated];
DWORD bytesRecvd = 0;
DWORD flags = 0;
for (DWORD i = 0; i < receiveBuffersAllocated; ++i)
{
EXTENDED_OVERLAPPED *pOverlapped = pBufs + i;
ZeroMemory(pOverlapped, sizeof(EXTENDED_OVERLAPPED));
pOverlapped->buf.buf = pBuffer + offset;
pOverlapped->buf.len = recvBufferSize;
offset += recvBufferSize;
if (SOCKET_ERROR == ::WSARecvFrom(discoverSock, &(pOverlapped->buf), 1, &bytesRecvd, &flags, NULL, NULL, pOverlapped, 0))
{
const DWORD lastError = ::GetLastError();
if (lastError != ERROR_IO_PENDING)
{
ErrorExit("WSARecv", lastError);
}
}
}
if (totalBuffersAllocated != pendingRecvs)
{
std::cout << pendingRecvs << " receives pending" << std::endl;
}
};
}
如上代码有两处问题点,一个是调用PostIOCPRecvs地方,一个是PostIOCPRecvs实现的地方;
问题点1在调用的地方多网卡情况下pendingRecvs的值一直没有改变,归根到底还是问题点2的原因;
问题点2中在第一次调用后,totalBuffersAllocated == pendingRecvs了,后续的socket就不会再进来了,导致后续的overlapped和接受操作不会和IOCP对象绑定;
没有绑定接受操作到IOCP,即使有数据进到该网卡IOCP也不会有反应,和我们的现象一致。
4、解决方案
我们在使用IOCP时,一定要确保一下几点:
- 确保socket对象与IOCP对象绑定
- 确保socket读写操作与IOCP对象绑定(这里其实是两个一个是读,一个是写)
上述代码只需要微微改动就可以解决
void GevDiscoverClient::PostIOCPRecvs(SOCKET discoverSock, const size_t recvBufferSize,
const size_t pendingRecvs) {
//while (totalBuffersAllocated < pendingRecvs) // 问题点2
do
{
// ... ...
// ... ...
}while(0);// 这里判断可以详细点,对pendingRecvs进行进一步判断
}