UE依据TCP协议传递数据的实现方式
TCP协议是一种基于字节流的传输层通信协议,在UE中传输字节流可以通过原生C++或者UE c++两种方式。
c++ 中的字节流
C++的主要数据类型,主要分为三类:布尔型,整型(char型从本质上说,也是种整型类型,它是长度为1的整数,通常用来存放字符的ASCII码),浮点型。而 *_t
是typedef定义的表示标志,是结构的一种标注。即我们所看到的 uint8_t、uint16_t、uint32_t都不是新的数据类型,而是通过typedef给类型起得别名。*uint8_t是用1个字节表示的;uint16_t是用2个字节表示的;uint32_t是用4个字节表示的。
typedef signed char int8_t;
typedef short int int16_t;
typedef int int32_t;
typedef unsigned char uint8_t;
typedef unsigned short int uint16_t;
typedef unsigned int uint32_t;
uint8_t num=67;
cout << num << endl; //输出结果为C
C++ 中将uint8_t
作为字节流的基本单位,其基本转化如下:
// 基础类型到容器转换
template <typename T>
std::vector<std::uint8_t> ToByte(T input)
{
std::uint8_t* bytePointer = reinterpret_cast<std::uint8_t*>(&input);
return std::vector<std::uint8_t>(bytePointer, bytePointer + sizeof(T));
}
// 容器到基础类型转换
template <typename T>
std::vector<std::uint8_t>& operator>>(std::vector<std::uint8_t>& in, T& out)
{
if (in.size() >= sizeof(T)) {
out = *reinterpret_cast<T*>(in.data());
in.erase(in.begin(), in.begin() + sizeof(T));
}else {
out = T{ 0 };
}
return in;
}
UEC++ 中的字节流
UE中的基础数据数字类型
bool:代表布尔值 (永远不要假设布尔值的大小) BOOL 将不会进行编译
TCHAR:代表字符型(永远不要假设TCHAR的大小)
uint8:代表无符号字节(占1个字节)
int8:代表有符号的字节(占1个字节)
uint16:代表无符号"短整型" (占2 个字节)
int16:代表有符号"短整型" (占2 个字节)
uint32:代表无符号整型(占4字节)
int32:代表带符号整型(占4字节)
uint64:代表无符号"四字" (8个字节)
int64:代表有符号"四字"(8个字节)
float:代表单精度浮点型 (占4 个字节)
double:代表双精度浮点型 (占8 个字节)
PTRINT:代表可以存放一个指针的整型 (永远不要假设PTRINT的大小)
虚幻引擎(UE)中的所有字符串都作为FStrings
或TCHAR
数组以[UTF-16](http://en.wikipedia.org/wiki/UTF-16/UCS-2)
格式存储在内存中。大多数代码假设2个字节等于一个代码点。UEC++ 中的基本字节 用 uint8
存储。
UE中的字符转换
对于确定内存大小的 类型如 int、float、FVector2D 等可以直接用
memcpy(写入地址, 准备写入数据的起始地址, 写入的数据大小)` 的方式 直接操作内存读写。
而对于FString、FText
等类型,就可以考虑先统一成转化成TCHAR
数组,再将数组中的单个CHAR
直接转化成uint8
后组合成uint8
组,进行传递。
由于要传递中文字符,当读取/转化时要用到 TCHAR_TO_UTF8
和UTF8_TO_TCHAR
这两个方法。
ByteArrary实现
////LByteArrary.h
class LByteArrary
{
public:
template<typename T>
T ReadBytes();
template<typename T>
void WriteBytes(T u);
void WriteString(FString message);
FString ReadString(int size);
int32 ByteAvaliable(); //计算剩余字节数
void Memcpy(void const* _Src, size_t _Size); // _Src :原字符地址 , _Size:要拷贝的字节大小
public:
LByteArrary(int32 size);
~LByteArrary();
public:
TArray<uint8> Datas;
int32 ActLength; // 本次操作的实际长度
int32 Position; // 本次操作的具体位置
};
template<typename T>
inline T LByteArrary::ReadBytes() {
T res = 0;
int n = sizeof(T);
if (ActLength - Position < n) return res;
memcpy(&res, Datas.GetData() + Position, n);
Position += n;
return res;
}
template<typename T>
inline void LByteArrary::WriteBytes(T u) {
int n = sizeof(T);
// 若Data数组不够写,则对Data手动扩容
if (Datas.Num()<n)
{
Datas.AddUninitialized(n);
}
memcpy(Datas.GetData() + Position, &u, n);
Position += n;
ActLength = Position;
return ;
}
//LByteArrary.cpp
#include "LByteArrary.h"
void LByteArrary::WriteString(FString message)
{
TCHAR* seriallizedChar = message.GetCharArray().GetData();
int32 size = FCString::Strlen(seriallizedChar);
size += 4; // 服务器要求预留一个结束位(int 4字节)
if (Datas.Num()-Position<size) //Datas.Num()-Position 数组还剩多少长度
{
Datas.AddUninitialized(size);
}
memcpy(Datas.GetData() + Position, (uint8*)TCHAR_TO_UTF8(seriallizedChar), size - 4);
Position += size;
ActLength = Position;
}
FString LByteArrary::ReadString(int size)
{
uint32 StringLength = ReadBytes<uint32>();
const FString ReceivedString = FString(StringLength, UTF8_TO_TCHAR(Datas.GetData() + Position));
Position += StringLength;
return ReceivedString;
}
int32 LByteArrary::ByteAvaliable()
{
return ActLength - Position;
}
void LByteArrary::Memcpy(void const* _Src, size_t _Size)
{
if (Datas.Num()-Position< _Size)
{
Datas.AddUninitialized(_Size);
}
memcpy(Datas.GetData() + Position, _Src, _Size);
Position += _Size;
ActLength = Position;
}
LByteArrary::LByteArrary(int32 size)
{
Datas.Init(0, size);
}
LByteArrary::~LByteArrary()
{
}
代码参考自 https://www.bilibili.com/video/BV1Xk4y1B7Q4?p=17&vd_source=ecf09c4e73dd9afbbf64e026474a25e0
作者 : [天空游荡的鱼]
(https://space.bilibili.com/453151910)