io完成端口发送数据问题!
我做了个服务端使用了IO完成端口 用了WSARecv等函数
写了个测试程序 循环20万次向服务的发送数据 数据大小为4096字节
服务端也是每次接收4096
客户端同时在本机运行测试 但是测试中时服务端有时候收到数据内容为空啊
我明明是向服务端发送了20万次的数据 但是有时候数据为空的时候服务端打印出的接收次数为20多万次
我搞不明白了
[最优解释]
发送4096很容易,但要接收4096,就不一定了。
我遇到一些人,包括一些很有经验的人,认为recv时,指定了一个长度,这个长度就是接收数据的长度,其实这个长度指的是缓存的长度,至于接收多少,要看数据有多少,一般都会小于这个长度,但不会大。
我不知道你有没有也犯这种错误,没有代码,我只能猜测。
当然,要让recv非要读取多少字节,也行,那就是指定MSG_WAITALL参数,但很少用,这个参数意义并不大。特别是包长不定的情况下。
[其他解释]
你每次Send的返回值都检测了?
把返回值拿来看看,异常用GetLastError打印看看
[其他解释]
代码如下
服务端的
while(TRUE)
{
if ((Accept = WSAAccept(Listen, NULL, NULL, NULL, 0)) == SOCKET_ERROR)
{
printf("WSAAccept() failed with error %d\n", WSAGetLastError());
return;
}
if ((PerHandleData = (LPPER_HANDLE_DATA) GlobalAlloc(GPTR,
sizeof(PER_HANDLE_DATA))) == NULL)
{
printf("GlobalAlloc() failed with error %d\n", GetLastError());
return;
}
printf("Socket number %d connected\n", Accept);
PerHandleData->Socket = Accept;
if (CreateIoCompletionPort((HANDLE) Accept, CompletionPort, (DWORD) PerHandleData,
0) == NULL)
{
printf("CreateIoCompletionPort failed with error %d\n", GetLastError());
return;
}
if ((PerIoData = (LPPER_IO_OPERATION_DATA) GlobalAlloc(GPTR, sizeof(PER_IO_OPERATION_DATA))) == NULL)
{
printf("GlobalAlloc() failed with error %d\n", GetLastError());
return;
}
Flags = 0;
WSABUF wBuf;
wBuf.buf = PerIoData->buf;
wBuf.len = DATA_BUFSIZE;
if (WSARecv(Accept, &wBuf, 1, &RecvBytes, &Flags,
&(PerIoData->Overlapped), NULL) == SOCKET_ERROR)
{
if (WSAGetLastError() != ERROR_IO_PENDING)
{
printf("WSARecv() failed with error %d\n", WSAGetLastError());
return;
}
}
Flags = WSAGetLastError();
DWORD WINAPI ServerWorkerThread(LPVOID CompletionPortID)
{
HANDLE CompletionPort = (HANDLE) CompletionPortID;
DWORD BytesTransferred;
LPOVERLAPPED Overlapped;
LPPER_HANDLE_DATA PerHandleData;
LPPER_IO_OPERATION_DATA PerIoData;
DWORD SendBytes, RecvBytes;
DWORD Flags;
printf("ServerWorkerThread %d start \n", GetCurrentThreadId());
while(TRUE)
{
if (GetQueuedCompletionStatus(CompletionPort, &BytesTransferred,
(LPDWORD)&PerHandleData, (LPOVERLAPPED *) &PerIoData, INFINITE) == 0)
{
MessageBox(NULL, "GetQueuedCompletionStatus error", "", MB_OK);
printf("GetQueuedCompletionStatus failed with error %d\n", GetLastError());
if (closesocket(PerHandleData->Socket) == SOCKET_ERROR)
{
printf("closesocket() failed with error %d\n", WSAGetLastError());
continue;
}
GlobalFree(PerHandleData);
GlobalFree(PerIoData);
continue;
}
if (BytesTransferred == 0)
{
MessageBox(NULL, "BytesTransferred 0", "", MB_OK);
if (closesocket(PerHandleData->Socket) == SOCKET_ERROR)
continue;
GlobalFree(PerHandleData);
GlobalFree(PerIoData);
continue;
}
RecvCount++;
if (strcmp(PerIoData->buf, "") == 0)
MessageBox(NULL, "buf 0", "", MB_OK);
printf("byte: %d count: %d %s\n", BytesTransferred, RecvCount, PerIoData->buf);
GlobalFree(PerIoData);
LPPER_IO_OPERATION_DATA tPerIoData;
if ((tPerIoData = (LPPER_IO_OPERATION_DATA) GlobalAlloc(GPTR, sizeof(PER_IO_OPERATION_DATA))) == NULL)
{
printf("GlobalAlloc() failed with error %d\n", GetLastError());
continue;
}
DWORD Flags = 0;
DWORD RecvBytes;
WSABUF wBuf;
wBuf.buf = tPerIoData->buf;
wBuf.len = DATA_BUFSIZE;
if (WSARecv(PerHandleData->Socket, &wBuf, 1, &RecvBytes, &Flags,
&(tPerIoData->Overlapped), NULL) == SOCKET_ERROR)
{
if (WSAGetLastError() != ERROR_IO_PENDING)
{
printf("WSARecv() failed with error %d\n", WSAGetLastError());
return 0;
}
}
}
}
客户端
char buffer[4096]="0hello! It is me";
int i = 0;
while (i< 2000000)
{
send(s,buffer,sizeof(buffer),0);
i++;
}
有时候执行了这步MessageBox(NULL, "buf 0", "", MB_OK);弹出对话框了 我郁闷
[其他解释]
你的情况跟我猜的完全一样,我就说一句,其它的你去领会。
你自以为是的认为BytesTransferred就等于4096了。
我说过,一次接收,并不一定等于4096,如果客户端发了4096个字符,服务端分两次接收,第一次接收假设是2048字节,第二次接收2048字节,你觉得第二次接收会不会执行到:
MessageBox(NULL, "buf 0", "", MB_OK);
[其他解释]
另外,你知道char buffer[4096]="0hello! It is me";这句的语义吗?这是c语言基础了,正是它和服务端的分包问题,造成了执行到:
MessageBox(NULL, "buf 0", "", MB_OK);
[其他解释]
既然发生了分包,那你服务端收到多于2000000,就是自然而然的了。
[其他解释]
楼上说的就是了
[其他解释]
恩 谢谢!
第二次接收到2048字节 但是BytesTransferred等于4096
那我到底要怎么判断当前接收到多少字节啊?
[其他解释]
BytesTransferred 为4096 就是接收到了4096字节, 这不会错的.
看了下你的代码 PER_IO_OPERATION_DATA 结构体没有贴出来, 所以不知道
PER_IO_OPERATION_DATA中 buf是不是数组以及buf的大小.
每次调用 WSARecv 之前,先把 tPerIoData 字段 全部设为0.
tPerIoData 里面的值是会影响IOCP行为的.
[其他解释]
我的意思是,你每次认为BytesTransferred行于4096,才会写出你上面的代码。如果你不做这个假设,那么你就不会写出这样的代码,也不会为出现所谓的怪异行为而感觉奇怪了。
真实情况是,每次的BytesTransferred都不一定等于4096,可能小,也可能大(如果你提供的buffer大于4096的话),也就是出现了分包与粘包现象。
这些现象一但发生,非常有可能让程序执行到
MessageBox(NULL, "buf 0", "", MB_OK);
你这句代码strcmp(PerIoData->buf, "") == 0的意思,是判断字符串是不是为空。而你发送的数据是char buffer[4096]="0hello! It is me";除了前面10来个字符,后面的字符都是\0,所以只要从me后面的任意字符分包,都会执行到MessageBox(NULL, "buf 0", "", MB_OK);
[其他解释]
不要想当然,判断一下你接收的数据量就可以了!
[其他解释]
学习一下哈
[其他解释]
我今天改了下 判断了下如果数据包不全 就继续接收余下的数据!
但是又出现个问题 包是接全了 但是有的包数据全是错误的啊!
客户端循环30万次发送数据 每次4096字节 最终经过服务端的处理后是完整接收到了30万次的数据包
但是这30万个数据包中有大半数据包内容是错误的
我想问下如果客户端发送4096字节时分2次发送给服务端 第一次发送3000字节 第二次发送余下的1096字节
这2步是紧跟着的吗? 我在服务端接收到第一次的3000字节后 只要接下来有1096字节收到时 就认为这是此数据包余下的1096字节吗?
应该怎么做才能完整接收数据包啊 谢谢
[其他解释]
服务端
while(TRUE)
{
if (GetQueuedCompletionStatus(CompletionPort, &BytesTransferred,
(LPDWORD)&PerHandleData, (LPOVERLAPPED *) &PerIoData, INFINITE) == 0)
{
MessageBox(NULL, "GetQueuedCompletionStatus error", "", MB_OK);
printf("GetQueuedCompletionStatus failed with error %d\n", GetLastError());
if (closesocket(PerHandleData->Socket) == SOCKET_ERROR)
{
printf("closesocket() failed with error %d\n", WSAGetLastError());
continue;
}
GlobalFree(PerHandleData);
free(PerIoData);
continue;
}
if (BytesTransferred == 0)
{
printf("接收=%d 完整包=%d\n",nCount,nComplete);
if (closesocket(PerHandleData->Socket) == SOCKET_ERROR)
continue;
GlobalFree(PerHandleData);
free(PerIoData);
continue;
}
LPPER_IO_OPERATION_DATA tPerIoData;
tPerIoData = (LPPER_IO_OPERATION_DATA)malloc(sizeof(PER_IO_OPERATION_DATA));
if (tPerIoData == NULL)
{
printf("GlobalAlloc() failed with error %d\n", GetLastError());
continue;
}
ZeroMemory(tPerIoData, sizeof(PER_IO_OPERATION_DATA));
WSABUF wBuf;
if ( PerIoData->Length + BytesTransferred == DATA_BUFSIZE)
{
if (PerIoData->buf[0] == 'a' &&
PerIoData->buf[2000] == 'B' &&
PerIoData->buf[4095] == 'C')
{
nComplete++;
}
wBuf.buf = (char*)&(tPerIoData->buf);
wBuf.len = DATA_BUFSIZE;
nCount ++;
}
else
{
tPerIoData->Length += BytesTransferred;
strncpy(tPerIoData->buf,
PerIoData->buf,
tPerIoData->Length);
wBuf.buf = (char*)&(tPerIoData->buf) + tPerIoData->Length;
wBuf.len = DATA_BUFSIZE - tPerIoData->Length;
}
free(PerIoData);
DWORD Flags = 0;
DWORD RecvBytes;
if (WSARecv(PerHandleData->Socket, &wBuf, 1, &RecvBytes, &Flags,
&(tPerIoData->Overlapped), NULL) == SOCKET_ERROR)
{
if (WSAGetLastError() != ERROR_IO_PENDING)
{
printf("WSARecv() failed with error %d\n", WSAGetLastError());
return 0;
}
}
}
//########################################################
客户端
char buffer[4096] ;
ZeroMemory(buffer, 4096);
buffer[0] = 'a';
buffer[2000] = 'B';
buffer[4095] = 'C';
int i = 0;
while (i< 2000000)
{
send(s,buffer,4096,0);
i++;
}
#define DATA_BUFSIZE 4096
typedef struct
{
OVERLAPPED Overlapped;
CHAR buf[DATA_BUFSIZE];
ULONG Length;
} PER_IO_OPERATION_DATA, * LPPER_IO_OPERATION_DATA;
一次运行下来结果如下
接收=1998248 完整包1066162
我搞不清怎么接收完整的数据包了...............
[其他解释]
你的问题与网络编程无关了:
else
{
tPerIoData->Length = PerIoData->Length + BytesTransferred;
strncpy(tPerIoData->buf,
PerIoData->buf,
tPerIoData->Length);
wBuf.buf = (char*)&(tPerIoData->buf) + tPerIoData->Length;
wBuf.len = DATA_BUFSIZE - tPerIoData->Length;
}
你的代码确实是太难看了,通篇的内存分配、释放、内存拷贝,这些都是不需要的,我直说希望你不要生气,建议还是从基础开始慢慢来。
[其他解释]
恩 我下次注意了
但是重用一个PerIoData也会出错啊
这是哪里出错了呢?还是逻辑上有问题啊
麻烦告知下 谢谢!
[其他解释]
我就纳闷了 我重写了代码 没重复分配释放内存了 现在测试ok了
另外我上面还有个疑问想麻烦你告知下 谢谢!
我想问下如果客户端发送4096字节时分2次发送给服务端 第一次发送3000字节 第二次发送余下的1096字节
这2步是紧跟着的吗?
我在服务端接收到第一次的3000字节后 只要接下来有1096字节收到时 就认为这是此数据包余下的1096字节吗?
[其他解释]
代码难看并不一定就是错的,两回事。
所有的都别动,照我15楼改了,还出错?
另外我想问一下,你确定你的代码可以接收到数据,并且有一部分正确?
如果是,那我就集中看你的逻辑,并且默认你的socket api调用都是正确的。因为好些代码,比如你自己的一些结构,你没有贴出来,我只能假设它是正确的。
[其他解释]
我也担心这种情况啊 那余下的1096字节什么时候发过来我还不知道呢!
[其他解释]
你收到俩3000 你咋办?
[其他解释]
接收端分多少次接收,与发送端发了多少次(调用多少次send)没有关系。你可以理解为,一条河,上面用碰盆子往河里面倒水,下面用盆子从河里舀水。
拿你的例子来说,你客户端每次都发4096,服务端第一次接收3000字节(这叫分包),第二次不一定接收1096字节,它有可能收到任意数量的数据,比如1100字节,即收了第一个包剩余的1096字节之后,还收了第二个包(在客户端看来的第二个包)的前4个字节(这叫粘包),所以你应该把这1100个字节的将1096个字节,放到第一次收的3000字节的后面,这就就收到一个完整的包了,剩下的4字节保存起来,接着收,把收到的数据放在这4字节之后,直接另一个完整的包接收完毕(4096字节),如果我这样说你还不能懂,那你还是慢慢来吧,可能你还没有准备好网络编程。
总之,数据是顺序到达的,但每次收到多少字节,不做任何假设,以BytesTransferred为准。
[其他解释]
问题不是简单的问题,顶一个吧,最近太忙,祝福楼主
[其他解释]
哦 明白了 谢谢指点!