7.1---客户端与服务端的粘包现象演示(网络缓冲区)

本文详细探讨了客户端和服务端在通信中遇到的粘包问题,通过实例展示了测试过程,包括循环发送8KB和大量数据时的缓冲区溢出。作者介绍了自定义缓冲区和设置接收缓冲区的重要性,以确保数据交互的正确性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

客户端代码和服务端代码都使用前面文章所封装的class
本文先对缓冲区做各种测试,文章最后会介绍粘包现象。接下来的几篇文章中会介绍客户端与服务端如何处理这种粘包现象
一、测试1:循环发送与接收少量数据(8kb)
测试1的过程为:
服务端:服务端代码基本不变,其接收客户端发送过来的数据,在收到数据之后将相应数据回送给客户端
客户端:客户端使用while()循环一直向服务端发送程序
客户端与服务端的每次交互的单个数据包比较小,只有8字节

//服务端代码如下
#include "EasyTcpServer.hpp"
#include "MessageHeader.hpp"
 
int main()
{
 
	EasyTcpServer server1;
	server1.Bind("192.168.0.105", 4567);//IP地址在内外网测试时更改
	server1.Listen(5);
 
	while (server1.isRun())
	{
		server1.Onrun();
		//std::cout << "空闲时间,处理其他业务..." << std::endl;
	}
 
	server1.CloseSocket();
	std::cout << "服务端停止工作!" << std::endl;
 
	getchar();  //防止程序一闪而过
	return 0;
}
//客户端代码如下
#include "EasyTcpClient.hpp"
 
int main()
{
	EasyTcpClient client1;
	client1.ConnectServer("192.168.0.105", 4567);//IP地址在内外网测试时更改
 
	Login login;
	strcpy(login.userName, "dongshao");
	strcpy(login.PassWord, "123456");
    //一直发送Login类型的报文,之后服务端会给自己回送LoginResult类型的报文
	while (client1.isRun())
	{
		client1.Onrun();
		client1.SendData(&login);
	}
 
	client1.CloseSocket();
	std::cout << "客户端停止工作!" << std::endl;
	getchar();  //防止程序一闪而过
	return 0;
}

三、网络缓冲区的概念
在不同的操作系统中,系统为网络程序设置了数据的接收缓冲区和发送缓冲区,用来存储接收的网络数据/发送的网络数据
双方进行通信的基本流程为:发送端将发送的数据先发送到发送缓冲区中,之后数据通过网络传输层传输到接收端的接收缓冲区中,之后接收端从接收缓冲区中接收数据。这就是通信双薪进行数据传输的基本流程
————————————————版权声明:本文为CSDN博主「多栖技术控小董」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/qq_41453285/article/details/105396539
注意事项:
这个缓冲区是由操作系统底层的网络通信栈所决定的,程序一般不能更改,但是可以通过一些方法(套接字选项、设置配置文件等)来更改
不同操作系统间的缓冲区大小可能都不相同
有了缓冲区的概念之后,我们来解析上面的程序所表现的情况的原因:
在交互少量数据(8kb)时:
内网测试:交互少量的数据,发送端的发送速率虽然过快,但是数据量比较少,发送端的发送缓冲区和接收端的接受缓冲区都足够用,数据没有发生溢出的情况,因此程序运行都正确
外网测试:因为与外网测试,可能由于网络的原因,发送端的发送速率过快,但是接收端的接受速率慢于发送端的发送速率,导致发送端的发送缓冲区和接收端的接收缓冲区溢出,程序无法正确执行,因此发生错误
在交互大量数据(1032kb):
内网测试:交互大量数据,原理与上面的外网测试一样,发送端的发送缓冲区和接收端的接收缓冲区溢出,程序无法正确执行,因此发生错误
外网测试:同上,发送端的发送缓冲区和接收端的接收缓冲区溢出
四、自定义程序缓冲区的概念
网络缓冲区我们一般不去设置,我们通过在程序中自定义一个缓冲区,不论网络中有多少数据发送过来,我们直接使用recv()函数将网络缓冲区中的数据接收到程序所设置的自定义缓冲区中,这样就不会导致接收缓冲区的溢出了,程序也就可以发送正常
五、交互大量数据:为客户端设置程序接收缓冲区,但服务端不设置(程序卡死)
服务端与客户端代码
需要更改一下MessageHeader.hpp头文件中LogoutResult报文的大小,更改如下,为其添加了一个1024kb的data成员

struct LoginResult :public DataHeader
{
	LoginResult() :result(0) {
		cmd = CMD_LOGIN_RESULT;
		dataLength = sizeof(LoginResult);
	}
	int result;
	char data[1024];  //为了测试发送大量数据而添加的
};

服务端的代码不变,没有为其设置接收缓冲区
客户端的代码如下:
然后客户端在主程序中向服务端不断的发送Login类型的报文,之后服务端会给客户端回送LoginResult类型的报文(1032字节的)

#include "EasyTcpClient.hpp"
 
int main()
{
	EasyTcpClient client1;
	client1.ConnectServer("111.229.177.161", 4567);
 
	Login login;
	strcpy(login.userName, "dongshao");
	strcpy(login.PassWord, "123456");
 
	while (client1.isRun())
	{
		client1.Onrun();
		client1.SendData(&login); //一直发送Login类型的报文,之后服务端会给自己回送LoginResult类型的报文
	}
 
	client1.CloseSocket();
	std::cout << "客户端停止工作!" << std::endl;
	getchar();  //防止程序一闪而过
	return 0;
}

另外,在接收数据时,我们在应用层为客户端设置一个接收缓冲区,大小为409600KB。然后重新更改RecvData()函数,之前的RecvData()函数是先接收头部部分大小的数据,再去接收报文实体部分的数据,这样肯定会导致缓冲区的溢出,现在我们让其每次接收数据时,直接从网络接收缓冲区中最大可以接收409600KB大小的数据

//其余省略同之前一样
class EasyTcpClient
{
private:
	#define RECV_BUFF_SIZE 409600
	char _recvBuff[RECV_BUFF_SIZE ]; //接收缓冲区
}
 
int EasyTcpClient::RecvData()
{
	//直接最大可以接收409600字节,并且打印每次从网络缓冲区中接收的数据大小
	int _nLen = recv(_sock, _recvBuff, 409600, 0);
	if (_nLen < 0) {
		std::cout << "<Socket=" << _sock << ">:recv函数出错!" << std::endl;
		return -1;
	}
	else if (_nLen == 0) {
		std::cout << "<Socket=" << _sock << ">:接收数据失败,服务端已关闭!" << std::endl;
		return -1;
	}
	std::cout << "_nLen=" << _nLen << std::endl;
	return 0;
}

六、交互大量数据:为客户端和服务端都设置程序接收缓冲区(数据交互正常)
服务端与客户端代码
客户端代码与“五”中的一样,不需要更改
服务端的代码EasyTcpServer.hpp需要更改一下,为其添加一个接受数据缓冲区。并且更改RecvData()函数,之前的RecvData()函数是先接收头部部分大小的数据,再去接收报文实体部分的数据,这样肯定会导致缓冲区的溢出,现在我们让其每次接收数据时,直接从网络接收缓冲区中最大可以接收409600KB大小的数据。并且由于测试中客户端只发送Login类型的数据,因此在接收数据之后,直接使用SendData()函数给客户端回送一个LoginResult类型的报文

class EasyTcpServer
{
private:
	#define RECV_BUFF_SIZE 409600
	char _recvBuff[RECV_BUFF_SIZE ]; //接收缓冲区
};
 
int EasyTcpServer::RecvData(SOCKET _cSock)
{
	int _nLen = recv(_cSock, _recvBuff, 409600, 0);
	if (_nLen < 0) {
		std::cout << "recv函数出错!" << std::endl;
		return -1;
	}
	else if (_nLen == 0) {
		std::cout << "客户端<Socket=" << _cSock << ">:已退出!" << std::endl;
		return -1;
	}
	std::cout << "_nLen=" << _nLen << std::endl;
	LoginResult ret;
	SendData(_cSock, &ret);
 
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值