2015.08.26. 21:18답글기메경blocking accept()를 써서 return이 안되서 발생한 문제로 밝혀졌습니다 ㅋㅋ ㅠ nonblocing을 써봤지만 똑같은 현상이 나왔구 이젠 멀티쓰레드를 이용한 방법으로 하고있는데, 쓰레드 개념이 잘 잡혀있지 않아서 잘 안되네요 ㅋㅋ ㅠ
I succeeded to setup a TCP connection with my server , its working great !
But i felt disappointed about the Connect step. Indeed , when the IP/Port are correct , the connection is established at once . But , when the IP is wrong or the server is shutdown , the editor freeze at least 30 sec then the log (that says i can't connect) shows up.
I finally found the problem : FTcpSocketBuilder("SOCKETNAME").AsNonBlocking().Build() was not setting the socket AsNonBlocking.
I had to add "TCPSocket->SetNonBlocking(true);" in order to make it work so the editor doesn't freeze anymore when the server is shutdown.
Well , i was so glad my editor didn't freeze anymore when the server is shutdown that i didn't test when the server is alive..... and TCPSocket->SetNonBlocking(true) made it impossible to connect ....
I change the way i create the socket by doing : TCPSocket = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateSocket(NAME_Stream, TEXT("default"), false);
And now its working flawlessly !
I still don't understand why what i did was wrong so i let this post up as that sound buggy for me.
I have spent a while searching and learning on how to implement the talking between UE4 and Python. I have used Rama's code as well as some random ones on the internet to get me where I am. Below is the code I am using to talk to a Python UDP server that will handle all my non game related network code (e.g. Logging into the server and checking user info, sending remember tokens, retrieving saved characters and anything else you can think of that doesn't rely on the game engine.) I hope this helps.
Python Code
UDPServer.py
You could do the below differently and even put the handler in a different thread.
Code:
##################################
## UDP Server to Talk to UE4
##################################
### Imports
import socket
import sys
from UDPRequestHandler import *
HOST = ''
PORT = 8800
# Create the UDP Socket
try:
socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
#socket.setblocking(false)
print("Socket Created")
except:
print("Failed to create socket.")
sys.exit()
# Bind socket to local host and port
try:
socket.bind((HOST, PORT))
except:
print("Bind failed.")
sys.exit()
print("Socket bind complete.")
# Now keep talking to clients
while 1:
# Receive data from client (data, addr)
d = socket.recvfrom(1024)
data = str(d[0], "utf-8")
addr = d[1]
# Print to the server who made a connection.
print("{} wrote:".format(addr))
print(data)
# Now have our UDP handler handle the data
myUDPHandler = UDPRequestHandler()
myResponse = myUDPHandler.handle(data)
# Respond back
print(myResponse)
socket.sendto(bytes(myResponse, 'utf-8'), addr)
socket.close()
UDPRequestHandler.py
This is called to handle each connection.
Code:
########################
## UDPHandler Class
########################
### Imports
import socketserver
import json
from passlib.hash import pbkdf2_sha256
from UDPPostgreSQLConnector import *
class UDPRequestHandler:
def checkLogin(self, email, password):
# This checks to see if the player is who they say they are.
# If so send back a dict with the players ID and remember_token.
myDB = UDPPostgreSQLConnector()
pswdToCheck = pbkdf2_sha256.encrypt(password, rounds=200000,
salt_size=16)
dbResponse = myDB.selectWhere("game.players",
"*", "email = '" + email + "'")
return dbResponse
#if (pbkdf2_sha256.verify(password, dbResponse['password'])):
# return "Logged In"
#else:
# return "Invalid Password"
def handle(self, data):
myData = json.loads(data)
if myData['Request'] == 'Login':
ResponseStr = "{'Return': " + self.checkLogin(myData['Email'],
myData['Password'])
elif myData['Request'] == 'Test':
ResponseStr = pbkdf2_sha256.encrypt(myData['Password'], rounds=5000,
salt_size=16)
else:
ResponseStr = "I hear you"
response = ResponseStr
return response
UDPPostgreSQLConnector.py
This handles all database work. This is basic and will be expanded as needed. But you should get the idea. Also, sensitive info stripped.
Code:
#######################
## PostgreSQL Class
#######################
import psycopg2 as mdb
import psycopg2.extras
import sys
class UDPPostgreSQLConnector:
connection = None
def openConnection(self, db):
conString = "host='999.888.77.6' dbname='game' user='secret' password='secret'"
# opens a connection to the db
self.connection = mdb.connect(conString)
def closeConnection(self):
#Closes the connection
self.connection.close()
def selectWhere(self, table, columns, where):
# Use like
#selectWhere("users", "id", "email='lll'")
try:
self.openConnection(self.db)
cur = self.connection.cursor(cursor_factory=mdb.extras.RealDictCursor)
cur.execute("SELECT " + columns + " FROM " + table +
" WHERE " + where)
data = cur.fetchone()
if (data == ""):
data = "No users."
cur.close()
except mdb.DatabaseError as e:
data = 'Error %s' % e
#sys.exit(1)
finally:
if self.connection:
self.closeConnection()
return data
The python code is all stored on my Ubuntu server and not on my development computer. Below is the different classes and code for my test game.
UE4 C++ Code
UDP Networking Wrapper (H)
This is the main object I use in blueprints to send and receive.
Code:
#include "Object.h"
#include "Networking.h"
#include "Json.h"
#include "UDPReceiveWorker.h"
#include "UDPNetworkingWrapper.generated.h"
/**
*
*/
UCLASS(BlueprintType, Blueprintable)
class GAME_API UUDPNetworkingWrapper : public UObject
{
GENERATED_BODY()
public:
//----------------------------------------------------------
// Construction
/**
* Creates and initializes a new UDPNetworking object.
*
* @param Description - The description of the socket for debug purposes.
* @param SenderSocketName - Name of the sender socket for debug purposes.
* @param TheIP - IP of the the machine you want to send a message too.
* @param ThePort - The port of the machine you are trying to talk to.
* @param BufferSize - The size of the buffer for the socket
* @param AllowBroadCast - Allow broadcasting on this socket?
* @param Bound - Bind socket to the ip and port?
* @param Reusable - Is this socket reusable?
* @param Blocking - Is this socket blocking other data?
*/
UFUNCTION(BlueprintPure, Category = "UDPNetworking")
static UUDPNetworkingWrapper* ConstructUDPWrapper(const FString& Description, const FString& SenderSocketName, const FString& TheIP, const int32 ThePort, const int32 BufferSize,
const bool AllowBroadcast, const bool Bound, const bool Reusable, const bool Blocking);
static UUDPNetworkingWrapper* Constructor();
/**
* Sends the supplied message
* @param Message The message to be sent.
*/
UFUNCTION(BlueprintCallable, Category = "UDPNetworking")
bool sendMessage(FString Message);
//--------------------------------------------------------
// Destruction and reset
/** Destroys all data */
UFUNCTION(BlueprintCallable, Category = "UDPNetworking")
void UDPDestructor();
//// Grab Data
UFUNCTION(BlueprintCallable, Category = "UDPNetworking")
bool anyMessages();
/** Test Look for message */
UFUNCTION(BlueprintCallable, Category = "UDPNetworking")
FString GrabWaitingMessage();
static FString StringFromBinaryArray(const TArray<uint8>& BinaryArray);
private:
// Holds the socket we are sending on
FSocket* SenderSocket;
// Description for debugging
FString SocketDescription;
// Remote Address
FIPv4Endpoint RemoteEndPoint;
FIPv4Address RemoteAdress;
// Socket Subsystem
ISocketSubsystem* SocketSubsystem;
//UDPReceiveWorker* myRecieverWorker;
// The data
TArray<uint8> ReceivedData;
UDP Networking Wrapper (C)
CPP
Code:
#include "Game.h"
#include "UDPNetworkingWrapper.h"
UUDPNetworkingWrapper* UUDPNetworkingWrapper::Constructor()
{
return (UUDPNetworkingWrapper*)StaticConstructObject(UUDPNetworkingWrapper::StaticClass());
}
UUDPNetworkingWrapper* UUDPNetworkingWrapper::ConstructUDPWrapper(const FString& Description,
const FString& SenderSocketName, const FString& TheIP, const int32 ThePort, const int32 BufferSize,
const bool AllowBroadcast, const bool Bound, const bool Reusable, const bool Blocking)
{
UUDPNetworkingWrapper* wrapper = Constructor();
wrapper->SocketSubsystem = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM);
FIPv4Address::Parse(TheIP, wrapper->RemoteAdress);
wrapper->RemoteEndPoint = FIPv4Endpoint(wrapper->RemoteAdress, ThePort);
// First set our socket null
wrapper->SenderSocket = nullptr;
if (wrapper->SocketSubsystem != nullptr) // If socket subsytem is good
{
wrapper->SenderSocket = wrapper->SocketSubsystem->CreateSocket(NAME_DGram,
*Description, true);
if (wrapper->SenderSocket != nullptr) // Is our socket created
{
// Setup the socket
bool Error = !wrapper->SenderSocket->SetNonBlocking(!Blocking) ||
!wrapper->SenderSocket->SetReuseAddr(Reusable) ||
!wrapper->SenderSocket->SetBroadcast(AllowBroadcast) ||
!wrapper->SenderSocket->SetRecvErr();
if (!Error)
{
if (Bound)
{
Error = !wrapper->SenderSocket->Bind(*wrapper->RemoteEndPoint.ToInternetAddr());
}
}
if (!Error)
{
int32 OutNewSize;
wrapper->SenderSocket->SetReceiveBufferSize(BufferSize, OutNewSize);
wrapper->SenderSocket->SetSendBufferSize(BufferSize, OutNewSize);
}
if (Error)
{
GLog->Logf(TEXT("FUdpSocketBuilder: Failed to create the socket %s as configured"), *Description);
wrapper->SocketSubsystem->DestroySocket(wrapper->SenderSocket);
wrapper->SenderSocket = nullptr;
}
}
}
return wrapper;
}
bool UUDPNetworkingWrapper::sendMessage(FString Message)
{
if (!SenderSocket) return false;
int32 BytesSent;
FTimespan waitTime = FTimespan(10);
TCHAR *serializedChar = Message.GetCharArray().GetData();
int32 size = FCString::Strlen(serializedChar);
int32 sent = 0;
// Send to
//myRecieverWorker = UDPReceiveWorker::JoyInit(SenderSocket, waitTime);
bool success = SenderSocket->SendTo((uint8*)TCHAR_TO_UTF8(serializedChar), size,
BytesSent, *RemoteEndPoint.ToInternetAddr());
if (success && BytesSent > 0) // Success
{
return true;
}
else
{
return false;
}
}
FString UUDPNetworkingWrapper::GrabWaitingMessage()
{
uint32 Size;
TSharedRef<FInternetAddr> Sender = SocketSubsystem->CreateInternetAddr();
while (SenderSocket->HasPendingData(Size))
{
int32 Read = 0;
ReceivedData.Init(FMath::Min(Size, 65507u));
SenderSocket->RecvFrom(ReceivedData.GetData(), ReceivedData.Num(), Read, *Sender);
}
return StringFromBinaryArray(ReceivedData);
}
bool UUDPNetworkingWrapper::anyMessages()
{
uint32 Size;
if (SenderSocket->HasPendingData(Size))
{
return true;
}
return false;
}
FString UUDPNetworkingWrapper::StringFromBinaryArray(const TArray<uint8>& BinaryArray)
{
//Create a string from a byte array!
const std::string cstr(reinterpret_cast<const char*>(BinaryArray.GetData()), BinaryArray.Num());
//FString can take in the c_str() of a std::string
return FString(cstr.c_str());
}
void UUDPNetworkingWrapper::UDPDestructor()
{
SocketSubsystem->DestroySocket(SenderSocket);
SenderSocket = nullptr;
SocketSubsystem = nullptr;
//myRecieverWorker = nullptr;
}
Then in the editor just create an object with the UDPNetworkingWrapper class. Make a call to the constructor passing all the info. Then with the object you can send out messages and make a check for any new messages. Below is my test setup in a pawn blueprint.
No worries glad this helped some people. I honestly spent forever looking as well. As far as the UDPReceiveWorker.h goes, that was something I was working on as to get the handling of receiving messages in another thread so the game does wait for a response. I never could get it to work properly.
UDPReceiveWorker.h
Code:
#include "Networking.h"
#include "UDPNetworkingWrapper.h"
#include "NetworkingFunctionLibrary.h"
#include <string>
/**
* This class holds the multi threading data for receiving UDP packets
*/
class VIRTUALTABLETOP_API UDPReceiveWorker : public FRunnable
{
// Singleton instance, can access the thread at anytime.
static UDPReceiveWorker* Runnable;
// The data
TArray<uint8>* ReceivedDataRef;
/** Stop this thread? Uses Thread Safe Counter */
FThreadSafeCounter StopTaskCounter;
public:
UDPReceiveWorker(TArray<uint8>& ReceivedData, FSocket* InSocket, FTimespan& InWaitTime);
virtual ~UDPReceiveWorker();
// Begin FRunnable interface.
virtual bool Init();
virtual uint32 Run();
virtual void Stop();
// End FRunnable interface
void EnsureCompletion();
static UDPReceiveWorker* JoyInit(TArray<uint8>& ReceivedData,
FSocket* InSocket, FTimespan& InWaitTime);
/** Shuts down the thread. Static so it can easily be called from outside the thread context */
static void Shutdown();
private:
// Holds the network socket.
FSocket* Socket;
// Holds a pointer to the socket sub-system.
ISocketSubsystem* SocketSubsystem;
// Holds a flag indicating that the thread is stopping.
bool Stopping;
// Holds the thread object.
FRunnableThread* Thread;
// Holds the amount of time to wait for inbound packets.
FTimespan WaitTime;
};
UDPReceiverWorker.cpp
Code:
include "VirtualTabletop.h"
#include "UDPReceiveWorker.h"
UDPReceiveWorker* UDPReceiveWorker::Runnable = NULL;
UDPReceiveWorker::UDPReceiveWorker(TArray<uint8>& ReceivedData, FSocket* InSocket, FTimespan& InWaitTime)
: Socket(InSocket)
, StopTaskCounter(0)
, WaitTime(InWaitTime)
, Stopping(false)
{
const bool bAutoDeleteSelf = false;
const bool bAutoDeleteRunnable = false;
// Link the data
ReceivedDataRef = &ReceivedData;
SocketSubsystem = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM);
Thread = FRunnableThread::Create(this, TEXT("UDPReceiveWorker"), 0,
TPri_BelowNormal); //windows default = 8mb for thread, could specify more
}
UDPReceiveWorker::~UDPReceiveWorker()
{
delete Thread;
Thread = NULL;
SocketSubsystem = nullptr;
Socket = nullptr;
ReceivedDataRef = nullptr;
//myNetworkingWrapper = nullptr;
}
// Init
bool UDPReceiveWorker::Init()
{
return true;
}
//Run
uint32 UDPReceiveWorker::Run()
{
TSharedRef<FInternetAddr> Sender = SocketSubsystem->CreateInternetAddr();
while (!Stopping)
{
if (!Socket->Wait(ESocketWaitConditions::WaitForRead, WaitTime))
{
continue;
}
uint32 Size;
while (Socket->HasPendingData(Size))
{
int32 Read = 0;
Socket->RecvFrom(ReceivedDataRef->GetData(), ReceivedDataRef->Num(), Read,
*Sender);
}
}
return 0;
}
// Stop
void UDPReceiveWorker::Stop()
{
StopTaskCounter.Increment();
}
UDPReceiveWorker* UDPReceiveWorker::JoyInit(TArray<uint8>& ReceivedData, FSocket* InSocket,
FTimespan& InWaitTime)
{
//Create new instance of thread if it does not exist
// and the platform supports multi threading!
if (!Runnable && FPlatformProcess::SupportsMultithreading())
{
Runnable = new UDPReceiveWorker(ReceivedData, InSocket, InWaitTime);
}
return Runnable;
}
void UDPReceiveWorker::EnsureCompletion()
{
Stop();
Thread->WaitForCompletion();
}
void UDPReceiveWorker::Shutdown()
{
if (Runnable)
{
Runnable->EnsureCompletion();
delete Runnable;
Runnable = NULL;
}
}
//
//bool UDPReceiveWorker::IsThreadFinished()
//{
// if (Runnable) return Runnable->IsFinished();
// return true;
//}
#include "Networking.h"
//AYourClass definition
public:
FSocket* ListenerSocket;
FSocket* ConnectionSocket;
FIPv4Endpoint RemoteAddressForConnection;
bool StartTCPReceiver(
const FString& YourChosenSocketName,
const FString& TheIP,
const int32 ThePort
);
FSocket* CreateTCPConnectionListener(
const FString& YourChosenSocketName,
const FString& TheIP,
const int32 ThePort,
const int32 ReceiveBufferSize = 2*1024*1024
);
//Timer functions, could be threads
void TCPConnectionListener(); //can thread this eventually
void TCPSocketListener(); //can thread this eventually
//Format String IP4 to number array
bool FormatIP4ToNumber(const FString& TheIP, uint8 (&Out)[4]);
//Rama's StringFromBinaryArray
FString StringFromBinaryArray(const TArray<uint8>& BinaryArray);
.cpp
void AYourClass::Laaaaaauuuunch()
{
//IP = 127.0.0.1, Port = 8890 for my Python test case
if( ! StartTCPReceiver("RamaSocketListener", "127.0.0.1", 8890))
{
//UE_LOG "TCP Socket Listener Created!"
return;
}
//UE_LOG "TCP Socket Listener Created! Yay!"
}
//Rama's Start TCP Receiver
bool AYourClass::StartTCPReceiver(
const FString& YourChosenSocketName,
const FString& TheIP,
const int32 ThePort
){
//Rama's CreateTCPConnectionListener
ListenerSocket = CreateTCPConnectionListener(YourChosenSocketName,TheIP, ThePort);
//Not created?
if(!ListenerSocket)
{
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, FString::Printf(TEXT("StartTCPReceiver>> Listen socket could not be created! ~> %s %d"), *TheIP, ThePort));
return false;
}
//Start the Listener! //thread this eventually
GetWorldTimerManager().SetTimer(this,
&AYourClass::TCPConnectionListener, 0.01, true);
return true;
}
//Format IP String as Number Parts
bool AYourClass::FormatIP4ToNumber(const FString& TheIP, uint8 (&Out)[4])
{
//IP Formatting
TheIP.Replace( TEXT(" "), TEXT("") );
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// IP 4 Parts
//String Parts
TArray<FString> Parts;
TheIP.ParseIntoArray( &Parts, TEXT("."), true );
if ( Parts.Num() != 4 )
return false;
//String to Number Parts
for ( int32 i = 0; i < 4; ++i )
{
Out[i] = FCString::Atoi( *Parts[i] );
}
return true;
}
//Rama's Create TCP Connection Listener
FSocket* AYourClass::CreateTCPConnectionListener(const FString& YourChosenSocketName,const FString& TheIP, const int32 ThePort,const int32 ReceiveBufferSize)
{
uint8 IP4Nums[4];
if( ! FormatIP4ToNumber(TheIP, IP4Nums))
{
VShow("Invalid IP! Expecting 4 parts separated by .");
return false;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//Create Socket
FIPv4Endpoint Endpoint(FIPv4Address(IP4Nums[0], IP4Nums[1], IP4Nums[2], IP4Nums[3]), ThePort);
FSocket* ListenSocket = FTcpSocketBuilder(*YourChosenSocketName)
.AsReusable()
.BoundToEndpoint(Endpoint)
.Listening(8);
//Set Buffer Size
int32 NewSize = 0;
ListenSocket->SetReceiveBufferSize(ReceiveBufferSize, NewSize);
//Done!
return ListenSocket;
}
//Rama's TCP Connection Listener
void AYourClass::TCPConnectionListener()
{
//~~~~~~~~~~~~~
if(!ListenerSocket) return;
//~~~~~~~~~~~~~
//Remote address
TSharedRef<FInternetAddr> RemoteAddress = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateInternetAddr();
bool Pending;
// handle incoming connections
if (ListenerSocket->HasPendingConnection(Pending) && Pending)
{
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//Already have a Connection? destroy previous
if(ConnectionSocket)
{
ConnectionSocket->Close();
ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->DestroySocket(ConnectionSocket);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//New Connection receive!
ConnectionSocket = ListenerSocket->Accept(*RemoteAddress, TEXT("RamaTCP Received Socket Connection"));
if (ConnectionSocket != NULL)
{
//Global cache of current Remote Address
RemoteAddressForConnection = FIPv4Endpoint(RemoteAddress);
//UE_LOG "Accepted Connection! WOOOHOOOO!!!";
//can thread this too
GetWorldTimerManager().SetTimer(this,
&AYourClass::TCPSocketListener, 0.01, true);
}
}
}
//Rama's String From Binary Array
//This function requires
// #include <string>
FString AYourClass::StringFromBinaryArray(const TArray<uint8>& BinaryArray)
{
//Create a string from a byte array!
std::string cstr( reinterpret_cast<const char*>(BinaryArray.GetData()), BinaryArray.Num() );
return FString(cstr.c_str());
}
//Rama's TCP Socket Listener
void AYourClass::TCPSocketListener()
{
//~~~~~~~~~~~~~
if(!ConnectionSocket) return;
//~~~~~~~~~~~~~
//Binary Array!
TArray<uint8> ReceivedData;
uint32 Size;
while (ConnectionSocket->HasPendingData(Size))
{
ReceivedData.Init(FMath::Min(Size, 65507u));
int32 Read = 0;
ConnectionSocket->Recv(ReceivedData.GetData(), ReceivedData.Num(), Read);
//GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, FString::Printf(TEXT("Data Read! %d"), ReceivedData.Num()));
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
if(ReceivedData.Num() <= 0)
{
//No Data Received
return;
}
VShow("Total Data read!", ReceivedData.Num() );
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, FString::Printf(TEXT("Data Bytes Read ~> %d"), ReceivedData.Num()));
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Rama's String From Binary Array
const FString ReceivedUE4String = StringFromBinaryArray(ReceivedData);
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
VShow("As String!!!!! ~>",ReceivedUE4String);
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, FString::Printf(TEXT("As String Data ~> %s"), *ReceivedUE4String));
Core Research I Am Sharing with You
The hardest part of this whole process for me was converting the received binary data into UE4's FString format!
I wrote this function to do it, which adds the null terminator and everything, and uses the strong backbone of std::string!
//Rama's String From Binary Array
//This function requires
// #include <string>
FString AYourClass::StringFromBinaryArray(const TArray<uint8>& BinaryArray)
{
//Create a string from a byte array!
const std::string cstr( reinterpret_cast<const char*>(BinaryArray.GetData()), BinaryArray.Num() );
//FString can take in the c_str() of a std::string
return FString(cstr.c_str());
}
How It Works
The first socket listens on the port and IP supplied, and if a connection is received on this port, then the actual ListenerSocket is created.
The ListenerSocket is run in very short looping timer of 0.01 seconds.
지금 계속 해보고 있긴한데, 오류가 떠서 힘드네요 ㅠ VShow는 클라이언트 메시지여서 일단 주석처리했구요, 문제는 GetWorldTimerManager().SetTimer에서 error C2228: left of '.SetTimer' must have class/struct/union error C3861: 'GetWorldTimerManager': identifier not found
2015.12.24. 11:09답글ㅋ 저도 이거 해보고 있는데. 컴파일까지 됐다고 뜨는데... 그 이후로 뭘해야 하는지 모르겠어요. Player Controller 라서 Actor나 캐릭터 밑에 두고 실행하면 접속이 안된다는 메세지라도 뜨는거 아닌가요? 아무리 플레이를 눌러봐도 아무런 현상이 나타나지 않네요...
listen server => 'open MapName?Listen' 또는 'udk MapName?Listen'으로 시작합니다. dedicated server => 'udk server MapName'으로 시작합니다. client => 'open [ipaddress]' 또는 'udk [ipaddress]'으로 시작합니다.
network 기능이 내장되기는 했지만, 제작자가 s!crip로 짜서 연결해 줘야 하는 부분들이 제법 됩니다.^^;;;;;
그리고, UDK의 iOS용 app을 만들 경우, network 기능 제약이 심합니다.
- IPv6 사용 불가. ^^;;; - UDP 사용 불가. -_-;;;;;; - 특정 상태에서만 listen socket 생성 가능. -_-+
iOS용 UDK로 FPS/TPS가 아닌 network game을 만들려면, full license의 unreal engine을 구입해서 core의 objective-C 부분을 수정하시던가, 아니면, 우회적인 방법을 사용해서, 기능을 확장하는 수 밖에 없습니다. -_-+
저도..소캣프로그래밍은 안해봤지만.. mySocket->RecvFrom(recvd, size, sent, *serverAddress); 이부분에서.. ServerAddress 로 부터 값을 받아오는건가요 ? 음.. 루프백 이니까 르럴수도 있겠네요..
근데 .. 함수가 비긴 플레이인데 플레이 시작하면 딱 한번 실행되고 끝이잖아요? Recv 는 뭔가 기다려야할것같은 함수인데 샌드를 보내고 리시브를 받기까지 시간이 약간 걸릴텐데 그 과정을 무시하고 지나간게 아닐까요? Recv는 생각해봐두 쓰레드 만들어서 루프를 계속 돌면서 받아와야할것같은데 ㅎㅎ
2015.11.23. 10:11답글테디곰답변 감사합니다. RecvFrom은 제가 scanf 같이 기다려서 값을 받아오는 거라고 생각했는데, 윈속에선 그런식으로 동작했거든요. 멀티쓰레딩은 아직 해본적이 없어서 하려면 조금 복잡해지겠네요 ㅠㅠ 그리고 UDP는 TCP와 다르게 listen과 accept 과정이 없어요. ISocketSubsystem으로 소켓을 생성하는 부분에서는 첫번째 매개변수를 STREAM으로 주면 TCP 소켓이 만들어지고 DGram으로 주면 UDP가 만들어진다고 합니다. 뒤에 false는 true로 줘봤는데 무슨 차이가 있는건지는 모르겠네요. 아무리 검색해봐도 설명해주는 곳이 없어서 뭐하는 건지 알 수가 없어요. 라이브러리에 "overrides any platform specific protocol with UDP instead"라고 한 줄 적혀있네요.
Sets a timer to call the given native function at a set interval. If a timer is already set for this delegate, it will update the current timer to the new parameters and reset its elapsed time to 0.
Parameters
ParameterDescription
InOutHandle
Handle to identify this timer. If it is invalid when passed in it will be made into a valid handle.
InObj
Object to call the timer function on.
InTimerMethod
Method to call when timer fires.
InRate
The amount of time between set and firing. If <= 0.f, clears existing timers.
InbLoop
true to keep firing at Rate intervals, false to fire only once.
InFirstDelay
The time for the first iteration of a looping timer. If < 0.f inRate will be used.
Example Code
MyCharacter.cpp at line 97:
void AMyCharacter::ToggleTimer()
{
UWorld* World = GetWorld();
if( World )
{
// If the timer has expired or does not exist, start it
if( ( SampleTimerHandle.IsValid() == false ) || ( bTimerExpired) )
{
World->GetTimerManager().SetTimer(SampleTimerHandle, this, &AMyCharacter::SampleTimerExpired, 10.0f);
bTimerExpired = false;
}
else
{
if( World->GetTimerManager().IsTimerPaused(SampleTimerHandle) == true )
{
World->GetTimerManager().UnPauseTimer(SampleTimerHandle);
}
else
{
World->GetTimerManager().PauseTimer(SampleTimerHandle);
}
}
}
}
void AMyCharacter::SampleTimerExpired()
{
bTimerExpired = true;
}
MyCharacter.cpp at line 442:
if (SampleTimerHandle.IsValid() == true)
{
UWorld* World = GetWorld();
float TimerTime = 0.0f;
if (World != nullptr)
{
if (bTimerExpired == true)
{
TheHud->TimerText = FString::Printf(TEXT("Timer Has Expired"));
TheHud->TimerTextColor = FColor::Red;
}
else
{
if (World->GetTimerManager().IsTimerPaused(SampleTimerHandle) == true)
{
TheHud->TimerText = FString::Printf(TEXT("Current Time is %f - Timer Is Paused"), World->GetTimerManager().GetTimerRemaining(SampleTimerHandle));
TheHud->TimerTextColor = FColor::Yellow;
}
else
{
TheHud->TimerText = FString::Printf(TEXT("Current Time is %f"), World->GetTimerManager().GetTimerRemaining(SampleTimerHandle));
TheHud->TimerTextColor = FColor::Blue;
}
}
}
}
else
{
TheHud->TimerText = TEXT("Timer Not Started");
TheHud->TimerTextColor = FColor::Blue;
}
We’ve been quietly working on our new game here at OSnap! Games and one of the features our new game will include is a lobby system where all players can chat among one another. The UE4 dedicated server is great for a game server but it’s overkill for something as simple as a chat server so we set out to get UE4 connecting to a third party socket server.
Now that your address is ready to go you simply have to tell UE4 to start the connection.
bool connected = Socket->Connect(*addr);
If connected is true you've successfully connected to your socket server!
Sending a Message
Alright, so you're connected but how do you send messages to it? A lot of that is up to you. Depending on your server there are different ways to serialize and send a message. The important part though is how does UE4 handle it.
First things first, we must prepare the message to be sent.
What's going on in the above? We're sending a message to a socket server with the type "loadPlayer" and a single parameter of 1. What is happening in UE4 here is we're taking an FString, and turning it into a TCHAR*.
Finally, with the message formatted we can send it to our server!
Sending data in UE4 requires it to be a uint8* and again, Epic provides the tools to get from TCHAR* to UTF8 which can be sent as a uint8*.
As far as reading data back from your socket server the key is in the functions HasPendingData and Recv of the FSocket class.
Dependency in Build.cs
One final thing to take note of. Inside your project's Build.cs file you'll need to add the "Sockets" package as a dependency in PublicDependencyModuleNames.
PublicDependencyModuleNames.AddRange( new string[] { "Core", "CoreUObject", "Engine", "Sockets" } );
FUdpSocketReceiver의 경우 FRunnable을 상속받았기에, 알아서 데이터를 척척 받아줍니다.
다만 TCP로 구현하신다면, 루프를 돌리지 마시고 쓰레드로 구현하시는게 좋을 것 같습니다.
답변 감사합니다!! 우선 저는 TCP로 다른 만들어져있는 서버에 IP/port로 접근하는 클라이언트를 만드려고 합니다. 저 링크 저도 보고 따라서 해봤지만 빌드가 막혀서 헤더도 넣어보고 Actor에도 해봤다가 Pawn에도 해봤다가 삽질 중입니다 ㅜㅜ 위 TCP링크의 코드를 어디에 코딩 해야 할지도 정확히 모르겠네요 ㅜㅜ 클래스 만들떄 어떤 부모 클래스를 받아서 사용해야 할까요 ..? 혹시 다른 샘플 코드를 갖고 계신다면 공유 부탁드립니다 ㅜㅜ
2016.08.18. 11:57답글ihopi네 감사합니다! #include "Networking.h" 는 넣었고 //Start the Listener! //thread this eventually GetWorldTimerManager().SetTimer(this, &AGameModeTCPGameMode::TCPConnectionListener, 0.01, true); 에서 막혀있습니다. 액터로 만들어서 해봐야겠습니다. 감사합니다!
2016.08.18. 15:39답글송쩡이GetWorldTimerManager 는 엔진의 Actor.h에 기본으로 정의 되어있습니다. 혹시 액터를 상속받지 않으신게 아닌지? 그렇다면... GetWorld()->GetTimerManager().SetTimer(...) 이런식으로 접근해 보세요.
The core of my method is that your data structure must have its own serialization function defined, here is the code for a BP exposed serializable data structure in UE4.
#pragma once
#include "AnyCustomData.generated.h"
USTRUCT(BlueprintType)
struct FAnyCustomData
{
GENERATED_USTRUCT_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Joy Color")
FString Name = "Victory!";
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Joy Color")
int32 Count = 1;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Joy Color")
float Scale = 1.f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Joy Color")
FLinearColor Color = FLinearColor::Red;
FAnyCustomData()
{}
};
FORCEINLINE FArchive& operator<<(FArchive &Ar, FAnyCustomData& TheStruct )
{
Ar << TheStruct.Name;
Ar << TheStruct.Count;
Ar << TheStruct.Scale;
Ar << TheStruct.Color;
return Ar;
}
As you can see am using a C++ operator overload of operator<< to tell UE4's FArchive how to serialize my data structure.
Because the data types are simple I can rely on UE4's existing serialization overloads for each simple data type.
As you can see I am using my custom USTRUCT serialization function to send any data I want from one instance of UE4 to another, and straight into Blueprints!