【UE C++】WebSockets接收过长BinaryMessage时分段问题的解决方案 需改UE源码

本文介绍了在UE4/5中,当WebSocket接收大数据量且间隔时间短时,数据被分段处理的问题。作者分析了可能的原因,包括WS服务器端压缩、C++ LibWebSockets库的限制,以及UE源码中的一个待调查项。通过修改WS缓冲区大小和调整lws处理消息的回调方法,成功解决了分段问题,并提供了相关源码修改方案。
摘要由CSDN通过智能技术生成

问题简介

基于我上一篇文章 《虚幻引擎WebSocket简单应用》

在实际项目使用中,发现在大数据量,间隔时间短的情况下 UE接收到的数据被分段处理

本文通过修改UE websockets模块源码的方式,解决此问题。

tips: 需要去Github 上Epic ue获取引擎源码 修改编译。

问题复现

通过在TestWebsockets.cpp文件 OnPacketReceived方法打断点调试发现

从底层传来的 Data 被分段, Size 最大为8192 ,最可怕的是BytesRemaining(分段剩余字节数)一直为0,导致我们无法在此方法中把完整的消息组装起来,也无法判断消息片段处于哪一种状态。

疑似问题原因

WS Server端压缩

参考文章 链接: WebSocket的压缩引发的问题 发现在实际应用场景中,大多Websocket Server会通过Nginx代理,在其实现端(Java等后端)和服务器端会有一些配置 来保障WebSocket建立多连接及大数据量场景下的性能,其中压缩协议是很常见的Sec-WebSocket-Extensions: permessage-deflate 也许通过某些后端配置也可以解决分段问题,但笔者与公司后端排查后未解决,如有相关配置欢迎评论区讨论。

请添加图片描述

C++ LibWebSockets库

LibWebSockets通过官网学习:官网

相关相同问题 链接:
ws client: lws_remaining_packet_payload always returns zero with enabled permessage-deflate about libwebsockets

lws_remaining_packet_payload’ doesn’t work with ‘permessage_deflate’ enabled

引用回答
That api tells you what’s left in the fragment, which is all ws protocol tells us (NOT the message size).

With pmd, the message and the fragment contain compressed data. It’s not meaningful to tell you how much compressed data remains in the fragment, since this has an unknown relationship to the amount of decompressed data it will deflate to.

Instead of looking for information that doesn’t exist, lws can tell you what info it does have via lws_is_final_fragment()… it knows if it’s on the final fragment of the compressed message. And it knows if it is in the middle of passing the deflated data from that buffer to you. So it can delay telling you it’s final until it is on the last chunk of the final compressed bufferload. That will be correct then both for compressed and uncompressed messages.

from libwebsockets.

UE的一个TODO

请添加图片描述

在UE源码Websocket模块中 LwsWebSocketsManager.cpp 148行

// TODO: Investigate why enabling LwsExtensions prevents us from receiving packets larger than 1023 bytes, and also why lws_remaining_packet_payload returns 0 in that case

调查为什么启用LwsExtensions会阻止我们接收大于1023字节的数据包,以及为什么在这种情况下lws_remaining_packet_payload返回0

去Github上看了下历代版本UE源码 (4.21 ~ 5.3 截止2023年10月)

这个TODO是一直都在 看来还得靠自己啊 O(∩_∩)O

自己解决问题

主要修改了两个文件 WebSockets模块中

  1. LwsWebSocketsManager.cpp
  2. LwsWebSocket.cpp

所有lws的结构体 在源码中发现lws相关 可以在此查阅

请添加图片描述

处理消息的重要回调方法

在LwsWebSocketsManager中InitWebSockets是对lws的应用,包括对lws对象的一些配置,及注册ws相关回调方法。在86行 LwsProtocol.callback = &FLwsWebSocketsManager::StaticCallbackWrapper; 是一个关键方法的回调注册

所有的回调通过 LwsWebSocket.cpp文件LwsCallback 处理 Socket->LwsCallback(Connection, Reason, Data, Length);

在LwsWebSocket.cpp是lws根据WS状态(协议)来处理收到的消息的具体实现,在接收Binary Message时会根据Remain (BytesLeft)字段 判断是否已经完整接收一段消息,并把消息推送到消息队列

case LWS_CALLBACK_CLIENT_RECEIVE:
const SIZE_T BytesLeft = lws_remaining_packet_payload(Instance);
if (BytesLeft == 0){
bWakeGameThread = true;
ReceiveTextQueue.Enqueue(MakeUnique<FLwsReceiveBufferText>(MoveTemp(ReceiveBuffer)));
ReceiveBuffer.Empty();}	

正如UE源码这个TODO所说: 自己调查为啥lws_remaining_packet_payload方法总是返回0 也就是所有被分段的信息都被标记为完成接收状态并由消息队列广播出去。

修改WS缓冲区大小

未处理完成的消息会被存入缓冲区 既然已经到了修改源码的层面,我们可以把缓冲区的大小调大一些

LwsWebSocketsManager.cpp

		for (const FString& Protocol : Protocols)
	{
		FTCHARToUTF8 ConvertName(*Protocol);

		// We need to hold on to the converted strings
		ANSICHAR* Converted = static_cast<ANSICHAR*>(FMemory::Malloc(ConvertName.Length() + 1));
		FCStringAnsi::Strcpy(Converted, ConvertName.Length(), (const ANSICHAR*)ConvertName.Get());
		lws_protocols LwsProtocol;
		FMemory::Memset(&LwsProtocol, 0, sizeof(LwsProtocol));
		LwsProtocol.name = Converted;
		LwsProtocol.callback = &FLwsWebSocketsManager::StaticCallbackWrapper;
		LwsProtocol.per_session_data_size = 0;	// libwebsockets has two methods of specifying userdata that is used in callbacks
												// we can set it ourselves (during lws_client_connect_via_info - we do this, or via lws_set_wsi_user)
												// or libwebsockets can allocate memory for us using this parameter.  We want to set it ourself, so set this to 0.
 		LwsProtocol.rx_buffer_size = 10 * 1024 * 1024; // Largest frame size we support
		LwsProtocol.tx_packet_size = 10 * 1024 * 1024;
		LwsProtocols.Emplace(MoveTemp(LwsProtocol));
	}

	// LWS requires a zero terminator (we don't pass the length)
	LwsProtocols.Add({ nullptr, nullptr, 0, 0 });

	// Subscribe to log events.  Everything except LLL_PARSER
	static_assert(LLL_COUNT == 11, "If LLL_COUNT increases, libwebsockets has added new log categories, analyze if we should be listening to them");
	lws_set_log_level(LLL_ERR | LLL_WARN | LLL_NOTICE | LLL_INFO | LLL_DEBUG | LLL_HEADER | LLL_EXT | LLL_CLIENT | LLL_LATENCY | LLL_USER, &LwsLog);

	struct lws_context_creation_info ContextInfo = {};

	ContextInfo.port = CONTEXT_PORT_NO_LISTEN;
	ContextInfo.protocols = LwsProtocols.GetData();
	ContextInfo.uid = -1;
	ContextInfo.gid = -1;
	ContextInfo.options |= LWS_SERVER_OPTION_PEER_CERT_NOT_REQUIRED | LWS_SERVER_OPTION_DISABLE_OS_CA_CERTS | LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
	ContextInfo.max_http_header_data = 0;

	int32 MaxHttpHeaderData = 1024 * 32;
	GConfig->GetInt(TEXT("WebSockets.LibWebSockets"), TEXT("MaxHttpHeaderData"), MaxHttpHeaderData, GEngineIni);
	ContextInfo.max_http_header_data2 = MaxHttpHeaderData;
	ContextInfo.pt_serv_buf_size = MaxHttpHeaderData;

修改lws处理消息的回调方法

LwsWebSocket.cpp

通过阅读libweosocket库官网,仔细阅读了每一个处理消息的方法
发现除了lws_remaining_packet_payload来判断 剩余多少分段消息外
还可以用lws两个api来判断 lws_is_first_fragment lws_is_final_fragment

废话不多说贴代码

case LWS_CALLBACK_CLIENT_RECEIVE:
	{
		isFirst = lws_is_first_fragment(Instance);
		isEnd = lws_is_final_fragment(Instance);
			BytesLeft = lws_remaining_packet_payload(Instance);
		UE_LOG(LogWebSockets, VeryVerbose, TEXT("FLwsWebSocket[%d]::LwsCallback: Received LWS_CALLBACK_CLIENT_RECEIVE Length=%d BytesLeft=%d"), Identifier, Length, BytesLeft);
		bool bWakeGameThread = false;
		if (bWantsMessageEvents)
		{
			FUTF8ToTCHAR Convert((const ANSICHAR*)Data, Length);
			ReceiveBuffer.Append(Convert.Get(), Convert.Length());
			if (isEnd == 1)
			{
				bWakeGameThread = true;
				ReceiveTextQueue.Enqueue(MakeUnique<FLwsReceiveBufferText>(MoveTemp(ReceiveBuffer)));
				ReceiveBuffer.Empty();
			}
		}
		if (bWantsRawMessageEvents)
		{
			bWakeGameThread = true;
			ReceiveBinaryQueue.Enqueue(MakeUnique<FLwsReceiveBufferBinary>(static_cast<const uint8*>(Data), Length, BytesLeft));
		}

重新编译UE源码 问题解决 能够获取正确的消息

  • 6
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值