发送大数据量,是否要用CSocket?我用CAsyncSocket,当Client数据量大时,会循环Send,导致OnSend不停的触发,并且Server的OnReceive不停触发,接收不到数据。

ttnewday 2009-02-14 10:31:04
发送大数据量,是否要用CSocket?我用CAsyncSocket,当Client数据量大时,会循环Send,导致OnSend不停的触发,并且Server的OnReceive不停触发,接收不到数据。
...全文
335 12 打赏 收藏 转发到动态 举报
写回复
用AI写文章
12 条回复
切换为时间正序
请发表友善的回复…
发表回复
ttnewday 2009-02-15
  • 打赏
  • 举报
回复
CAnsiMemFile 改为 CMemFile,编译才能通过的。
ttnewday 2009-02-15
  • 打赏
  • 举报
回复
怎样改都改不成正确的,Receive老是收不完正确的内容。帮忙看看哪里应该要修改。
m_send是stl的string,从其他地方对这个变量赋值;Server的接收内容放在C:\test.txt,需要事先在C盘建一个test.txt文件。
Client端:
OnSend:
void CCliOprSocket::OnSend(int nErrorCode)
{
CAnsiMemFile file;
file.Write(m_send.c_str(), m_send.length()+1);
file.SeekToBegin();

int len = 0;
BYTE* pByte = NULL;

int sendLength = file.GetLength();

int dataLength, cbLeftToSend;
BYTE* sendData = NULL;
dataLength = sendLength;

dataLength = htonl(dataLength);
cbLeftToSend = sizeof(dataLength);

/*发送内容长度*/
do
{
int cbBytesSent;
BYTE* bp = (BYTE*)(&dataLength) + sizeof(dataLength) - cbLeftToSend;
cbBytesSent = Send(bp, cbLeftToSend);
if(cbBytesSent == SOCKET_ERROR)
{
int iErr = ::GetLastError();
TRACE("客户端发送内容长度时出现sock错误。\n"
"已发送字节数:%d\n"
"上次错误:%d\n", cbBytesSent, iErr);

goto PreReturnCleanup;
}

cbLeftToSend -= cbBytesSent;
}
while(cbLeftToSend > 0);

sendData = new BYTE[SEND_BUFFER_SIZE];
cbLeftToSend = sendLength;

/*发送内容*/
do
{
int sendThisTime, doneSoFar, buffOffset;
sendThisTime = file.Read(sendData, SEND_BUFFER_SIZE);
buffOffset = 0;

do
{
doneSoFar = Send(sendData + buffOffset, sendThisTime);

if(doneSoFar == SOCKET_ERROR)
{
int iErr = ::GetLastError();
TRACE("客户端发送内容时出现sock错误。\n"
"已发送字节数:%d\n"
"上次错误:%d\n", doneSoFar, iErr);

goto PreReturnCleanup;
}

buffOffset += doneSoFar;
sendThisTime -= doneSoFar;
cbLeftToSend -= doneSoFar;
m_send.erase(0, doneSoFar);
}
while(sendThisTime > 0);
}
while(cbLeftToSend > 0);

PreReturnCleanup:
delete[] sendData;
file.Detach();
file.Close();

CAsyncSocket::OnSend(nErrorCode);
}

Server端:
OnReceive:

void CSrvOprSocket::OnReceive(int nErrorCode)
{
CAnsiMemFile file;
int len = 0;
BYTE* pByte = NULL;

CStdioFile f;
f.Open("C:\\test.txt", CStdioFile::modeReadWrite);
f.SeekToEnd();

int dataLength, cbBytesRet, cbLeftToReceive;
BYTE* recdData = NULL;
/*获取内容长度*/
cbLeftToReceive = sizeof(dataLength);
do
{
BYTE* bp = (BYTE*)(&dataLength) + sizeof(dataLength) - cbLeftToReceive;
cbBytesRet = Receive(bp, cbLeftToReceive);
if(cbBytesRet == SOCKET_ERROR || cbBytesRet == 0)
{
int iErr = ::GetLastError();
TRACE("服务端接收内容长度时出现socket错误。\n"
"\t接收字节数(0代表连接已关闭):%d\n"
"\t上次错误:%d\n", cbBytesRet, iErr);

goto PreReturnCleanup;
}
cbLeftToReceive -= cbBytesRet;
}
while(cbLeftToReceive > 0);


/*获取内容*/
dataLength = ntohl(dataLength);
recdData = new BYTE[RECV_BUFFER_SIZE];
cbLeftToReceive = dataLength;

do
{
int iGet, iRecd;
iGet = cbLeftToReceive < RECV_BUFFER_SIZE ? cbLeftToReceive : RECV_BUFFER_SIZE;
iRecd = Receive(recdData, iGet);

if(iRecd == SOCKET_ERROR || iRecd == 0)
{
int iErr = ::GetLastError();
TRACE("服务端接收内容时出现socket错误。\n"
"\t接收字节数(0代表连接已关闭):%d\n"
"\t上次错误:%d\n", cbBytesRet, iErr);

goto PreReturnCleanup;
}

file.Write(recdData, iRecd);
}
while(cbLeftToReceive > 0);


PreReturnCleanup:
len = file.GetLength();
//AfxMessageBox(CString(file.GetPtr()));
if(len>0)
{
pByte = file.Detach();
f.Write(pByte, len);
}
len = 0;
f.Close();
delete[] pByte;
delete[] recdData;
file.Close();
AsyncSelect(FD_READ);
CAsyncSocket::OnReceive(nErrorCode);
}
arong1234 2009-02-14
  • 打赏
  • 举报
回复
你的设计思想要完全改变才行,你不能指望在一次OnSend调用中把file内容完全发送出去。你需要一个内部数据结构保存你要发送的数据,在不同的OnSend调用中发送他

基本思想是:
1. 首先要建立一个队列,保存你需要发送的数据
2. 如果Socket当前处于阻塞状态,直接退出发送,等待下次OnSend
3. 循环发送队列中的消息,每次完全发送成功,则移出队列,如果只是部分成功,则记录这次发送成功到第几个字节。这样循环发送直到你遇到阻塞错误,一旦遇到WSAEWOULDBLOCK,立刻标记socket为阻塞状态,并退出OnSend,如果遇到其他错误,应该立刻关闭socket,重新建立socket,从头发送
第三步中OnSend会不停重复,直到你完全发送完成

至于每次发送的报文,由于报文必然会被网络分包,你必须在发送的开始告诉对方这个报文到底有多大,然后由服务端接收时根据长度信息,通过多次receive进行拼接

[Quote=引用 9 楼 ttnewday 的回复:]
Client的OnSend事件。
帮忙看看。

#define SEND_BUFFER_SIZE 4096

void CCliOprSocket::OnSend(int nErrorCode)
{
CMemFile file;
file.Write(m_send.GetBuffer(0)+'\0', m_send.GetLength()+1);
file.SeekToBegin();

int sendLength = file.GetLength();

int dataLength, cbLeftToSend;
BYTE* sendData = NULL;
dataLength = sendLength;

dataLength = htonl(dataLength);
cbLeftToSend = …
[/Quote]
ttnewday 2009-02-14
  • 打赏
  • 举报
回复
Client的OnSend事件。
帮忙看看。

#define SEND_BUFFER_SIZE 4096

void CCliOprSocket::OnSend(int nErrorCode)
{
CMemFile file;
file.Write(m_send.GetBuffer(0)+'\0', m_send.GetLength()+1);
file.SeekToBegin();

int sendLength = file.GetLength();

int dataLength, cbLeftToSend;
BYTE* sendData = NULL;
dataLength = sendLength;

dataLength = htonl(dataLength);
cbLeftToSend = sizeof(dataLength);

/*发送内容长度*/
do
{
int cbBytesSent;
BYTE* bp = (BYTE*)(&dataLength) + sizeof(dataLength) - cbLeftToSend;
cbBytesSent = Send(bp, cbLeftToSend);
if(cbBytesSent == SOCKET_ERROR)
{
int iErr = ::GetLastError();
TRACE("客户端发送内容长度时出现sock错误。\n"
"已发送字节数:%d\n"
"上次错误:%d\n", cbBytesSent, iErr);
goto PreReturnCleanup;
}
cbLeftToSend -= cbBytesSent;
}
while(cbLeftToSend > 0);

sendData = new BYTE[SEND_BUFFER_SIZE];
cbLeftToSend = sendLength;


/*发送内容*/
do
{
int sendThisTime, doneSoFar, buffOffset;
sendThisTime = file.Read(sendData, SEND_BUFFER_SIZE);
buffOffset = 0;
m_send.Delete(0, file.GetLength()>sendThisTime?sendThisTime:file.GetLength());

do
{
Sleep(20);
doneSoFar = Send(sendData + buffOffset, sendThisTime);

if(doneSoFar == SOCKET_ERROR)
{
int iErr = ::GetLastError();
TRACE("客户端发送内容时出现sock错误。\n"
"已发送字节数:%d\n"
"上次错误:%d\n", doneSoFar, iErr);
goto PreReturnCleanup;
}

buffOffset += doneSoFar;
sendThisTime -= doneSoFar;
cbLeftToSend -= doneSoFar;
}
while(sendThisTime > 0);
}
while(cbLeftToSend > 0);

PreReturnCleanup:
delete[] sendData;
file.Close();

CAsyncSocket::OnSend(nErrorCode);
}
arong1234 2009-02-14
  • 打赏
  • 举报
回复
这不是被覆盖了,而是TCP报文总是把大报文分成小报文发送的,你发送2k字节,可能你要分三次才能接收完整,这是著名的“粘包”问题。
[Quote=引用 6 楼 ttnewday 的回复:]
就是将大的文件分段发送,所以才不停的触发OnSend。
我把Server的OnReceive接收到的数据保存到文本里,不知为什么,就是接收不完全,可能少了次数,或者这次没收完,下次的已经来了,导致数据被覆盖了。
[/Quote]
arong1234 2009-02-14
  • 打赏
  • 举报
回复
你的问题在于:你用的是异步模型,但是使用模型的方法确实同步模型的循环发送,这当然是有严重问题的。异步模型要善用其中的事件,不能使用蛮力不停的循环发送
ttnewday 2009-02-14
  • 打赏
  • 举报
回复
就是将大的文件分段发送,所以才不停的触发OnSend。
我把Server的OnReceive接收到的数据保存到文本里,不知为什么,就是接收不完全,可能少了次数,或者这次没收完,下次的已经来了,导致数据被覆盖了。
arong1234 2009-02-14
  • 打赏
  • 举报
回复
谁告诉你要定义一个超大buffer的?即使你buffer再大也没有用啊?!socket报文最大也就1460字节

但是不让你一次发完也不是让你循环发送啊?!当你send已经发送失败时,你再坚持发送只是浪费时间和CPU,你应该在这个时候用一个数据结构(一般是队列)把你待发报文保存起来,等下次网络允许你发送时(也就是OnSend触发时)再发送,这适合应该再OnSend里进行发送
gwemail2003 2009-02-14
  • 打赏
  • 举报
回复
send时应设置缓冲区,将大的文件分段发送。
ttnewday 2009-02-14
  • 打赏
  • 举报
回复
Send有个buffer,我设了buffer大小为4096,所以超过4096的数据,就会分次Send了。
难道数据长度为10000时,buffer的大小也要设为10000?
好像buffer的长度有限制,我试过数据量很大时,大概有100K,buffer设了100*1024,但Server接收不到。
arong1234 2009-02-14
  • 打赏
  • 举报
回复
OnSend/OnReceive被触发是异步通讯的本质特征,不知道你为什么把他当作一个“异常问题”,为什么会因为这个原因要换CSocket.
arong1234 2009-02-14
  • 打赏
  • 举报
回复
Send不应该循环发,当你遇到Send发送失败,且GetLastError=WSAEWOULDBLOCK时,你应该停止发送,直到OnSend被触发,你再在OnSend里继续发

OnReceive被触发但是接收不到数据是异常的,他只在有数据时才被触发

18,356

社区成员

发帖
与我相关
我的任务
社区描述
VC/MFC 网络编程
c++c语言开发语言 技术论坛(原bbs)
社区管理员
  • 网络编程
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧