c++ 网络编程实践(第二篇)

网络通信之粘包和拆包

在解释粘包与拆包的概念前,先大致解释下网络传输过程的缓冲模型,示意图如下:
在这里插入图片描述

对照上图,发送端的缓冲区A是开发者在堆或栈上分配的缓冲区,并将该缓冲区的首地址作为send函数的入参,发送缓冲区B则是通信框架内部已经分配好的一段缓冲区,其大小可通过socket来设置。用户通过send发送的数据或先存储到发送缓冲区B来存储,通信框架根据实际的网络状况来决定每次发送多少字节到网络传输通道中。同样的,网络传输通道到达接收端的数据,会先存储到接收缓冲区B(通信框架维护),开发者通过recv函数从接收缓冲区B中取一定长度的数据放到自己分配的接收缓冲区A中,然后就可以对齐进行解析及处理。

当发送端连续快速的发送数据,即发送速率快于接收方的接收和处理速度,导致接收缓冲区B被塞满了,这时,发送端就不能继续发送了,网络发生阻塞。针对这一状况,一种处理方式就是需要尽快的将接收缓冲区B清空,换句话说,就是recv每次从接收缓冲区B中取的数据多一些,这样就可以避免接收缓冲区B被塞满,进而导致网络阻塞。

recv每次取的数据过多的时候,而程序每次需要处理的数据长度可能没那么长,这时候就产生了粘包,要处理粘包数据,就需要先将粘包数据拆解成程序可以处理的长度的数据,这就叫拆包,通过粘包与拆包的过程,就可以有效缓解接收端网络阻塞的问题。

验证

1. 修改客户端连续发送命令

客户端每次发送68个字节的数据,服务端接收到消息后会返回8个字节的数据

int main()
{
    
    
	EasyClient client;
	client.InitSocket();
	client.Connect("127.0.0.1", 10087);

	// 创建发送命令线程
	//std::thread t(HandleSendProc, &client);
	//t.detach();

	// 循环接收消息并处理消息
	LoginIn data;
	strcpy(data.userName, "zhangsan");
	strcpy(data.pwd, "123456");
	while (client.IsRunning())
	{
    
    
		client.HandleMessage();
		// 连续发送数据
		client.SendData(&data);
	}

	client.CloseSocket();

	std::cout << "client exit" << std::endl;
	std::cin.get();
	return 0;

}

启动服务端和客户端后,可以发现此时连续发送并没有阻塞网络,说明接收端还可以忙的过来,此时服务端和客户端的吞吐量大概在40MBPS左右,而网络I/O只有617Kbps, 这是因为CPU都用在了打印上。
在这里插入图片描述
将打印去掉,可以发现,网络I/O大概在2Mbps左右
在这里插入图片描述
当把每次发送的数据量加大,比如每次发送的包为1092字节时,其两个客户端,发现sever发生异常,这就是网络阻塞了;
在这里插入图片描述

在服务端接收数据时,增大接收缓冲区A

  • 接收缓冲区A尽量分配到堆空间上
  • 当设置的接收缓冲区A较大时,recv从接收缓冲区B中取出的实际长度的字节可能是随机的
  • 构建较大的消息缓冲区,先将recv获取的数据存储到消息缓冲区,再对消息缓冲区循环,若其长度大于DataHeader,则进行取出并处理
const int RECV_BUFF_SIZE_UNIT = 10240;
char szRecv[RECV_BUFF_SIZE_UNIT] = {
    
     0 };
char szMsgBuf[RECV_BUFF_SIZE_UNIT*10] = {
    
     0 }; // 消息缓冲区
int lastPos = 0;
int EasyClient::RecvData()
{
    
    
	int len = recv(m_sock, szRecv, 10240, 0);
	/*std::cout << "recv data len = " << len << std::endl;*/
	if (len <= 0)
	{
    
    
		std::cout << "receive from client failed" << std::endl;
		return -1;
	}
	memcpy(szMsgBuf + lastPos, szRecv, len);
	lastPos += len;

	while (lastPos >= sizeof(DataHeader))
	{
    
    
		DataHeader* header = (DataHeader*)szMsgBuf;
		if (lastPos >= header->dataLen)
		{
    
    
			int leftSize = lastPos - header->dataLen;
			ProcessMessage(header);
			memcpy(szMsgBuf, szMsgBuf + header->dataLen, leftSize);
			lastPos = leftSize;
		}
		else
		{
    
    
			break;
		}
	}	
	return 0;
}

猜你喜欢

转载自blog.csdn.net/tianzhiyi1989sq/article/details/113462535