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