【讨论】多核多线程Send,会不会导致接收到的包乱序?

shenyi0106 2011-05-06 10:00:49
加精
众所周知,TCP是流协议,没有包分界符,而且TCP是通过滑动窗口来发送和接收数据的,那么势必会有以下情况发生:
多核多线程情况下,同时调用send函数向一个hSock发送数据,由于种种原因(如线程切换,滑动窗口中有的单元收到ACK等情况)是否会造成发送时一个消息中包含多个消息的片段,进而导致接收包乱序的情况。

欢迎大家来讨论一下,目前对这个问题百思不得其解。

备注:测试结果是接收没有乱序,但是粘包。
...全文
2491 71 打赏 收藏 转发到动态 举报
写回复
用AI写文章
71 条回复
切换为时间正序
请发表友善的回复…
发表回复
liubio0 2013-03-27
  • 打赏
  • 举报
回复
引用 103 楼 wangyaninglm 的回复:
引用 48 楼 shenyi0106 的回复:引用 45 楼 zhao4zhong1 的回复:试验证明,会。 你试试一个线程循环send 65535个字节的0x00,同时另一线程循环send 1个字节的0xFF 然后在接收端检查收到的字节流中是否所有相邻的0x00都必然有65535个字节? 这是粘包,不是乱序 C/C++ code?1234567891011121……
怎么没人讨论了呢
shiter 2013-01-29
  • 打赏
  • 举报
回复
引用 48 楼 shenyi0106 的回复:
引用 45 楼 zhao4zhong1 的回复:试验证明,会。 你试试一个线程循环send 65535个字节的0x00,同时另一线程循环send 1个字节的0xFF 然后在接收端检查收到的字节流中是否所有相邻的0x00都必然有65535个字节? 这是粘包,不是乱序
#include<stdio.h>
#include <stdlib.h>
#include <winsock2.h>
#include <string.h>

#pragma comment(lib,"ws2_32.lib")

#define PORT 9999
#define IPADDR "127.0.0.1"
#define BACKLOG 20
#define FILENAME 200
#define LENGTH 200
#define BUFFERSIZE 1024*8

struct FILEHEAD //FILE-head  struct
{
	char filename[LENGTH];//file name
	unsigned int length;//the byte of the file

};

struct FILEDATA //FILE-data  struct
{
	char filename[LENGTH];//file name
	char package[BUFFERSIZE];//package data
	unsigned int length;//the byte of the file
	unsigned int index;//index of the package

};

struct sockaddr_in clientaddr;  //Definition of the external variable for thread function call

void getFileInformation(FILEHEAD file)
{
	printf( "file information :\n" );
	printf( "  Filename: %s\n", file.filename );
	//printf( "  Ext: %s\n", file.ext );
	printf( " the file length is: %ld btye\n", file.length );
}

void showClientinfo()
{
	//获取当前系统时间
	SYSTEMTIME st;
	GetLocalTime(&st);
	char SysDate[30];
	//将systime中的时间转变为字符串存入SysDate[30];
	sprintf(SysDate, "%4d-%2d-%2d %2d:%2d:%2d", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond);
	//Server显示客户端信息
	printf("%s Recv from Client [%s:%d] : %s\n", SysDate, inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
	//服务器向客户端回显信息
}

DWORD WINAPI requestThread(LPVOID lparam)
{
	FILEHEAD filehead;
	FILEDATA filedata;
	SOCKET newsock=(SOCKET)(LPVOID)lparam;
	//char buf[BUFFERSIZE]={0};
	memset(&filehead,0,sizeof(filehead));
	memset(&filedata,0,sizeof(filedata));

	showClientinfo();

	//printf("等待文件头信息 ...\n");

	int length_file_info=recv(newsock,(char *)&filehead,sizeof(filehead),0);
	if (SOCKET_ERROR==length_file_info)
	{
		printf("receive failed!\n");
		closesocket(newsock);
		return -1;
		
	}
	if (length_file_info<=0)
	{
		exit(1);//异常退出
	}

	getFileInformation(filehead);//打印文件信息

	FILE *fp=NULL;
	fp=fopen(filehead.filename,"wb+");
	if (NULL==fp)
	{
		perror("fail to build the file!!!\n");
		exit(1);
	}
	
	//printf("要接收的文件名为:");
	//printf(filehead.filename);//打印文件名	
	//printf ("\n catch file now....\n");
	
	int recv_length=0;//接收到字节的长度
	
	//Sleep(100);
	
	printf("开始接收...\n");
	filedata.index=0;
	while (1)
	{
		recv_length=recv(newsock,(char *)&filedata,sizeof(filedata),0);
		if (recv_length == SOCKET_ERROR)
		{
			printf("recv failed !\n");
			closesocket(newsock);
			//WSACleanup();
			return -1;
		}
		
		fwrite(filedata.package,1,BUFFERSIZE,fp);
		if (0==recv_length)
		{
			break;
		}
		//printf("第%d块接收成功!\n",filedata.index);
	}
	printf("\n接收完成...\n\n");
	
	fflush(fp);
	fclose(fp);
	fp=NULL;
	
	return 0;
}

int main(int argc,char *argv[])
{
	//初始化winsock版本信息,加载动态链接库(dll)
	WSADATA wsData;
	if (WSAStartup(MAKEWORD(2,2),&wsData)!=0)
	{
		printf("WSAStartup failed !!!\n");
		return -1;
	}
	
	//创建套接字
	SOCKET socklisten;
	if((socklisten=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))==INVALID_SOCKET)
	{
		printf("socket failed!!!\n");
		WSACleanup();
		return -1;
	}
	
	//设置服务器地址
	struct sockaddr_in servaddr;
	
	memset(&servaddr,0,sizeof(servaddr));
	servaddr.sin_family=AF_INET;
	servaddr.sin_port=htons(PORT);
	servaddr.sin_addr.S_un.S_addr=inet_addr(IPADDR);
	
	//绑定socket地址结构到监听套接字
	if (bind(socklisten,(sockaddr *)&servaddr,sizeof(servaddr))!=0)
	{
		printf("binding failed!!!\n");
		closesocket(socklisten);
		WSACleanup();
	}
	
	//在server上运行监听
	if (listen(socklisten,20)!=0)
	{
		printf("listen failed !!!\n");
		closesocket(socklisten);
		WSACleanup();
		return -1;
	}
	
	//接收客户端的连接请求
	printf("TCP server is start!!!\n");
	
	//clientaddrlength要有初值,
	int client_addr_length = sizeof(clientaddr);
	memset(&clientaddr,0,client_addr_length);
	SOCKET connect;
	
	//循环等待
	while (1)
	{
		if ((connect=accept(socklisten,(sockaddr *)&clientaddr,&client_addr_length))==INVALID_SOCKET)
		{
			printf("accept failed!!!\n");
			closesocket(connect);
			WSACleanup();
			return -1;
		}
		
		//创建新线程
		DWORD ThreadID;
		CreateThread(NULL,0,requestThread,(LPVOID)connect,0,&ThreadID);
	}
	
	

}
乱了。。。两个客户端发送大一点的文件,就乱
albertcorleone 2011-10-25
  • 打赏
  • 举报
回复
tcp的可靠,保证数据有序,其实还是有一些意外需要考虑。

【粘包】send数据时会出现粘包。send的机制,本身并非一旦send马上把数据传到对方主机,而是把数据拷入内核的一个发送缓冲区,伺机再发送数据。假如两次send的时间很短,前一次的数据还没真正发送,那么第二次调用send时会比较缓冲区中剩余空间是否足够容纳第二次传入的数据量,如果足够,第二次send的数据就会放在第一次send的数据之后,一起发送。如果不足够,send就会挂起,直到缓冲区中数据被发送到对端主机从新被标记为可用为止。如果第一次和第二次的数据是放在缓冲区中一起发送的,这就导致了粘包,就是说接收端一次recv接收到的数据是两次send发送的数据。并非一个recv对应一个send。

【send可能被调用多次】如果调用send发送很多数据,大于内核中缓冲区的大小,那么send返回值是实际拷贝进内核缓冲区中的数据量,该值可能比需要发送的数据量小,你需要反复几次调用send直到最后累计返回值和需要发送的数据一样多为止。

【tcp的有序】send时是把数据拷贝到内核的发送缓冲区,这个内核缓冲区大小可以通过一些socket函数进行修改。不过,内核缓冲区是可能比一个tcp包能传输的数据大,就是说这里边的数据会分成好几个tcp包进行传递。(以下是推理:对于send主机而言,直到此次内核缓冲区中数据全部发送到对端,即每个包都收到了ack,才能进行下一次send;对于recv主机来说,它把接受到的tcp包解包,把数据放入接收缓冲区,等待数据凑齐并确保其有序后,才让recv函数执行拷贝,把数据拷到recv指定的用户缓冲区。所以,几乎可以认为每次到达send缓冲区的数据和recv缓冲区接收到的数据才是一一对应的。tcp的有序性也就是在这里才保持有序。如果发送方的socket内核缓冲区大于接收方的socket内核缓冲区,就会导致接收方被挤爆,所以一次send的包也不应该太大,需要定义个用户层的分割机制)

【无序的产生】再来看多线程情况。在同一个socket上,假设每次send的数据量都很大,需要反复调用send才能发送完数据。看一个例子。有两个线程分别发送用户A和用户B的资料给对端主机。A和B的资料都很多,一个send是不够的,需要调用两次send。A的资料分为A1和A2发送,B的资料分为B1和B2发送。第一个线程先执行,把A1发送出去了,接着第二个线程执行,把B1发送出去了。然后第一个线程再次执行把A2发出,然后第二个线程发送B2。那么对端收到的结果就是A1B1A2B2。这就是无序的。
zlcp520 2011-05-13
  • 打赏
  • 举报
回复
内容存入剪贴板
csdn网速很慢 2011-05-12
  • 打赏
  • 举报
回复
网络接口本来就慢单线程都有CPU空闲时间。多线程完全多余。再说多线程是否会乱序看你程序怎么处理数据流的跟send也没关系啊。
warlock 2011-05-12
  • 打赏
  • 举报
回复
据我所知,TCPIP发送数据包的时候,每个包都有一个序号,在在网络上可能后发的包先到,到了请求机上再根据这个序号重新组成数据块。序号定了,就不会乱
chenjiawei007 2011-05-11
  • 打赏
  • 举报
回复
此贴让我感受到了 ”虎头蛇尾“这个成语的意思,看来百度文库看的少了。
xiaocongzhi 2011-05-11
  • 打赏
  • 举报
回复
按理说应该不会,TCP内部应该有相应的处理机制防止这种乱序发生的。
go299 2011-05-11
  • 打赏
  • 举报
回复
可以一部分一部分的发送啊
dfasri 2011-05-11
  • 打赏
  • 举报
回复
是不是指...
假如存在两份500M的内存块
然后开两个线程, 用同一个SOCKET一次性的send(socket, 500Mbuf) 直接把这两块500M的内存块发送出去.
接收端是正确接收到500M的连续内存块两块?
不会吧...
不过我没试过...
yyds2022 2011-05-11
  • 打赏
  • 举报
回复
我碰到过这种情况,百思不得其解,纠结的很。以至于对网络编程有点恐惧感了。
yi05xia 2011-05-11
  • 打赏
  • 举报
回复
多线程的问题也很困扰我,我先学习一下!
A1218Tiger 2011-05-11
  • 打赏
  • 举报
回复
[Quote=引用 5 楼 chenjiawei007 的回复:]
粘包有可能是发送数据逻辑上的问题,
[/Quote]
别听他的,这明显是没好好学的。
dj32008 2011-05-10
  • 打赏
  • 举报
回复
不懂。。
ming32001 2011-05-10
  • 打赏
  • 举报
回复
绝对不是灌水!!!
射天狼 2011-05-10
  • 打赏
  • 举报
回复
要自己定义命令结构。
在命令结构包含在固定的“包头”与“包尾”中。
tj_swjtu 2011-05-09
  • 打赏
  • 举报
回复
接触接触一下。
向上一区 2011-05-09
  • 打赏
  • 举报
回复
我做了一个多线程的服务器,客户端的socket都放到socketthread类中,这样就是一个客户端一个thread了。当然独占一个send了。之间没有关系,当然除了,userInfocontainer和roomInfoContainer这两个数据属于共享数据。不会出现乱序的。
popyanyang 2011-05-09
  • 打赏
  • 举报
回复
学习学习,但没有资源分了!
ZQW136517011 2011-05-08
  • 打赏
  • 举报
回复
这些是在讲什么啊……不懂啊
加载更多回复(51)

18,356

社区成员

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

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