最近在使用UE的Socket模块与Python服务器进行通信时遇到了一些坑,特此记录一下。
先来复现一下问题,这里只截取关键代码。
UE端:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 bool ASoc::SendMsg (const FString& Msg) { TSharedRef<FInternetAddr> TargetAddr = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateInternetAddr(); FString Serialized = Msg; bool bSend; TCHAR* SeriallizedChar = Serialized.GetCharArray().GetData(); int32 Size = FCString::Strlen(SeriallizedChar) + 1 ; int32 Sent = 0 ; bSend = SocClient->SendTo((uint8*)TCHAR_TO_UTF8(SeriallizedChar),Size,Sent,*TargetAddr); if (bSend) { UE_LOG(LOGNLPFORUE,Log,TEXT("[To LTP | %d]: %s" ),Size,*Msg); } else { UE_LOG(LOGNLPFORUE,Log,TEXT("Failed to send Msg to tlp" )); } return bSend; }
Python端:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 def socrecv (): global data,conn,addr,soc while True : data = str (conn.recv(recvbuff),'utf-8' ,'ignore' ) print('[recv msg from ue |' ,sys.getsizeof(data),']: ' ,repr (data))def soclisten (): global soc,bind,conn,addr,recvthread soc=socket.socket(socket.AF_INET,socket.SOCK_STREAM) soc.bind((ip,port)) soc.listen(5 ) print('server listen...' ) bind = True while True : conn,addr = soc.accept() print(addr,'已接入' ) recvthread = Thread(target=socrecv) recvthread.setDaemon(True ) recvthread.start() soclisten()
运行结果:
UE端发送的数据:
LOGNLPFORUE : [To LTP | 45 ]: {:,:,:}LOGNLPFORUE : [To LTP | 102 ]: {:,:,:}
Python端接收到数据:
[recv msg from ue | 148 ]: '{"cmd" :"ltp" ,"type" :"cws" ,"data" :"他叫汤' [recv msg from ue | 151 ]: '{" cmd":" ltp"," type":" cws"," data":" He told Tom to get the coat, but Tom brought a piece of underwear"}\x00'
可以看到数据容量并没有超出缓存上限,且Python端接收的数据都有做utf-8的编码转换,但依旧出现了中文数据接收不全,容量更大的英文数据反而没问题。
问题出在了UE端的FSocket::SendTo函数,SendTo函数的定义:
bool FSocket::SendTo (const uint8* Data, int32 Count, int32& BytesSent, const FInternetAddr& Destination)
Data就是我们要发送的字节数据,Count数据的大小,BytesSent记录的是数据的发送进度,Destination是要发送数据的地址。
问题就出在Count的值上,可以看到在上面的代码中我们是直接计算的FString的长度,然后以这个长度作为发送的数据大小,在纯英文的数据中这没有任何问题,但在中文数据中,由于中文编码的特殊性,FString应该有做特殊的编码处理,导致直接计算FString的长度作为发送数据的字节大小其实是小于真实数据大小的,这就导致在UE端发送中文数据时就没有发送完整到数据,所以Python端接收到数据就出现数据不全的问题。
既然知道原因了,接下来就可以解决了。那么我们就需要去找一个计算FString中文数据真实字节数的算法来计算SenTo要发送字节数据大小。
在网上我也没找到相关的算法代码,于是就去请教了一位大佬,大佬给了我一份算法代码:
int32 ASoc ::CalcUtf0NumFromString (const FString & Str ) { int32 result = 0 ; for (int i = 0 ; i < Str .Len (); i++) { if (Str [i] <= 0x7f ) result = result + 1 ; else if (Str [i] > 0x7f && Str [i] <= 0x07ff ) result = result + 2 ; else if (Str [i] > 0x07ff && Str [i] <= 0xffff ) result = result + 3 ; else result = result + 4 ; } return result + 1 ; }
没有去深究FString的中英文编码,代码我是没看明白的,使用这个算法计算数据的字节大小,就能计算出正确的大小。
然后UE端的代码将int32 Size = FCString::Strlen(SeriallizedChar) + 1;
换成int32 Size = CalcUtf0NumFromString(SeriallizedChar);
,问题就解决了。