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

posted @ 2022-03-30 09:31  LDnanchao  阅读(778)  评论(0编辑  收藏  举报