UE4網絡通訊


UE4本身自己支持多人聯機,在他的自己的一套框架內支持Actor和變量的更新,但是這種並不能滿足大多數的數據請求。

於是需要使用socket和自己的服務器進行數據交互。對此有兩套解決方案

1、使用Fsocket自行實行socket請求

2、使用商城或第三方插件

使用Fsocket需要提前設置一些參數

在bulid.cs中設置

PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "Sockets", "Networking" });

並在需要使用的地方引入對應的庫

#include "Networking.h"

下面是一個簡單的TCP請求,Fsocket提供了多種創建方式

FString ASocketTestGameModeBase::GetForTCP(const FString URLPath)
{
	ISocketSubsystem* SocketSubsystem = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM);
	UE_LOG(LogTemp, Log, TEXT("創建socket"));
	FSocket* ClntSocket = SocketSubsystem->CreateSocket(NAME_Stream, TEXT("TCP"), ESocketProtocolFamily::IPv4);
	if(!ClntSocket)
	{
		return "CreatSocket Error";
	}
	FIPv4Address add  = FIPv4Address(127, 0, 0, 1);
	UE_LOG(LogTemp, Log, TEXT("轉換地址"));
	TSharedPtr<FInternetAddr> InternetAddr =  SocketSubsystem->CreateInternetAddr(add.Value,8080);
	UE_LOG(LogTemp, Log, TEXT("鏈接中"));
	if(!ClntSocket->Connect(*InternetAddr))
	{
		return "Connect Error";
	}
	TArray<uint8> Data;
	int32 Count = 1024;
	Data.Init(0, Count);
	int32 BytesSent;
	UE_LOG(LogTemp, Log, TEXT("請求數據"));
	ClntSocket->Recv(Data.GetData(), Count, BytesSent);
	UE_LOG(LogTemp, Log, TEXT("轉換數據"));
	FString message = FString(ANSI_TO_TCHAR(reinterpret_cast<const char*>(Data.GetData())));
	UE_LOG(LogTemp, Log, TEXT("%s"),*message);
	ClntSocket->Close();
	return  "Connect success";
}

大致步驟和普通的socket請求一致

  1. 創建socket
  2. 鏈接目標ip地址和端口
  3. 交換數據
  4. 關閉端口

上面代碼存在一個問題,就是如果服務器沒有發送信息就會一直等待,造成阻塞,因此需要添加異步或者多線程以避免堵塞主線程。使用SetTimer同樣會導致堵塞。或者使用 HasPendingData去監聽數據。

對於服務器,使用了之前的一部分代碼

#define _CRT_SECURE_NO_WARNINGS
#define _WINSOCK_DEPRECATED_NO_WARNINGS

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>
#include <process.h>
#include <filesystem>

#define  BUF_SIZE 1024
#define  BUF_SMALL 100



unsigned WINAPI RequestHandler(void* arg);
const char* ContentType(char* file);
void SendData(SOCKET sock, char* ct, char* fileName);
void SendErrorMSG(SOCKET sock);
void ErrorHandling(const char* message);

int main(int argc, char* argv[])
{
	WSADATA wsadata;
	SOCKET hServSock, hClntSock;
	SOCKADDR_IN servAdr, clntAdr;

	HANDLE hThread;
	DWORD dwThreadID;
	int clntAdrSize;

	if(argc !=2)
	{
		printf("Usage:%s <port\n", argv[0]);
		exit(1);
	}

	if (WSAStartup(MAKEWORD(2, 2), &wsadata) != 0)
		ErrorHandling("WSAStartup() error");

	hServSock = socket(PF_INET, SOCK_STREAM, 0);
	memset(&servAdr, 0, sizeof(servAdr));
	servAdr.sin_family = AF_INET;
	servAdr.sin_addr.s_addr = htonl(INADDR_ANY);
	servAdr.sin_port = htons(atoi(argv[1]));

	if (bind(hServSock, (SOCKADDR*)&servAdr, sizeof(servAdr)) == SOCKET_ERROR)
		ErrorHandling("bind() error");
	if (listen(hServSock, 5) == SOCKET_ERROR)
		ErrorHandling("listen() error");

	/* 請求響應 */
	while (1)
	{
		clntAdrSize = sizeof(clntAdr);
		hClntSock = accept(hServSock, (SOCKADDR*)&clntAdr, &clntAdrSize);
		printf("Connection Request : %s:%d \r\n",
			inet_ntoa(clntAdr.sin_addr), ntohs(clntAdr.sin_port));
		hThread = (HANDLE)_beginthreadex(
			NULL, 0, RequestHandler, (void*)hClntSock, 0, (unsigned*)&dwThreadID);
	}
	closesocket(hServSock);
	WSACleanup();
	return 0;

}
unsigned WINAPI RequestHandler(void* arg)
{
	SOCKET hClntSock = (SOCKET)arg;
	/*char buf[BUF_SIZE];
	char method[BUF_SMALL];
	char ct[BUF_SMALL];
	char fileName[BUF_SMALL];*/


	SendErrorMSG(hClntSock);
	//recv(hClntSock, buf, BUF_SIZE, 0);
	//if(strstr(buf,"HTTP/")==NULL)
	//{
	//	SendErrorMSG(hClntSock);
	//	closesocket(hClntSock);
	//	return 1;
	//}

	//strcpy(method, strtok(buf, " /"));
	//if (strcmp(method, "GET"))//查看是否為GET方式
	//	SendErrorMSG(hClntSock);

	//strcpy(fileName, strtok(NULL, " /"));//查看請求名
	//strcpy(ct, ContentType((fileName)));//查看ContentType
	//SendData(hClntSock, ct, fileName);
	return 0;

}

void SendData(SOCKET sock, char* ct, char* fileName)
{
	char protocol[] = "HTTP/1.0 200 OK\r\n";
	char servName[] = "Server:simple web server\r\n";
	char cntLen[] = "Content-length:2048\r\n";
	char cnType[BUF_SMALL];
	char buf[BUF_SIZE];
	FILE* sendFile;

	sprintf(cnType, "Content-type:%s\r\n\r\n", ct);
	
	if((sendFile = fopen(fileName,"r")) == NULL)
	{
		SendErrorMSG(sock);
		return;
	}

	/* 傳輸頭信息 */
	send(sock, protocol, strlen(protocol), 0);
	send(sock, servName, strlen(servName), 0);
	send(sock, cntLen, strlen(cntLen), 0);
	send(sock, cnType, strlen(cnType), 0);

	/* 傳輸請求數據 */
	while (fgets(buf, BUF_SIZE, sendFile) != NULL)
		send(sock, buf, strlen(buf), 0);

	closesocket(sock);
}

void SendErrorMSG(SOCKET sock)
{
	char protocol[] = "HTTP/1.0 400 Bad Request\r\n";
	char servName[] = "Server:simple web server\r\n";
	char cntLen[] = "Content-length:2048\r\n";
	char cnType[] = "Content-type:text/html\r\n\r\n";
	char content[] = "<html><head><title>NETWORK<title></head><body><font size=+5><br> 發生錯誤!查看請求文件名和請求方式</br></font></body></html>";

	/* 傳輸頭信息 */
	send(sock, protocol, strlen(protocol), 0);
	send(sock, servName, strlen(servName), 0);
	send(sock, cntLen, strlen(cntLen), 0);
	send(sock, cnType, strlen(cnType), 0);
	send(sock, content, strlen(content), 0);
	closesocket(sock);
}
const char* ContentType(char* file)
{
	char extension[BUF_SMALL];
	char fileName[BUF_SMALL];
	strcpy(fileName, file);
	strtok(fileName, ".");
	strcpy(extension, strtok(NULL, "."));
	if (!strcmp(extension, "html") || !strcmp(extension, "htm"))
		return  "text/html";
	else
		return  "text/plain";
}

void ErrorHandling(const char* message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}

這段代碼會主動發送數據,但是是報錯的信息,如果看得懂的,可以調整一些代碼,就可以直接改成http服務器。

image-20220330091914296

這就是響應后得到的數據。基本上驗證了Fsocket可以支持Socket編程。

至於關於網絡通信的一些插件

HPTcpSocketPlugin

TCP Socket Plugin

參考

https://unrealcommunity.wiki/third-party-socket-server-connection-hkujjjbn

https://unrealcommunity.wiki/tcp-socket-listener-receive-binary-data-from-an-ip/port-into-ue4-(full-code-sample)-1eefbvdk

https://zhuanlan.zhihu.com/p/165033931


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM