一、简介 UE4引擎是提供了Sockets模块和Networking模块的,博主在研究此功能时也是参考的Sockets模块和Networking模块的源码,其中引擎为我们提供了一些实例类很有参考价值,比如Sockets模块中的MultichannelTcpReceiver.h和MultichannelTcpSender.h,Networking模块中的UdpSocketReceiver.h和UdpSocketSender.h。博主的例子就是研究参考了以上代码写出来的。
编译与运行环境
UnrealEngine 4.15 , Visual Studio 2015
实现功能介绍 博主的例子实现的是一个使用Socket多线程TCP通信的客户端。在主线程中发消息,子线程中收消息。当然也能类似的实现两个子线程分别收发消息。Socket相关函数都定义在了GameInstance中,以便我们能在不同场景都能调用。
二、代码实现
服务器代码
此处使用了一个有简单收发功能的服务器,用VS2015新建空项目即可:
//SocketTest.cpp: Defines the entry point for the console application#include
#include
#include
#include
#include
#pragma comment(lib, "WS2_32.lib")using namespace std;
int main()
{
WSADATA wsaData;
SOCKET serverSock;
SOCKADDR_IN serverAddr;
SOCKET clientSock;
SOCKADDR_IN clientAddr;
cout << "Server Start!" << endl;
//加载套接字库
int err = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (0 != err)
{
cout << "WSAStartup failed!" << endl;
return 1;
}//构造监听SOCKET,流式SOCKET
serverSock = socket(AF_INET, SOCK_STREAM, 0);
//配置监听地址和端口
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(4399);
//本地监听端口:4399
serverAddr.sin_addr.S_un.S_addr = INADDR_ANY;
//绑定SOCKET
int retVal = bind(serverSock, (SOCKADDR*)&serverAddr, sizeof(serverAddr));
if (SOCKET_ERROR == retVal)
{
cout << "bind failed!" << endl;
closesocket(serverSock);
WSACleanup();
return -1;
}retVal = listen(serverSock, 5);
if (SOCKET_ERROR == retVal)
{
cout << "listen failed!" << endl;
closesocket(serverSock);
WSACleanup();
return -1;
}char IPdot[20] = { '\0' };
inet_ntop(AF_INET, (void*)&serverAddr.sin_addr, IPdot, 16);
printf("Welcome,the Host %s is running!Now Wating for someone comes in!\n", IPdot);
int addrClientlen = sizeof(clientAddr);
//等待客户端连接
clientSock = accept(serverSock, (SOCKADDR*)&clientAddr, &addrClientlen);
while (1)
{
//发送数据
char sendBuff[50];
//sprintf_s(sendBuff, "welcome %s to here", inet_ntoa(clientAddr.sin_addr));
char IPdotdec[20] = { '\0' };
inet_ntop(AF_INET, (void*)&clientAddr.sin_addr, IPdotdec, 16);
sprintf_s(sendBuff, "From Server: welcome %s to here", IPdotdec);
send(clientSock, sendBuff, strlen(sendBuff) + 1, 0);
//接收数据
char recvBuff[50];
recv(clientSock, recvBuff, 50, 0);
printf(" %s \n\n", recvBuff);
Sleep(1000);
}
//关闭套接字,关闭加载的套接字库
closesocket(serverSock);
WSACleanup();
return 0;
}
模块添加
新建项目,在.Build.cs文件中添加Sockets模块和Networking模块:
// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.using UnrealBuildTool;
public class SocketThread : ModuleRules
{
public SocketThread(TargetInfo Target)
{
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "HeadMountedDisplay","Sockets", "Networking" });
}
}
Socket相关函数定义
STGameInstance.h
//this is the STGameInstance.h#pragma once#include "Engine/GameInstance.h"
#include "Networking.h"
#include "ReceiveThread.h"
#include "STGameInstance.generated.h"UCLASS()
class SOCKETTHREAD_API USTGameInstance : public UGameInstance
{
GENERATED_BODY()public:
//创建Socket并连接到服务器(主线程)
UFUNCTION(BlueprintCallable, Category = "MySocket")
bool SocketCreate(FString IPStr, int32 port);
//发消息(主线程)
UFUNCTION(BlueprintCallable, Category = "MySocket")
bool SocketSend(FString message);
//收消息(子线程)
UFUNCTION(BlueprintCallable, Category = "MySocket")
bool SocketReceive();
UFUNCTION(BlueprintCallable, Category = "MySocket")
bool ThreadEnd();
FString StringFromBinaryArray(TArray BinaryArray);
public:
FSocket *Host;
FIPv4Address ip;
FRunnableThread* m_RecvThread;
};
STGameInstance.cpp
//this is the STGameInstance.cpp#include "SocketThread.h"
#include "STGameInstance.h"bool USTGameInstance::SocketCreate(FString IPStr, int32 port)
{
FIPv4Address::Parse(IPStr, ip);
//将传入的IPStr转为IPv4地址//创建一个addr存放ip地址和端口
TSharedPtr addr = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateInternetAddr();
addr->SetIp(ip.Value);
addr->SetPort(port);
//创建客户端socket
Host = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateSocket(NAME_Stream, TEXT("default"), false);
//连接成功
if (Host->Connect(*addr))
{
UE_LOG(LogTemp, Warning, TEXT("Connect Succeed!"));
return true;
}
//连接失败
else
{
UE_LOG(LogTemp, Warning, TEXT("Connect Failed!"));
return false;
}
}bool USTGameInstance::SocketSend(FString message)
{
TCHAR *seriallizedChar = message.GetCharArray().GetData();
int32 size = FCString::Strlen(seriallizedChar) + 1;
int32 sent = 0;
if (Host->Send((uint8*)TCHAR_TO_UTF8(seriallizedChar), size, sent))
{
UE_LOG(LogTemp, Warning, TEXT("___Send Succeed!"));
return true;
}
else
{
UE_LOG(LogTemp, Warning, TEXT("___Send Failed!"));
return false;
}
}bool USTGameInstance::SocketReceive()
{
m_RecvThread = FRunnableThread::Create(new FReceiveThread(Host), TEXT("RecvThread"));
return true;
}bool USTGameInstance::ThreadEnd()
{
if (m_RecvThread != nullptr)
{
m_RecvThread->Kill(true);
delete m_RecvThread;
}
return true;
}FString USTGameInstance::StringFromBinaryArray(TArray BinaryArray)
{
return FString(ANSI_TO_TCHAR(reinterpret_cast(BinaryArray.GetData())));
}
收消息线程
ReceiveThread.h
//this is the ReceiveThread.h
#pragma once#include "ThreadingBase.h"
#include "Networking.h"class SOCKETTHREAD_API FReceiveThread : public FRunnable
{
public:
FReceiveThread(FSocket* client): m_Client(client)
{}
~FReceiveThread()
{
stopping = true;
}virtual bool Init() override
{
stopping = false;
return true;
}virtual uint32 Run() override
{
if (!m_Client)
{
return 0;
}TArray ReceiveData;
uint8 element = 0;
//接收数据包
while (!stopping)//线程计数器控制
{
ReceiveData.Init(element, 1024u);
int32 read = 0;
m_Client->Recv(ReceiveData.GetData(), ReceiveData.Num(), read);
const FString ReceivedUE4String = FString(ANSI_TO_TCHAR(reinterpret_cast(ReceiveData.GetData())));
FString log = "Server:" + ReceivedUE4String;
UE_LOG(LogTemp, Warning, TEXT("*** %s"), *log);
FPlatformProcess::Sleep(0.01f);
}return 1;
}virtual void Stop() override
{
stopping = true;
//计数器-1
}private:
FSocket* m_Client;
//客户端套接字
bool stopping;
//循环控制
FThreadSafeCounter m_StopTaskCounter;
//线程引用计数器
};
三、运行 【UE4引擎|UE4 Sockets多线程TCP通信】在编辑器中,创建我们之前定义的GameInstance的蓝图类,并在Project Setting中设其为默认GameInstance。创建一个GameMode,在他的EventGraph中添加如下蓝图逻辑:
文章图片
其中Succe是一个bool类型变量,Index是一个Int类型变量,不改默认值。
之后将这个GameMode绑定到当前场景中,编译运行服务器,然后Play,大功告成!博主运行结果如下:
文章图片
可以看到,收发消息都成功了。
推荐阅读
- OS|多进程和多线程的区别是什么(多进程和多线程的优缺点分析)
- python|redis 绑定ip不生效,redis外网无法访问的解决方案
- 网络安全|网络安全防护技术
- 计算机网络笔记|1_计算机网络_OSI七层模型-TCP/IP五层模型
- 计算机网络|TCP流有啥特点(沾包问题又是啥?如何解决粘包问题?)
- C++|基于QT实现简单的TCP通信
- 十万个为什么|为什么需要TCP加速(TCP怎么加速呢?)
- QT|QT实现TCP通信
- 多线程|关于可见性