轉自:https://blog.csdn.net/zilisen/article/details/75007447
一、簡介
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 <stdio.h> #include <string> #include <iostream> #include <WinSock2.h> #include <WS2tcpip.h> #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<uint8> 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<FInternetAddr> 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<uint8> BinaryArray) { return FString(ANSI_TO_TCHAR(reinterpret_cast<const char*>(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<uint8> 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<const char*>(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; //線程引用計數器 };
三、運行
在編輯器中,創建我們之前定義的GameInstance的藍圖類,並在Project Setting中設其為默認GameInstance。創建一個GameMode,在他的EventGraph中添加如下藍圖邏輯:

其中Succe是一個bool類型變量,Index是一個Int類型變量,不改默認值。
之后將這個GameMode綁定到當前場景中,編譯運行服務器,然后Play,大功告成!博主運行結果如下:

可以看到,收發消息都成功了。
