852 lines
30 KiB
C++
852 lines
30 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "CoreTypes.h"
|
|
|
|
#if PLATFORM_HAS_BSD_SOCKETS
|
|
|
|
#include "BSDSocketTypesPrivate.h"
|
|
#include "EventLoop/IEventLoop.h"
|
|
#include "EventLoop/BSDSocket/EventLoopIOManagerBSDSocketSelect.h"
|
|
#include "TestHarness.h"
|
|
|
|
#include "SocketSubsystem.h"
|
|
|
|
namespace UE::EventLoop
|
|
{
|
|
struct FBSDSocketFuncs
|
|
{
|
|
static SOCKET Open(int32 Family, int32 Type, int32 Protocol)
|
|
{
|
|
return socket(Family, Type, Protocol);
|
|
}
|
|
|
|
static void Close(SOCKET Socket)
|
|
{
|
|
closesocket(Socket);
|
|
}
|
|
|
|
static bool SetSendBufferSize(SOCKET Socket, int32 Size)
|
|
{
|
|
SOCKLEN SizeSize = sizeof(int32);
|
|
bool bOk = setsockopt(Socket, SOL_SOCKET, SO_SNDBUF, (char*)&Size, sizeof(int32)) == 0;
|
|
|
|
// Read the value back in case the size was modified
|
|
int32 NewSize;
|
|
getsockopt(Socket, SOL_SOCKET, SO_SNDBUF, (char*)&NewSize, &SizeSize);
|
|
|
|
return bOk && Size == NewSize;
|
|
}
|
|
|
|
static bool SetRecvBufferSize(SOCKET Socket, int32 Size)
|
|
{
|
|
SOCKLEN SizeSize = sizeof(int32);
|
|
bool bOk = setsockopt(Socket, SOL_SOCKET, SO_RCVBUF, (char*)&Size, sizeof(int32)) == 0;
|
|
|
|
// Read the value back in case the size was modified
|
|
int32 NewSize;
|
|
getsockopt(Socket, SOL_SOCKET, SO_RCVBUF, (char*)&NewSize, &SizeSize);
|
|
|
|
return bOk && Size == NewSize;
|
|
}
|
|
|
|
static bool SetNonBlocking(SOCKET Socket, bool bIsNonBlocking)
|
|
{
|
|
#if PLATFORM_HAS_BSD_SOCKET_FEATURE_WINSOCKETS
|
|
u_long Value = bIsNonBlocking ? true : false;
|
|
return ioctlsocket(Socket, FIONBIO, &Value) == 0;
|
|
#else
|
|
int Flags = fcntl(Socket, F_GETFL, 0);
|
|
//Set the flag or clear it, without destroying the other flags.
|
|
Flags = bIsNonBlocking ? Flags | O_NONBLOCK : Flags ^ (Flags & O_NONBLOCK);
|
|
int err = fcntl(Socket, F_SETFL, Flags);
|
|
return (err == 0 ? true : false);
|
|
#endif
|
|
}
|
|
|
|
static bool SetReusePort(SOCKET Socket, bool bIsReusePort)
|
|
{
|
|
int32 Resuse = bIsReusePort ? true : false;
|
|
bool bOk = setsockopt(Socket, SOL_SOCKET, SO_BROADCAST, (char*)&Resuse, sizeof(int32)) == 0;
|
|
bOk &= setsockopt(Socket, SOL_SOCKET, SO_REUSEADDR, (char*)&Resuse, sizeof(int32)) == 0;
|
|
return bOk;
|
|
}
|
|
|
|
static bool BindLoopback(SOCKET Socket)
|
|
{
|
|
sockaddr_in Addr;
|
|
FMemory::Memzero(&Addr, sizeof(Addr));
|
|
Addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
|
Addr.sin_family = AF_INET;
|
|
|
|
return Bind(Socket, Addr);
|
|
}
|
|
|
|
static bool Bind(SOCKET Socket, const sockaddr_in& Addr)
|
|
{
|
|
return bind(Socket, (const sockaddr*)&Addr, sizeof(sockaddr_in)) == 0;
|
|
}
|
|
|
|
static bool Listen(SOCKET Socket, int32 MaxBacklog)
|
|
{
|
|
return listen(Socket, MaxBacklog) == 0;
|
|
}
|
|
|
|
static bool GetAddress(SOCKET Socket, sockaddr_in& OutAddr)
|
|
{
|
|
SOCKLEN Size = sizeof(sockaddr_in);
|
|
|
|
// Figure out what ip/port we are bound to
|
|
return getsockname(Socket, (sockaddr*)&OutAddr, &Size) == 0;
|
|
}
|
|
|
|
static bool Connect(SOCKET Socket, const sockaddr_in& Addr)
|
|
{
|
|
SOCKLEN Size = sizeof(sockaddr_in);
|
|
int32 Return = connect(Socket, (const sockaddr*)&Addr, Size);
|
|
|
|
ISocketSubsystem* SocketSubsystem = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM);
|
|
check(SocketSubsystem);
|
|
ESocketErrors Error = SocketSubsystem->TranslateErrorCode(Return);
|
|
|
|
// EWOULDBLOCK is not an error, and EINPROGRESS is fine on initial connection as it may still be creating for nonblocking sockets
|
|
return ((Error == SE_NO_ERROR) || (Error == SE_EWOULDBLOCK) || (Error == SE_EINPROGRESS));
|
|
}
|
|
|
|
static SOCKET Accept(SOCKET Socket)
|
|
{
|
|
return accept(Socket, NULL, NULL);
|
|
}
|
|
|
|
static bool Send(SOCKET Socket, const uint8* Data, int32 Count, int32& BytesSent)
|
|
{
|
|
BytesSent = send(Socket,(const char*)Data,Count,0);
|
|
return BytesSent >= 0;
|
|
}
|
|
|
|
static bool Recv(SOCKET Socket, uint8* Data, int32 BufferSize, int32& BytesRead)
|
|
{
|
|
int SocketType = 0;
|
|
SOCKLEN SocketTypeLength = sizeof(int32);
|
|
if (getsockopt(Socket, SOL_SOCKET, SO_TYPE, (char*)&SocketType, &SocketTypeLength) == SOCKET_ERROR)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool bSuccess = false;
|
|
const bool bStreamSocket = (SocketType == SOCK_STREAM);
|
|
BytesRead = recv(Socket, (char*)Data, BufferSize, 0);
|
|
|
|
if (BytesRead >= 0)
|
|
{
|
|
// For Streaming sockets, 0 indicates a graceful failure
|
|
bSuccess = !bStreamSocket || (BytesRead > 0);
|
|
}
|
|
else
|
|
{
|
|
// For Streaming sockets, don't treat SE_EWOULDBLOCK as an error
|
|
ISocketSubsystem* SocketSubsystem = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM);
|
|
check(SocketSubsystem);
|
|
bSuccess = bStreamSocket && (SocketSubsystem->TranslateErrorCode(BytesRead) == SE_EWOULDBLOCK);
|
|
BytesRead = 0;
|
|
}
|
|
|
|
return bSuccess;
|
|
}
|
|
|
|
static bool SendTo(SOCKET Socket, const uint8* Data, int32 Count, int32& BytesSent, const sockaddr_in& Addr)
|
|
{
|
|
SOCKLEN Size = sizeof(sockaddr_in);
|
|
// Write the data and see how much was written
|
|
BytesSent = sendto(Socket, (const char*)Data, Count, 0, (const sockaddr*)&(Addr), Size);
|
|
return BytesSent >= 0;
|
|
}
|
|
|
|
static bool RecvFrom(SOCKET Socket, uint8* Data, int32 BufferSize, int32& BytesRead, sockaddr_in& Addr)
|
|
{
|
|
int SocketType = 0;
|
|
SOCKLEN SocketTypeLength = sizeof(int32);
|
|
if (getsockopt(Socket, SOL_SOCKET, SO_TYPE, (char*)&SocketType, &SocketTypeLength) == SOCKET_ERROR)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool bSuccess = false;
|
|
const bool bStreamSocket = (SocketType == SOCK_STREAM);
|
|
SOCKLEN Size = sizeof(sockaddr_in);
|
|
|
|
// Read into the buffer and set the source address
|
|
BytesRead = recvfrom(Socket, (char*)Data, BufferSize, 0, (sockaddr*)&Addr, &Size);
|
|
|
|
if (BytesRead >= 0)
|
|
{
|
|
// For Streaming sockets, 0 indicates a graceful failure
|
|
bSuccess = !bStreamSocket || (BytesRead > 0);
|
|
}
|
|
else
|
|
{
|
|
ISocketSubsystem* SocketSubsystem = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM);
|
|
check(SocketSubsystem);
|
|
// For Streaming sockets, don't treat SE_EWOULDBLOCK as an error
|
|
bSuccess = bStreamSocket && (SocketSubsystem->TranslateErrorCode(BytesRead) == SE_EWOULDBLOCK);
|
|
BytesRead = 0;
|
|
}
|
|
|
|
return bSuccess;
|
|
}
|
|
};
|
|
|
|
// Todo - remove eventloop parameter from IOManager.
|
|
class FMockEventLoop : public IEventLoop
|
|
{
|
|
public:
|
|
FMockEventLoop() = default;
|
|
virtual ~FMockEventLoop() = default;
|
|
virtual bool Init() override { return false; }
|
|
virtual void RequestShutdown(FOnShutdownComplete&& OnShutdownComplete) override {}
|
|
virtual FTimerHandle SetTimer(FTimerCallback&& Callback, FTimespan InRate, bool InbRepeat, TOptional<FTimespan> InFirstDelay) override { return FTimerHandle(); }
|
|
virtual void ClearTimer(FTimerHandle& InHandle, FOnTimerCleared&& OnTimerCleared) override {}
|
|
virtual void PostAsyncTask(FAsyncTask&& Task) override {}
|
|
virtual void Run() override {}
|
|
virtual bool RunOnce(FTimespan WaitTime) override { return false; }
|
|
virtual FTimespan GetLoopTime() const override { return FTimespan(); }
|
|
};
|
|
|
|
TEST_CASE("IOManagerBSDSocketSelect", "[Online][EventLoop][IOManager][Smoke]")
|
|
{
|
|
FMockEventLoop MockEventLoop;
|
|
FIOManagerBSDSocketSelect IOManager(MockEventLoop, FIOManagerBSDSocketSelect::FParams());
|
|
|
|
REQUIRE(IOManager.Init());
|
|
|
|
ISocketSubsystem* SocketSubsystem = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM);
|
|
check(SocketSubsystem);
|
|
|
|
SECTION("Notify tests")
|
|
{
|
|
double StartTime = 0;
|
|
FTimespan Duration;
|
|
|
|
// 1. Waiting while no events are received will wait for at least the requested duration.
|
|
StartTime = FPlatformTime::Seconds();
|
|
IOManager.Poll(FTimespan::FromMilliseconds(50));
|
|
Duration = FTimespan::FromSeconds(FPlatformTime::Seconds() - StartTime);
|
|
CHECK(Duration > FTimespan::FromMilliseconds(50));
|
|
|
|
// 2. Notifying the IO manager will wake the loop up early.
|
|
StartTime = FPlatformTime::Seconds();
|
|
IOManager.Notify();
|
|
IOManager.Poll(FTimespan::FromMilliseconds(50));
|
|
Duration = FTimespan::FromSeconds(FPlatformTime::Seconds() - StartTime);
|
|
CHECK(Duration < FTimespan::FromMilliseconds(1));
|
|
}
|
|
|
|
SECTION("Single socket tests")
|
|
{
|
|
// Size of buffer used for sending test data.
|
|
constexpr uint64 WriteBufferSize = 128;
|
|
TArray<uint8> WriteBuffer;
|
|
WriteBuffer.AddDefaulted(WriteBufferSize);
|
|
|
|
// Size of buffer used for receiving test data.
|
|
constexpr uint64 ReadBufferSize = 128;
|
|
TArray<uint8> ReadBuffer;
|
|
ReadBuffer.AddDefaulted(ReadBufferSize);
|
|
|
|
// Socket buffer sizes for send / recv
|
|
constexpr int32 SocketSendBufferSize = 64;
|
|
constexpr int32 SocketReceiveBufferSize = 64;
|
|
|
|
// Create listen socket.
|
|
SOCKET ListenSocket = FBSDSocketFuncs::Open(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
|
REQUIRE(ListenSocket != INVALID_SOCKET);
|
|
REQUIRE(FBSDSocketFuncs::SetSendBufferSize(ListenSocket, SocketSendBufferSize));
|
|
REQUIRE(FBSDSocketFuncs::SetRecvBufferSize(ListenSocket, SocketReceiveBufferSize));
|
|
REQUIRE(FBSDSocketFuncs::SetNonBlocking(ListenSocket, true));
|
|
REQUIRE(FBSDSocketFuncs::BindLoopback(ListenSocket));
|
|
REQUIRE(FBSDSocketFuncs::Listen(ListenSocket, 1));
|
|
sockaddr_in HostAddr;
|
|
FMemory::Memzero(&HostAddr, sizeof(HostAddr));
|
|
REQUIRE(FBSDSocketFuncs::GetAddress(ListenSocket, HostAddr));
|
|
|
|
// Create remote socket.
|
|
SOCKET RemoteSocket1 = FBSDSocketFuncs::Open(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
|
REQUIRE(RemoteSocket1 != INVALID_SOCKET);
|
|
REQUIRE(FBSDSocketFuncs::SetSendBufferSize(RemoteSocket1, SocketSendBufferSize));
|
|
REQUIRE(FBSDSocketFuncs::SetRecvBufferSize(RemoteSocket1, SocketReceiveBufferSize));
|
|
REQUIRE(FBSDSocketFuncs::SetNonBlocking(RemoteSocket1, true));
|
|
REQUIRE(FBSDSocketFuncs::Connect(RemoteSocket1, HostAddr));
|
|
|
|
// Create local socket.
|
|
ESocketErrors ErrorCode1 = SE_NO_ERROR;
|
|
SOCKET LocalSocket1 = INVALID_SOCKET;
|
|
do
|
|
{
|
|
LocalSocket1 = FBSDSocketFuncs::Accept(ListenSocket);
|
|
ErrorCode1 = SocketSubsystem->GetLastErrorCode();
|
|
}
|
|
while (ErrorCode1 == SE_EWOULDBLOCK);
|
|
REQUIRE(LocalSocket1 != INVALID_SOCKET);
|
|
|
|
SECTION("Single waiting socket tests")
|
|
{
|
|
SECTION("Wait on socket readable")
|
|
{
|
|
SOCKET TriggeredSocket = INVALID_SOCKET;
|
|
ESocketIoRequestStatus TriggeredStatus = ESocketIoRequestStatus::Ok;
|
|
EIOFlags TriggeredFlags = EIOFlags::None;
|
|
uint32 CallbackTriggerCount = 0;
|
|
|
|
FIORequestBSDSocket Request;
|
|
Request.Socket = LocalSocket1;
|
|
Request.Flags = EIOFlags::Read;
|
|
Request.Callback = [&](SOCKET Socket, ESocketIoRequestStatus Status, EIOFlags SignaledFlags){
|
|
TriggeredSocket = Socket;
|
|
TriggeredStatus = Status;
|
|
TriggeredFlags = SignaledFlags;
|
|
++CallbackTriggerCount;
|
|
};
|
|
|
|
FIORequestHandle RequestHandle = IOManager.GetIOAccess().CreateSocketIORequest(MoveTemp(Request));
|
|
CHECK(RequestHandle.IsValid());
|
|
|
|
// 1. No events triggered when no data has arrived at the socket.
|
|
IOManager.Poll(FTimespan::Zero());
|
|
CHECK(CallbackTriggerCount == 0);
|
|
|
|
// 2. Send data to the socket.
|
|
int32 BytesSent = 0;
|
|
CHECK(FBSDSocketFuncs::Send(RemoteSocket1, WriteBuffer.GetData(), 1, BytesSent));
|
|
CHECK(BytesSent == 1);
|
|
|
|
// 3. See that socket has a read event.
|
|
TriggeredFlags = EIOFlags::None;
|
|
IOManager.Poll(FTimespan::Zero());
|
|
CHECK(CallbackTriggerCount == 1);
|
|
CHECK(TriggeredSocket == LocalSocket1);
|
|
CHECK(TriggeredStatus == ESocketIoRequestStatus::Ok);
|
|
CHECK(TriggeredFlags == EIOFlags::Read);
|
|
|
|
// 4. See that socket still has a read event.
|
|
IOManager.Poll(FTimespan::Zero());
|
|
CHECK(CallbackTriggerCount == 2);
|
|
CHECK(TriggeredSocket == LocalSocket1);
|
|
CHECK(TriggeredStatus == ESocketIoRequestStatus::Ok);
|
|
CHECK(TriggeredFlags == EIOFlags::Read);
|
|
|
|
// 5. Read the waiting data from the socket.
|
|
int32 BytesRead = 0;
|
|
CHECK(FBSDSocketFuncs::Recv(LocalSocket1, ReadBuffer.GetData(), ReadBufferSize, BytesRead));
|
|
CHECK(BytesRead == 1);
|
|
|
|
// 6. See that socket no longer has a read event.
|
|
TriggeredFlags = EIOFlags::None;
|
|
IOManager.Poll(FTimespan::Zero());
|
|
CHECK(CallbackTriggerCount == 2);
|
|
}
|
|
|
|
SECTION("Wait on socket writeable")
|
|
{
|
|
SOCKET TriggeredSocket = INVALID_SOCKET;
|
|
ESocketIoRequestStatus TriggeredStatus = ESocketIoRequestStatus::Ok;
|
|
EIOFlags TriggeredFlags = EIOFlags::None;
|
|
uint32 CallbackTriggerCount = 0;
|
|
|
|
FIORequestBSDSocket Request;
|
|
Request.Socket = LocalSocket1;
|
|
Request.Flags = EIOFlags::Write;
|
|
Request.Callback = [&](SOCKET Socket, ESocketIoRequestStatus Status, EIOFlags SignaledFlags){
|
|
TriggeredSocket = Socket;
|
|
TriggeredStatus = Status;
|
|
TriggeredFlags = SignaledFlags;
|
|
++CallbackTriggerCount;
|
|
};
|
|
|
|
FIORequestHandle RequestHandle = IOManager.GetIOAccess().CreateSocketIORequest(MoveTemp(Request));
|
|
CHECK(RequestHandle.IsValid());
|
|
|
|
// 1. See that socket has a write event.
|
|
IOManager.Poll(FTimespan::Zero());
|
|
CHECK(CallbackTriggerCount == 1);
|
|
CHECK(TriggeredSocket == LocalSocket1);
|
|
CHECK(TriggeredStatus == ESocketIoRequestStatus::Ok);
|
|
CHECK(TriggeredFlags == EIOFlags::Write);
|
|
|
|
// 2. Write data to the socket until it returns an error indicating that it would block.
|
|
int32 BytesSent = 0;
|
|
int32 TotalBytesSent = 0;
|
|
while (FBSDSocketFuncs::Send(LocalSocket1, WriteBuffer.GetData(), WriteBufferSize, BytesSent));
|
|
|
|
// 3. See that socket no longer has a write event.
|
|
IOManager.Poll(FTimespan::Zero());
|
|
CHECK(CallbackTriggerCount == 1);
|
|
|
|
// 4. Read the data from the remote end of the connection.
|
|
int32 BytesRead = 0;
|
|
while (FBSDSocketFuncs::Recv(RemoteSocket1, ReadBuffer.GetData(), ReadBufferSize, BytesRead))
|
|
{
|
|
if (BytesRead == 0)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
// 5. See that socket now has a write event.
|
|
IOManager.Poll(FTimespan::FromMilliseconds(10));
|
|
CHECK(CallbackTriggerCount == 2);
|
|
CHECK(TriggeredSocket == LocalSocket1);
|
|
CHECK(TriggeredStatus == ESocketIoRequestStatus::Ok);
|
|
CHECK(TriggeredFlags == EIOFlags::Write);
|
|
}
|
|
|
|
SECTION("Receive both read and write events with one request")
|
|
{
|
|
SOCKET TriggeredSocket = INVALID_SOCKET;
|
|
ESocketIoRequestStatus TriggeredStatus = ESocketIoRequestStatus::Ok;
|
|
EIOFlags TriggeredFlags = EIOFlags::None;
|
|
uint32 CallbackTriggerCount = 0;
|
|
|
|
FIORequestBSDSocket Request;
|
|
Request.Socket = LocalSocket1;
|
|
Request.Flags = EIOFlags::Read | EIOFlags::Write;
|
|
Request.Callback = [&](SOCKET Socket, ESocketIoRequestStatus Status, EIOFlags SignaledFlags){
|
|
TriggeredSocket = Socket;
|
|
TriggeredStatus = Status;
|
|
TriggeredFlags = SignaledFlags;
|
|
++CallbackTriggerCount;
|
|
};
|
|
|
|
FIORequestHandle RequestHandle = IOManager.GetIOAccess().CreateSocketIORequest(MoveTemp(Request));
|
|
CHECK(RequestHandle.IsValid());
|
|
|
|
// 1. No events triggered when no data has arrived at the socket.
|
|
IOManager.Poll(FTimespan::Zero());
|
|
CHECK(CallbackTriggerCount == 1);
|
|
CHECK(TriggeredSocket == LocalSocket1);
|
|
CHECK(TriggeredStatus == ESocketIoRequestStatus::Ok);
|
|
CHECK(EnumHasAnyFlags(TriggeredFlags, EIOFlags::Write));
|
|
CHECK(!EnumHasAnyFlags(TriggeredFlags, EIOFlags::Read));
|
|
|
|
// 2. Send data to the socket.
|
|
int32 BytesSent = 0;
|
|
CHECK(FBSDSocketFuncs::Send(RemoteSocket1, WriteBuffer.GetData(), 1, BytesSent));
|
|
CHECK(BytesSent == 1);
|
|
|
|
// 3. See that socket has a read event.
|
|
IOManager.Poll(FTimespan::Zero());
|
|
CHECK(CallbackTriggerCount == 2);
|
|
CHECK(TriggeredSocket == LocalSocket1);
|
|
CHECK(TriggeredStatus == ESocketIoRequestStatus::Ok);
|
|
CHECK(EnumHasAnyFlags(TriggeredFlags, EIOFlags::Write));
|
|
CHECK(EnumHasAnyFlags(TriggeredFlags, EIOFlags::Read));
|
|
}
|
|
|
|
SECTION("Canceling a request stops signaling events")
|
|
{
|
|
SOCKET TriggeredSocket = INVALID_SOCKET;
|
|
ESocketIoRequestStatus TriggeredStatus = ESocketIoRequestStatus::Ok;
|
|
EIOFlags TriggeredFlags = EIOFlags::None;
|
|
uint32 CallbackTriggerCount = 0;
|
|
|
|
FIORequestBSDSocket Request;
|
|
Request.Socket = LocalSocket1;
|
|
Request.Flags = EIOFlags::Read;
|
|
Request.Callback = [&](SOCKET Socket, ESocketIoRequestStatus Status, EIOFlags SignaledFlags){
|
|
TriggeredSocket = Socket;
|
|
TriggeredStatus = Status;
|
|
TriggeredFlags = SignaledFlags;
|
|
++CallbackTriggerCount;
|
|
};
|
|
|
|
FIORequestHandle RequestHandle = IOManager.GetIOAccess().CreateSocketIORequest(MoveTemp(Request));
|
|
CHECK(RequestHandle.IsValid());
|
|
|
|
// 1. No events triggered when no data has arrived at the socket.
|
|
IOManager.Poll(FTimespan::Zero());
|
|
CHECK(CallbackTriggerCount == 0);
|
|
|
|
// 2. Send data to the socket.
|
|
int32 BytesSent = 0;
|
|
CHECK(FBSDSocketFuncs::Send(RemoteSocket1, WriteBuffer.GetData(), 1, BytesSent));
|
|
CHECK(BytesSent == 1);
|
|
|
|
// 3. See that socket has a read event.
|
|
IOManager.Poll(FTimespan::Zero());
|
|
CHECK(CallbackTriggerCount == 1);
|
|
CHECK(TriggeredSocket == LocalSocket1);
|
|
CHECK(TriggeredStatus == ESocketIoRequestStatus::Ok);
|
|
CHECK(TriggeredFlags == EIOFlags::Read);
|
|
|
|
// 4. Cancel the request.
|
|
IOManager.GetIOAccess().DestroyIORequest(RequestHandle);
|
|
|
|
// 5. See that event is not triggered.
|
|
IOManager.Poll(FTimespan::Zero());
|
|
CHECK(CallbackTriggerCount == 1);
|
|
}
|
|
|
|
SECTION("TCP socket remote end closing triggers writeable wakeup")
|
|
{
|
|
SOCKET TriggeredSocket = INVALID_SOCKET;
|
|
ESocketIoRequestStatus TriggeredStatus = ESocketIoRequestStatus::Ok;
|
|
EIOFlags TriggeredFlags = EIOFlags::None;
|
|
uint32 CallbackTriggerCount = 0;
|
|
|
|
FIORequestBSDSocket Request;
|
|
Request.Socket = LocalSocket1;
|
|
Request.Flags = EIOFlags::Write;
|
|
Request.Callback = [&](SOCKET Socket, ESocketIoRequestStatus Status, EIOFlags SignaledFlags){
|
|
TriggeredSocket = Socket;
|
|
TriggeredStatus = Status;
|
|
TriggeredFlags = SignaledFlags;
|
|
++CallbackTriggerCount;
|
|
};
|
|
|
|
FIORequestHandle RequestHandle = IOManager.GetIOAccess().CreateSocketIORequest(MoveTemp(Request));
|
|
CHECK(RequestHandle.IsValid());
|
|
|
|
// 1. See that socket has a write event.
|
|
IOManager.Poll(FTimespan::Zero());
|
|
CHECK(CallbackTriggerCount == 1);
|
|
CHECK(TriggeredSocket == LocalSocket1);
|
|
CHECK(TriggeredStatus == ESocketIoRequestStatus::Ok);
|
|
CHECK(TriggeredFlags == EIOFlags::Write);
|
|
|
|
// 2. Write data to the socket until it returns an error indicating that it would block.
|
|
int32 BytesSent = 0;
|
|
while (FBSDSocketFuncs::Send(LocalSocket1, WriteBuffer.GetData(), WriteBufferSize, BytesSent));
|
|
|
|
// 3. See that socket no longer has a write event.
|
|
IOManager.Poll(FTimespan::Zero());
|
|
CHECK(CallbackTriggerCount == 1);
|
|
|
|
// 4. Close the remote end of the connection.
|
|
FBSDSocketFuncs::Close(RemoteSocket1);
|
|
|
|
// 5. See that socket now has a write event.
|
|
IOManager.Poll(FTimespan::Zero());
|
|
CHECK(CallbackTriggerCount == 2);
|
|
CHECK(TriggeredSocket == LocalSocket1);
|
|
CHECK(TriggeredStatus == ESocketIoRequestStatus::Ok);
|
|
CHECK(TriggeredFlags == EIOFlags::Write);
|
|
}
|
|
|
|
SECTION("More than one request for the same socket")
|
|
{
|
|
SOCKET TriggeredSocket = INVALID_SOCKET;
|
|
ESocketIoRequestStatus TriggeredStatus = ESocketIoRequestStatus::Ok;
|
|
EIOFlags TriggeredFlags = EIOFlags::None;
|
|
uint32 CallbackTriggerCount1 = 0;
|
|
uint32 CallbackTriggerCount2 = 0;
|
|
|
|
FIORequestBSDSocket Request1;
|
|
Request1.Socket = LocalSocket1;
|
|
Request1.Flags = EIOFlags::Read;
|
|
Request1.Callback = [&](SOCKET Socket, ESocketIoRequestStatus Status, EIOFlags SignaledFlags){
|
|
TriggeredSocket = Socket;
|
|
TriggeredStatus = Status;
|
|
TriggeredFlags = SignaledFlags;
|
|
++CallbackTriggerCount1;
|
|
};
|
|
|
|
FIORequestHandle RequestHandle1 = IOManager.GetIOAccess().CreateSocketIORequest(MoveTemp(Request1));
|
|
CHECK(RequestHandle1.IsValid());
|
|
|
|
FIORequestBSDSocket Request2;
|
|
Request2.Socket = LocalSocket1;
|
|
Request2.Flags = EIOFlags::Write;
|
|
Request2.Callback = [&](SOCKET Socket, ESocketIoRequestStatus Status, EIOFlags SignaledFlags){
|
|
TriggeredSocket = Socket;
|
|
TriggeredStatus = Status;
|
|
TriggeredFlags = SignaledFlags;
|
|
++CallbackTriggerCount2;
|
|
};
|
|
|
|
FIORequestHandle RequestHandle2 = IOManager.GetIOAccess().CreateSocketIORequest(MoveTemp(Request2));
|
|
CHECK(RequestHandle2.IsValid());
|
|
|
|
IOManager.Poll(FTimespan::Zero());
|
|
CHECK(CallbackTriggerCount1 == 0);
|
|
CHECK(CallbackTriggerCount2 == 1);
|
|
CHECK(TriggeredStatus == ESocketIoRequestStatus::Invalid);
|
|
}
|
|
}
|
|
|
|
SECTION("Multiple waiting TCP socket tests")
|
|
{
|
|
// Create remote socket.
|
|
SOCKET RemoteSocket2 = FBSDSocketFuncs::Open(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
|
REQUIRE(RemoteSocket2 != INVALID_SOCKET);
|
|
REQUIRE(FBSDSocketFuncs::SetSendBufferSize(RemoteSocket2, SocketSendBufferSize));
|
|
REQUIRE(FBSDSocketFuncs::SetRecvBufferSize(RemoteSocket2, SocketReceiveBufferSize));
|
|
REQUIRE(FBSDSocketFuncs::SetNonBlocking(RemoteSocket2, true));
|
|
REQUIRE(FBSDSocketFuncs::Connect(RemoteSocket2, HostAddr));
|
|
|
|
// Create local socket.
|
|
ESocketErrors ErrorCode2 = SE_NO_ERROR;
|
|
SOCKET LocalSocket2 = INVALID_SOCKET;
|
|
do
|
|
{
|
|
LocalSocket2 = FBSDSocketFuncs::Accept(ListenSocket);
|
|
ErrorCode2 = SocketSubsystem->GetLastErrorCode();
|
|
}
|
|
while (ErrorCode2 == SE_EWOULDBLOCK);
|
|
REQUIRE(LocalSocket2 != INVALID_SOCKET);
|
|
|
|
SECTION("Triggering LocalSocket1 does not trigger LocalSocket2")
|
|
{
|
|
SOCKET TriggeredSocket1 = INVALID_SOCKET;
|
|
ESocketIoRequestStatus TriggeredStatus1 = ESocketIoRequestStatus::Ok;
|
|
EIOFlags TriggeredFlags1 = EIOFlags::None;
|
|
uint32 CallbackTriggerCount1 = 0;
|
|
FIORequestBSDSocket Request1;
|
|
Request1.Socket = LocalSocket1;
|
|
Request1.Flags = EIOFlags::Read;
|
|
Request1.Callback = [&](SOCKET Socket, ESocketIoRequestStatus Status, EIOFlags SignaledFlags){
|
|
TriggeredSocket1 = Socket;
|
|
TriggeredStatus1 = Status;
|
|
TriggeredFlags1 = SignaledFlags;
|
|
++CallbackTriggerCount1;
|
|
};
|
|
FIORequestHandle RequestHandle1 = IOManager.GetIOAccess().CreateSocketIORequest(MoveTemp(Request1));
|
|
CHECK(RequestHandle1.IsValid());
|
|
|
|
SOCKET TriggeredSocket2 = INVALID_SOCKET;
|
|
ESocketIoRequestStatus TriggeredStatus2 = ESocketIoRequestStatus::Ok;
|
|
EIOFlags TriggeredFlags2 = EIOFlags::None;
|
|
uint32 CallbackTriggerCount2 = 0;
|
|
FIORequestBSDSocket Request2;
|
|
Request2.Socket = LocalSocket2;
|
|
Request2.Flags = EIOFlags::Read;
|
|
Request2.Callback = [&](SOCKET Socket, ESocketIoRequestStatus Status, EIOFlags SignaledFlags){
|
|
TriggeredSocket2 = Socket;
|
|
TriggeredStatus2 = Status;
|
|
TriggeredFlags2 = SignaledFlags;
|
|
++CallbackTriggerCount2;
|
|
};
|
|
FIORequestHandle RequestHandle2 = IOManager.GetIOAccess().CreateSocketIORequest(MoveTemp(Request2));
|
|
CHECK(RequestHandle2.IsValid());
|
|
|
|
// 1. No events triggered when no data has arrived at the socket.
|
|
IOManager.Poll(FTimespan::Zero());
|
|
CHECK(CallbackTriggerCount1 == 0);
|
|
CHECK(CallbackTriggerCount2 == 0);
|
|
|
|
// 2. Send data to the socket.
|
|
int32 BytesSent = 0;
|
|
CHECK(FBSDSocketFuncs::Send(RemoteSocket1, WriteBuffer.GetData(), 1, BytesSent));
|
|
CHECK(BytesSent == 1);
|
|
|
|
// 3. See that socket has a read event.
|
|
TriggeredFlags1 = EIOFlags::None;
|
|
IOManager.Poll(FTimespan::Zero());
|
|
CHECK(CallbackTriggerCount1 == 1);
|
|
CHECK(CallbackTriggerCount2 == 0);
|
|
CHECK(TriggeredSocket1 == LocalSocket1);
|
|
CHECK(TriggeredStatus1 == ESocketIoRequestStatus::Ok);
|
|
CHECK(TriggeredFlags1 == EIOFlags::Read);
|
|
|
|
// 4. See that socket still has a read event.
|
|
IOManager.Poll(FTimespan::Zero());
|
|
CHECK(CallbackTriggerCount1 == 2);
|
|
CHECK(CallbackTriggerCount2 == 0);
|
|
CHECK(TriggeredSocket1 == LocalSocket1);
|
|
CHECK(TriggeredStatus1 == ESocketIoRequestStatus::Ok);
|
|
CHECK(TriggeredFlags1 == EIOFlags::Read);
|
|
|
|
// 5. Read the waiting data from the socket.
|
|
int32 BytesRead = 0;
|
|
CHECK(FBSDSocketFuncs::Recv(LocalSocket1, ReadBuffer.GetData(), ReadBufferSize, BytesRead));
|
|
CHECK(BytesRead == 1);
|
|
|
|
// 6. See that socket no longer has a read event.
|
|
TriggeredFlags1 = EIOFlags::None;
|
|
IOManager.Poll(FTimespan::Zero());
|
|
CHECK(CallbackTriggerCount1 == 2);
|
|
CHECK(CallbackTriggerCount2 == 0);
|
|
}
|
|
|
|
FBSDSocketFuncs::Close(RemoteSocket2);
|
|
FBSDSocketFuncs::Close(LocalSocket2);
|
|
}
|
|
|
|
SECTION("Waiting on a TCP and UDP socket")
|
|
{
|
|
// Create local socket.
|
|
SOCKET LocalSocket2 = FBSDSocketFuncs::Open(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
|
REQUIRE(LocalSocket2 != INVALID_SOCKET);
|
|
|
|
REQUIRE(FBSDSocketFuncs::SetNonBlocking(LocalSocket2, true));
|
|
REQUIRE(FBSDSocketFuncs::BindLoopback(LocalSocket2));
|
|
sockaddr_in HostAddr2;
|
|
FMemory::Memzero(&HostAddr2, sizeof(HostAddr2));
|
|
REQUIRE(FBSDSocketFuncs::GetAddress(LocalSocket2, HostAddr2));
|
|
|
|
// Create remote socket.
|
|
SOCKET RemoteSocket2 = FBSDSocketFuncs::Open(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
|
REQUIRE(RemoteSocket2 != INVALID_SOCKET);
|
|
REQUIRE(FBSDSocketFuncs::SetSendBufferSize(RemoteSocket2, SocketSendBufferSize));
|
|
REQUIRE(FBSDSocketFuncs::SetRecvBufferSize(RemoteSocket2, SocketReceiveBufferSize));
|
|
REQUIRE(FBSDSocketFuncs::SetNonBlocking(RemoteSocket2, true));
|
|
|
|
SECTION("Recevie events for TCP and UDP socket types.")
|
|
{
|
|
SOCKET TriggeredSocket1 = INVALID_SOCKET;
|
|
ESocketIoRequestStatus TriggeredStatus1 = ESocketIoRequestStatus::Ok;
|
|
EIOFlags TriggeredFlags1 = EIOFlags::None;
|
|
uint32 CallbackTriggerCount1 = 0;
|
|
FIORequestBSDSocket Request1;
|
|
Request1.Socket = LocalSocket1;
|
|
Request1.Flags = EIOFlags::Read;
|
|
Request1.Callback = [&](SOCKET Socket, ESocketIoRequestStatus Status, EIOFlags SignaledFlags){
|
|
TriggeredSocket1 = Socket;
|
|
TriggeredStatus1 = Status;
|
|
TriggeredFlags1 = SignaledFlags;
|
|
++CallbackTriggerCount1;
|
|
};
|
|
FIORequestHandle RequestHandle1 = IOManager.GetIOAccess().CreateSocketIORequest(MoveTemp(Request1));
|
|
CHECK(RequestHandle1.IsValid());
|
|
|
|
SOCKET TriggeredSocket2 = INVALID_SOCKET;
|
|
ESocketIoRequestStatus TriggeredStatus2 = ESocketIoRequestStatus::Ok;
|
|
EIOFlags TriggeredFlags2 = EIOFlags::None;
|
|
uint32 CallbackTriggerCount2 = 0;
|
|
FIORequestBSDSocket Request2;
|
|
Request2.Socket = LocalSocket2;
|
|
Request2.Flags = EIOFlags::Read;
|
|
Request2.Callback = [&](SOCKET Socket, ESocketIoRequestStatus Status, EIOFlags SignaledFlags){
|
|
TriggeredSocket2 = Socket;
|
|
TriggeredStatus2 = Status;
|
|
TriggeredFlags2 = SignaledFlags;
|
|
++CallbackTriggerCount2;
|
|
};
|
|
FIORequestHandle RequestHandle2 = IOManager.GetIOAccess().CreateSocketIORequest(MoveTemp(Request2));
|
|
CHECK(RequestHandle2.IsValid());
|
|
|
|
// 1. No events triggered when no data has arrived at the socket.
|
|
IOManager.Poll(FTimespan::Zero());
|
|
CHECK(CallbackTriggerCount1 == 0);
|
|
CHECK(CallbackTriggerCount2 == 0);
|
|
|
|
// 2. Send data to the socket.
|
|
int32 BytesSent = 0;
|
|
CHECK(FBSDSocketFuncs::Send(RemoteSocket1, WriteBuffer.GetData(), 1, BytesSent));
|
|
CHECK(BytesSent == 1);
|
|
|
|
// 3. See that socket has a read event.
|
|
IOManager.Poll(FTimespan::Zero());
|
|
CHECK(CallbackTriggerCount1 == 1);
|
|
CHECK(CallbackTriggerCount2 == 0);
|
|
CHECK(TriggeredSocket1 == LocalSocket1);
|
|
CHECK(TriggeredStatus1 == ESocketIoRequestStatus::Ok);
|
|
CHECK(TriggeredFlags1 == EIOFlags::Read);
|
|
|
|
// 4. Send data to the UDP socket.
|
|
BytesSent = 0;
|
|
CHECK(FBSDSocketFuncs::SendTo(RemoteSocket2, WriteBuffer.GetData(), 1, BytesSent, HostAddr2));
|
|
CHECK(BytesSent == 1);
|
|
|
|
// 5. See that both sockets have a read event.
|
|
IOManager.Poll(FTimespan::Zero());
|
|
CHECK(CallbackTriggerCount1 == 2);
|
|
CHECK(CallbackTriggerCount2 == 1);
|
|
CHECK(TriggeredSocket1 == LocalSocket1);
|
|
CHECK(TriggeredStatus1 == ESocketIoRequestStatus::Ok);
|
|
CHECK(TriggeredFlags1 == EIOFlags::Read);
|
|
CHECK(TriggeredSocket2 == LocalSocket2);
|
|
CHECK(TriggeredStatus2 == ESocketIoRequestStatus::Ok);
|
|
CHECK(TriggeredFlags2 == EIOFlags::Read);
|
|
|
|
// 5. Read the waiting data from the TCP socket.
|
|
int32 BytesRead = 0;
|
|
CHECK(FBSDSocketFuncs::Recv(LocalSocket1, ReadBuffer.GetData(), ReadBufferSize, BytesRead));
|
|
CHECK(BytesRead == 1);
|
|
|
|
// 6. See that the UDP socket still has a read event.
|
|
IOManager.Poll(FTimespan::Zero());
|
|
CHECK(CallbackTriggerCount1 == 2);
|
|
CHECK(CallbackTriggerCount2 == 2);
|
|
CHECK(TriggeredSocket2 == LocalSocket2);
|
|
CHECK(TriggeredStatus2 == ESocketIoRequestStatus::Ok);
|
|
CHECK(TriggeredFlags2 == EIOFlags::Read);
|
|
}
|
|
|
|
FBSDSocketFuncs::Close(RemoteSocket2);
|
|
FBSDSocketFuncs::Close(LocalSocket2);
|
|
}
|
|
|
|
FBSDSocketFuncs::Close(ListenSocket);
|
|
FBSDSocketFuncs::Close(RemoteSocket1);
|
|
FBSDSocketFuncs::Close(LocalSocket1);
|
|
}
|
|
|
|
SECTION("Wait on too many sockets")
|
|
{
|
|
struct FCallbackData
|
|
{
|
|
SOCKET Socket = INVALID_SOCKET;
|
|
ESocketIoRequestStatus Status = ESocketIoRequestStatus::Ok;
|
|
EIOFlags SignaledFlags = EIOFlags::None;
|
|
};
|
|
|
|
TArray<SOCKET> Sockets;
|
|
TArray<FCallbackData> CallbackData;
|
|
|
|
SOCKET BaseSocket = FBSDSocketFuncs::Open(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
|
REQUIRE(BaseSocket != INVALID_SOCKET);
|
|
REQUIRE(FBSDSocketFuncs::SetNonBlocking(BaseSocket, true));
|
|
REQUIRE(FBSDSocketFuncs::SetReusePort(BaseSocket, true));
|
|
REQUIRE(FBSDSocketFuncs::BindLoopback(BaseSocket));
|
|
|
|
sockaddr_in BaseAddr;
|
|
FMemory::Memzero(&BaseAddr, sizeof(BaseAddr));
|
|
REQUIRE(FBSDSocketFuncs::GetAddress(BaseSocket, BaseAddr));
|
|
|
|
const int32 MaxTestSockets = 10000;
|
|
for (int32 index = 0; index < MaxTestSockets; ++index)
|
|
{
|
|
SOCKET Socket = FBSDSocketFuncs::Open(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
|
REQUIRE(Socket != INVALID_SOCKET);
|
|
REQUIRE(FBSDSocketFuncs::SetNonBlocking(Socket, true));
|
|
REQUIRE(FBSDSocketFuncs::SetReusePort(Socket, true));
|
|
REQUIRE(FBSDSocketFuncs::Bind(Socket, BaseAddr));
|
|
Sockets.Add(Socket);
|
|
|
|
FIORequestBSDSocket Request;
|
|
Request.Socket = Socket;
|
|
Request.Flags = EIOFlags::Read;
|
|
Request.Callback = [&CallbackData](SOCKET Socket, ESocketIoRequestStatus Status, EIOFlags SignaledFlags){
|
|
CallbackData.Add({Socket, Status, SignaledFlags});
|
|
};
|
|
FIORequestHandle RequestHandle = IOManager.GetIOAccess().CreateSocketIORequest(MoveTemp(Request));
|
|
CHECK(RequestHandle.IsValid());
|
|
|
|
IOManager.Poll(FTimespan::Zero());
|
|
|
|
if (!CallbackData.IsEmpty())
|
|
{
|
|
CHECK(CallbackData.Num() == 1);
|
|
CHECK(CallbackData[0].Socket == Socket);
|
|
CHECK(CallbackData[0].Status == ESocketIoRequestStatus::NoResources);
|
|
CHECK(CallbackData[0].SignaledFlags == EIOFlags::None);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Check that a failure occurred.
|
|
CHECK(!CallbackData.IsEmpty());
|
|
|
|
for (SOCKET Socket : Sockets)
|
|
{
|
|
FBSDSocketFuncs::Close(Socket);
|
|
}
|
|
|
|
FBSDSocketFuncs::Close(BaseSocket);
|
|
}
|
|
|
|
IOManager.Shutdown();
|
|
}
|
|
|
|
} // UE::EventLoop
|
|
|
|
#endif // PLATFORM_HAS_BSD_SOCKETS
|