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请求一致
- 创建socket
- 链接目标ip地址和端口
- 交换数据
- 关闭端口
上面代码存在一个问题,就是如果服务器没有发送信息就会一直等待,造成阻塞,因此需要添加异步或者多线程以避免堵塞主线程。使用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服务器。
这就是响应后得到的数据。基本上验证了Fsocket可以支持Socket编程。
至于关于网络通信的一些插件
HPTcpSocketPlugin
TCP Socket Plugin
参考
https://unrealcommunity.wiki/third-party-socket-server-connection-hkujjjbn