求教:socket连续传输时大量数据错误

cyfage 2010-02-09 11:09:38
在下原本要写一个利用SOCKET进行文件传输的程序,结果在测试过程中发现一旦开始传输,程序在发送10个包之内的时间里就会出现丢包现象,后面接收到的包读出来全是错误。
在百思不得其解的情况下不断的写简化的测试程序,结果最后发现我使用的同步阻塞式的传输方式,在连续传输一小段时间以后就会出错,不明白为什么会出现这个问题……

经过反复测试,发现将发送端的发包速度降低到每100毫秒发送一个包以后就不容易出现这一点,降低到200毫秒以上则该问题彻底消失,接收到的包始终正确。

1、请问是因为连续过快的传输,造成接收端反复读写缓冲区造成的问题吗?
2、如果是,那么那些高速传输的程序是如何解决这一点的?

请各位达人指教!

这是接收端的代码:
#include <iostream>
#include <WinSock2.h>
#include <assert.h>
#pragma comment(lib, "WSOCK32")
using namespace std;

//连接的步骤:
//1:WSAStartup函数
//2:socket函数
//3:设定SeverAddr属性
//4:bind函数
//5:listen函数
//6:accept函数

int main()
{
WSADATA wsaData;
int Ret;
int ResBind;
int Port = 5150;
int ClientAddrLen;
char DataBuffer[1024];
char theTemp[1024];
SOCKET ListeningSocket;
SOCKADDR_IN ServerAddr;
SOCKET NewConnection = INVALID_SOCKET;
SOCKADDR_IN ClientAddr;
memset(DataBuffer, 0, sizeof(DataBuffer));

Ret = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (Ret != 0)
{
cout<<"WSAStartup Failed with Error "<<Ret<<" ."<<endl;
}
else
{
cout<<"WSAStartup Successful."<<endl;
}
//第一个参数为地址族;第二个为套接字类型;第三个为protocol,对于TCP/IP
//协议设定为IPPROTO_TCP
ListeningSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (ListeningSocket == INVALID_SOCKET)
{
cout<<"ListeningSocket Failed with Error "<<WSAGetLastError()<<" ."<<endl;
WSACleanup();
return 0;
}
else
{
cout<<"ListeningSocket Created Successful."<<endl;
}

ServerAddr.sin_family = AF_INET;
ServerAddr.sin_port = htons(Port);
ServerAddr.sin_addr.s_addr = htonl(INADDR_ANY); //允许绑定到系统所有可用接口
//bind函数:将已创建的套接字绑定到一个已知地址上
//第一个参数为等待客户端连接的套接字;第二个参数为建立缓冲区;第三个参数为
//协议决定的传递的结构长度
ResBind = bind(ListeningSocket, (SOCKADDR *)&ServerAddr, sizeof(ServerAddr));
if (ResBind == SOCKET_ERROR)
{
cout<<"Bind Failed with Error "<<WSAGetLastError()<<" ."<<endl;
closesocket(ListeningSocket);
WSACleanup();
return 0;
}
else
{
cout<<"Bind Created Successful."<<endl;
}
//listen函数:指示套接字等候连接传入
//第一个是指示等候的套接字;第二个是等待队列长度
if (listen(ListeningSocket, 5) == SOCKET_ERROR)
{
printf("listen failed with error %d\n", WSAGetLastError());
closesocket(ListeningSocket);
WSACleanup();
return 0;
}
else
{
cout<<"Llisten Started, Waiting for Cilent.."<<endl;
}

//accept函数:同意与客户端的链接
//第一个参数:监听模式的套接字;第二个是一个有效的SOCKADDR_IN地址
//试图保持循环,否则根本无法进行测试
/*int addrlen=sizeof(ServerAddr);*/
int addrlen=sizeof(ClientAddr);
memset(theTemp, 0, sizeof(theTemp));
strcpy(theTemp, "在测试中,如果服务端保持循环,则能成功看到这一步");
while (1)
{
//服务程序调用accept函数从处于监听状态的流套接字s的客户连接请求队列中取出排在最前的一个客户请求,
//并且创建一个新的套接字来与客户套接字创建连接通道,如果连接成功,就返回新创建的套接字的描述符,
//以后与客户套接字交换数据的是新创建的套接字;如果失败就返回INVALID_SOCKET。该函数的第一个参数指
//定处于监听状态的流套接字;操作系统利用第二个参数来返回新创建的套接字的地址结构;操作系统利用第
//三个参数来返回新创建的套接字的地址结构的长度。
NewConnection = accept(ListeningSocket, (SOCKADDR*) &ClientAddr, &addrlen);
if (INVALID_SOCKET != NewConnection)
{
//该步显示始终没有出现
cout<<"Get Connection Successfully From "<<inet_ntoa(ClientAddr.sin_addr)<<endl;
closesocket(ListeningSocket);
printf("We are waiting to receive data...\n");

//recv函数:
//第一个参数:用于接收的套接字;第二个参数:存放数据的缓冲区;第三个参数:缓冲区长度;
//第四个参数:0,MSG_PEEK或MSG_OOB
int i = 0;
while (i < 30000)
{
memset(DataBuffer, 0, sizeof(DataBuffer));
Ret = recv(NewConnection, DataBuffer, sizeof(DataBuffer), 0);
int Res = strcmp(DataBuffer, theTemp);
++ i;
cout<<i<<endl;
if (0 != Res)
{
cout<<i<<endl;
cout<<DataBuffer<<endl;
assert(0 == Res);
}
}
}
}
//关闭连接
closesocket(ListeningSocket);
WSACleanup();

return 0;
}


这是发送端的代码:
#include <winsock2.h>
#include <stdio.h>
#pragma comment(lib, "WSOCK32")
//连接的步骤
//1:WSAStartup函数
//2:socket函数
//3:SeverAddr设定
//4:connect函数

void main()
{
WSADATA wsaData;
SOCKET s;
SOCKADDR_IN ServerAddr;
int Port = 5150;
int Ret;

if ((Ret = WSAStartup(MAKEWORD(2,2), &wsaData)) != 0)
{
printf("WSAStartup failed with error %d\n", Ret);
return;
}

if ((s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP))
== INVALID_SOCKET)
{
printf("socket failed with error %d\n", WSAGetLastError());
WSACleanup();
return;
}

ServerAddr.sin_family = AF_INET;
ServerAddr.sin_port = htons(Port);
ServerAddr.sin_addr.s_addr = inet_addr("192.168.1.5");

printf("We are trying to connect to %s:%d...\n",
inet_ntoa(ServerAddr.sin_addr), htons(ServerAddr.sin_port));

if (connect(s, (SOCKADDR *) &ServerAddr, sizeof(ServerAddr))
== SOCKET_ERROR)
{
printf("connect failed with error %d\n", WSAGetLastError());
closesocket(s);
WSACleanup();
return;
}
else
{
//在测试中,如果服务端保持循环,则能成功看到这一步
printf("Our connection succeeded.\n");
printf("Now Try to Send Message.\n");
}

int i = 0;
while (i < 30000)
{
Ret = send(s, "在测试中,如果服务端保持循环,则能成功看到这一步", 5, 0);
++ i;
printf("%d\n", i);
}


printf("We are closing the connection.\n");
closesocket(s);
WSACleanup();
}


出错的问题显示:
http://p13.freep.cn/p.aspx?u=v20_p13_p_1002091107392251_0.jpg

[URL=http://p13.freep.cn/p.aspx?u=v20_p13_p_1002091107392251_0.jpg&click=1][IMG]http://p13.freep.cn/p.aspx?u=v20_p13_p_1002091107392251_0.jpg[/IMG][/URL]
...全文
816 13 打赏 收藏 转发到动态 举报
写回复
用AI写文章
13 条回复
切换为时间正序
请发表友善的回复…
发表回复
cattycat 2010-02-09
  • 打赏
  • 举报
回复
你连续发送的时候,接收端没来得及接收的话,会出现发送错误,你发送端继续发送,接收端会出现丢包的情况,接收端也是有缓冲的,不同步的话,数据会出错。这种情况下,建议用异步通信。
stjay 2010-02-09
  • 打赏
  • 举报
回复
发送端
send(s, "在测试中,如果服务端保持循环,则能成功看到这一步", 5, 0);
应为
char *buf = "在测试中,如果服务端保持循环,则能成功看到这一步";
send(s, buf, strlen(buf), 0);或send(s, buf, strlen(buf) + 1, 0);
yutaooo 2010-02-09
  • 打赏
  • 举报
回复

client发送的一个segment,在server端又没有说只通过一次recv就能完全接收到。你这里,从出错的图片上看,recv只收到了2个中文字符 '在测', 那明显与期待的完整句子不 cmp。不要去strcmp.

TCP是面向字节流的协议。没有分界限定。比如 每次发送300字符,发送3000次。你只能认为有300*3000个连续的字节。可以通过不断的recv全部收到。每次recv能收到多少,不确定。是否要3000次recv,也不确定,可能多些次,可能少些次。

消息的限定要应用层自己实现。可以通过头部,定义len字段。可以通过特殊字符标记结尾。
kingstarer 2010-02-09
  • 打赏
  • 举报
回复
你可以改一下协议 先发消息头 说明一下接下来有多少个字符到达
kingstarer 2010-02-09
  • 打赏
  • 举报
回复
把Ret打印出来看看

我怀疑是太快了 导致两个包连到一起接收了

再加上是中文 可能某个字符只接收了一半就显示 于是出现乱码
yutaooo 2010-02-09
  • 打赏
  • 举报
回复

一般就2种方式来分隔消息。如前面的帖子。可以用一个应用层的固定长度电文头,电文头中记录整个电文长度。以此区分电文边界。或者,在电文间放特殊字符,比如'\r\n\n'表示电文结束。
cyfage 2010-02-09
  • 打赏
  • 举报
回复
引用 11 楼 yutaooo 的回复:
其实,是没有传输速度过快的问题的。记住,我们讨论的是TCP,它没有这个问题,因为TCP协议对此有控制。UDP是有这个问题的。大致描述一下这个问题。

在接收方,有接收缓冲区,这不是用户提供的,而是在kernel有一个缓冲。TCP是有确认报文的协议。现在假设发送方发送非常快,接收方的上层应用来不及处理,接收缓冲区很快就满了。这时候,确认报文中的某一项(window大小)被设为0,以通告对方(发送方),我没有空间存放了,先别发了。发送方会停下。因此,没有速度过快的问题。

这个,可以看正规的计算机网络方面的书,比如:计算机网络,5版,谢希仁。

头部len,可以这样,先只接收4字节,这是假设用4字节计算segment的长度。转换成int后,说x.下次接收x字节。这两个是通过设定recv的第3参数,并且循环,直到满足收‘满’为止。这样的方式来控制。


请问,那如果是像我这种情况,该怎么处理呢?
yutaooo 2010-02-09
  • 打赏
  • 举报
回复

其实,是没有传输速度过快的问题的。记住,我们讨论的是TCP,它没有这个问题,因为TCP协议对此有控制。UDP是有这个问题的。大致描述一下这个问题。

在接收方,有接收缓冲区,这不是用户提供的,而是在kernel有一个缓冲。TCP是有确认报文的协议。现在假设发送方发送非常快,接收方的上层应用来不及处理,接收缓冲区很快就满了。这时候,确认报文中的某一项(window大小)被设为0,以通告对方(发送方),我没有空间存放了,先别发了。发送方会停下。因此,没有速度过快的问题。

这个,可以看正规的计算机网络方面的书,比如:计算机网络,5版,谢希仁。

头部len,可以这样,先只接收4字节,这是假设用4字节计算segment的长度。转换成int后,说x.下次接收x字节。这两个是通过设定recv的第3参数,并且循环,直到满足收‘满’为止。这样的方式来控制。
cyfage 2010-02-09
  • 打赏
  • 举报
回复
为这个问题我翻了不少资料,
可大多数示例都只做了个连接、传输的简单示范代码就为止了,大版大版的都是那些看了也无法理解的基础协议等等。
对于高速传输过程中怎样解决出错或者来不及接收等问题,很难找到相关的资料书籍。

请问有这方面的资料可看吗?
cyfage 2010-02-09
  • 打赏
  • 举报
回复
感谢楼上的各位,经过测试,证实是传输速度过快的问题。

对于先发送个包告诉接收方将有多少字节数到达的指点,我觉得很迷惑:

通过:
Ret = recv(NewConnection, DataBuffer, sizeof(DataBuffer), 0);

这句函数的结果来对比字节数是否接收正确?
如果没有接收到预料中的字节数该怎么办?发信息给发送端,叫它把刚才的包再发一次?
接下来如果重发了一次,那么从第多少个字节起往缓冲区存入?……

这样的搞法,接收和发送端的代码将复杂到我无法解决的程度,我对此的理解似乎陷入了误差。
请问在高速接收发送的时候,是采用怎样的设计方式的?
如果接收包有错误或者没有按时及时送到,该怎样处理?
Tody Guo 2010-02-09
  • 打赏
  • 举报
回复
楼上正解。这是问题的所在啊。
盘股之 2010-02-09
  • 打赏
  • 举报
回复
楼主粗心,send(s, "在测试中,如果服务端保持循环,则能成功看到这一步", 5, 0);
这个5不对啊,strlen("在测试中,如果服务端保持循环,则能成功看到这一步");
就好。
如果只发5个byte,在服务器端就只能显示“在测”这两个字,所以
int Res = strcmp(DataBuffer, theTemp); 肯定是!=0的
++ i;
cout<<i<<endl;
if (0 != Res)
{
到这儿了。
cout<<i<<endl;
cout<<DataBuffer<<endl;
assert(0 == Res);
}
赵4老师 2010-02-09
  • 打赏
  • 举报
回复
只要你深刻理解TCP/IP是基于流而不是基于包的协议,问题就解决了。

64,644

社区成员

发帖
与我相关
我的任务
社区描述
C++ 语言相关问题讨论,技术干货分享,前沿动态等
c++ 技术论坛(原bbs)
社区管理员
  • C++ 语言社区
  • encoderlee
  • paschen
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
  1. 请不要发布与C++技术无关的贴子
  2. 请不要发布与技术无关的招聘、广告的帖子
  3. 请尽可能的描述清楚你的问题,如果涉及到代码请尽可能的格式化一下

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