UE 网络初始化流程

概述

客户端socket初始化,服务端socket初始化
客户端和服务器如何握手
客户端登陆流程

UE Server Socket 初始化流程

代码版本5.3

StartPlayInEditorGameInstance

从我们点击运行三角形开始
创建gameinstance 然后到这里
FGameInstancePIEResult UGameInstance::StartPlayInEditorGameInstance(ULocalPlayer* LocalPlayer, const FGameInstancePIEParameters& Params)
if (Params.NetMode == PIE_Client) {} else {}

UENUM()
enum EPlayNetMode : int
{
    /** A standalone game will be started. This will not create a dedicated server, nor automatically connect to one. A server can be launched by enabling bLaunchSeparateServer if you need to test offline -> server connection flow for your game. */
    PIE_Standalone UMETA(DisplayName="Play Standalone"),
    /** The editor will act as both a Server and a Client. Additional instances may be opened beyond that depending on the number of clients. */
    PIE_ListenServer UMETA(DisplayName="Play As Listen Server"),
    /** The editor will act as a Client. A server will be started for you behind the scenes to connect to. */
    PIE_Client UMETA(DisplayName="Play As Client"),
};

我们这里是服务器走else

Init Url

构造一个URL。就是初始化IP,端口这样的一个配置

FURL URL;

URL = FURL(NULL, *EditorEngine->BuildPlayWorldURL(*PIEMapName, Params.bStartInSpectatorMode, ExtraURLOptions), TRAVEL_Absolute);

。。。

EnableListenServer

到这里开始准备 Listen了

        if (Params.NetMode == PIE_ListenServer)
        {
            // Add port
            uint32 ListenPort = 0;
            uint16 ServerPort = 0;
            if (Params.EditorPlaySettings->GetServerPort(ServerPort))
            {
                ListenPort = ServerPort;
            }

            // Start a listen server
            ensureMsgf(EnableListenServer(true, ListenPort), TEXT("Starting Listen Server for Play in Editor failed!"));
        }
World->Listen(ListenURL);

UWorld listen

我们到了UWorld的listen

bool UWorld::Listen( FURL& InURL )
{
    // 。。。

    // Create net driver.
    if (GEngine->CreateNamedNetDriver(this, NAME_GameNetDriver, NAME_GameNetDriver))
    {
        NetDriver = GEngine->FindNamedNetDriver(this, NAME_GameNetDriver);

        // 。。。

        NetDriver->SetWorld(this);

        // 。。。
    }

    if (NetDriver == nullptr)
    {
        GEngine->BroadcastNetworkFailure(this, NULL, ENetworkFailure::NetDriverCreateFailure);
        return false;
    }

    AWorldSettings* WorldSettings = GetWorldSettings();
    const bool bReuseAddressAndPort = WorldSettings ? WorldSettings->bReuseAddressAndPort : false;

    FString Error;
    if( !NetDriver->InitListen( this, InURL, bReuseAddressAndPort, Error ) )
    {

        // 。。。
        return false;
    }

    // 。。。
}

CreateNamedNetDriver

创建驱动,在其他网络库可能会叫net manager 之类的,一个总管理器
GEngine->CreateNamedNetDriver(this, NAME_GameNetDriver, NAME_GameNetDriver)

驱动创建成功,调用驱动的listen
NetDriver->InitListen( this, InURL, bReuseAddressAndPort, Error )

UIpNetDriver::InitListen

应该是来到了这里

bool UIpNetDriver::InitListen( FNetworkNotify* InNotify, FURL& LocalURL, bool bReuseAddressAndPort, FString& Error )
{
    if( !InitBase( false, InNotify, LocalURL, bReuseAddressAndPort, Error ) )
    {
        UE_LOG(LogNet, Warning, TEXT("Failed to init net driver ListenURL: %s: %s"), *LocalURL.ToString(), *Error);
        return false;
    }

    InitConnectionlessHandler();

    // Update result URL.
    //LocalURL.Host = LocalAddr->ToString(false);
    LocalURL.Port = LocalAddr->GetPort();
    UE_LOG(LogNet, Log, TEXT("%s IpNetDriver listening on port %i"), *GetDescription(), LocalURL.Port );

    return true;
}

重点是这个同文件的InitBase

bool UIpNetDriver::InitBase( bool bInitAsClient, FNetworkNotify* InNotify, const FURL& URL, bool bReuseAddressAndPort, FString& Error )
{
    using namespace UE::Net::Private;

    if (!Super::InitBase(bInitAsClient, InNotify, URL, bReuseAddressAndPort, Error))
    {
        return false;
    }

    // ..
}

UNetDriver::InitBase

他的基类的 InitBase
启动服务器xxx.exe的时候可以有 -log 之类的参数,一些超时参数就是在这里解析的

bool UNetDriver::InitBase(bool bInitAsClient, FNetworkNotify* InNotify, const FURL& URL, bool bReuseAddressAndPort, FString& Error)
{
    // 初始化一些参数
    // InitialConnectTimeout
    // ConnectionTimeout
    // NoTimeouts

    // Read any timeout overrides from the URL

    LastTickDispatchRealtime = FPlatformTime::Seconds();
    bool bSuccess = InitConnectionClass();

    // 。。。

    return bSuccess;
}
bool bSuccess = InitConnectionClass();

这里初始化了 UNetConnection

bool UNetDriver::InitConnectionClass(void)
{
    if (NetConnectionClass == NULL && NetConnectionClassName != TEXT(""))
    {
        NetConnectionClass = LoadClass<UNetConnection>(NULL,*NetConnectionClassName,NULL,LOAD_None,NULL);
        if (NetConnectionClass == NULL)
        {
            UE_LOG(LogNet, Error,TEXT("Failed to load class '%s'"),*NetConnectionClassName);
        }
    }
    return NetConnectionClass != NULL;
}

下一段

    if (!bInitAsClient)
    {
        ConnectionlessHandler.Reset();

        if (!IsUsingIrisReplication())
        {
            InitReplicationDriverClass();
            SetReplicationDriver(UReplicationDriver::CreateReplicationDriver(this, URL, GetWorld()));
        }

        // 。。。
    }

这里的 ConnectionlessHandler.Reset();

    /** Serverside PacketHandler for managing connectionless packets */
    TUniquePtr<PacketHandler> ConnectionlessHandler;

发包的结构

GetSocketSubsystem

倒退
bool UIpNetDriver::InitBase( bool bInitAsClient, FNetworkNotify* InNotify, const FURL& URL, bool bReuseAddressAndPort, FString& Error )

ISocketSubsystem* SocketSubsystem = GetSocketSubsystem();

去创建指定平台的Socket

ISocketSubsystem* UIpNetDriver::GetSocketSubsystem()
{
    return ISocketSubsystem::Get();
}

Engine/Source/Runtime/Sockets/Private/SocketSubsystem.cpp
ISocketSubsystem* ISocketSubsystem::Get(const FName& SubsystemName)

CreateAndBindSocket

bool UIpNetDriver::InitBase( bool bInitAsClient, FNetworkNotify* InNotify, const FURL& URL, bool bReuseAddressAndPort, FString& Error ) 中

    const int32 BindPort = bInitAsClient ? GetClientPort() : URL.Port;
    // Increase socket queue size, because we are polling rather than threading
    // and thus we rely on the OS socket to buffer a lot of data.
    const int32 DesiredRecvSize = bInitAsClient ? ClientDesiredSocketReceiveBufferBytes : ServerDesiredSocketReceiveBufferBytes;
    const int32 DesiredSendSize = bInitAsClient ? ClientDesiredSocketSendBufferBytes : ServerDesiredSocketSendBufferBytes;
    const EInitBindSocketsFlags InitBindFlags = bInitAsClient ? EInitBindSocketsFlags::Client : EInitBindSocketsFlags::Server;
    FCreateAndBindSocketFunc CreateAndBindSocketsFunc = [this, BindPort, bReuseAddressAndPort, DesiredRecvSize, DesiredSendSize]
                                    (TSharedRef<FInternetAddr> BindAddr, FString& Error) -> FUniqueSocket
        {
            return this->CreateAndBindSocket(BindAddr, BindPort, bReuseAddressAndPort, DesiredRecvSize, DesiredSendSize, Error);
        };

    bool bInitBindSocketsSuccess = Resolver->InitBindSockets(MoveTemp(CreateAndBindSocketsFunc), InitBindFlags, SocketSubsystem, Error);

    if (!bInitBindSocketsSuccess)
    {
        UE_LOG(LogNet, Error, TEXT("InitBindSockets failed: %s"), ToCStr(Error));

        return false;
    }

注意这个结构 FInternetAddr

UIpNetDriver::CreateAndBindSocket

return this->CreateAndBindSocket(BindAddr, BindPort, bReuseAddressAndPort, DesiredRecvSize, DesiredSendSize, Error); 这里

进去找到
FUniqueSocket NewSocket = CreateSocketForProtocol(BindAddr->GetProtocolType()); 这一句就是创建socket
这个地方一直往下翻
virtual FSocket CreateSocket(const FName& SocketType, const FString& SocketDescription, const FName& ProtocolName) = 0;
FSocket
FSocketSubsystemBSD::CreateSocket(const FName& SocketType, const FString& SocketDescription, const FName& ProtocolType)
Socket = socket(GetProtocolFamilyValue(SocketProtocolType), SocketTypeFlag | PlatformSpecificTypeFlags, ((bIsUDP) ? IPPROTO_UDP : IPPROTO_TCP));
找到这里就能找到操作系统平台的socket创建

然后往后
NewSocket->SetReuseAddr(bReuseAddressAndPort) 这里也能找到原生API setsockopt

bool FSocketBSD::SetReuseAddr(bool bAllowReuse)
{
    int Param = bAllowReuse ? 1 : 0;
    int ReuseAddrResult = setsockopt(Socket, SOL_SOCKET, SO_REUSEADDR, (char*)&Param, sizeof(Param));
#ifdef SO_REUSEPORT // Linux kernel 3.9+ and FreeBSD define this separately
    if (ReuseAddrResult == 0)
    {
        return setsockopt(Socket, SOL_SOCKET, SO_REUSEPORT, (char *)&Param, sizeof(Param)) == 0;
    }
#endif
    return ReuseAddrResult == 0;
}

设置收发缓冲区

    NewSocket->SetReceiveBufferSize(DesiredRecvSize, ActualRecvSize);
    NewSocket->SetSendBufferSize(DesiredSendSize, ActualSendSize);

BindAddr->SetPort(Port); 字节序

void FInternetAddrBSD::SetPort(int32 InPort)
{
#if PLATFORM_HAS_BSD_IPV6_SOCKETS
    if (GetProtocolType() == FNetworkProtocolTypes::IPv6)
    {
        ((sockaddr_in6*)&Addr)->sin6_port = htons(IntCastChecked<uint16>(InPort));
        return;
    }
#endif

    ((sockaddr_in*)&Addr)->sin_port = htons(IntCastChecked<uint16>(InPort));
}

int32 BoundPort = SocketSubsystem->BindNextPort(NewSocket.Get(), *BindAddr, MaxPortCountToTry + 1, 1);
这里的 Socket->Bind(Addr) 能找到原生的bind函数

int32 ISocketSubsystem::BindNextPort(FSocket* Socket, FInternetAddr& Addr, int32 PortCount, int32 PortIncrement)
{
    // go until we reach the limit (or we succeed)
    for (int32 Index = 0; Index < PortCount; Index++)
    {
        // try to bind to the current port
        if (Socket->Bind(Addr) == true)
        {
            // if it succeeded, return the port
            if (Addr.GetPort() != 0)
            {
                return Addr.GetPort();
            }
            else
            {
                return Socket->GetPortNo();
            }
        }
        // if the address had no port, we are done
        if( Addr.GetPort() == 0 )
        {
            break;
        }

        // increment to the next port, and loop!
        Addr.SetPort(Addr.GetPort() + PortIncrement);
    }

    return 0;
}

NewSocket->SetNonBlocking() 设置socket非阻塞

PacketHandler

回退
bool UIpNetDriver::InitListen( FNetworkNotify* InNotify, FURL& LocalURL, bool bReuseAddressAndPort, FString& Error )

InitConnectionlessHandler();
ConnectionlessHandler = MakeUnique(&DDoS);
这里面有初始化 PacketHandler

总结

StartPlayInEditorGameInstance
UWorld::Listen
CreateNamedNetDriver
UIpNetDriver::InitListen
UNetDriver::InitBase
GetSocketSubsystem

CreateSocketForProtocol 【socket】

SetReuseAddr    【socket opt】
SetReceiveBufferSize
SetSendBufferSize
SetPort

SocketSubsystem->BindNextPort 【bind】

SetNonBlocking

UE Client Socket 初始化

从Play In Editor 的 if (Params.NetMode == PIE_Client) 开始看
找到
EditorEngine->Browse(WorldContext, FURL(&BaseURL, URLString, (ETravelType)TRAVEL_Absolute), Error) == EBrowseReturnVal::Pending

EBrowseReturnVal::Type UEngine::Browse( FWorldContext& WorldContext, FURL URL, FString& Error )
{
    //

        WorldContext.PendingNetGame = NewObject<UPendingNetGame>();
        WorldContext.PendingNetGame->Initialize(URL); //-V595
        WorldContext.PendingNetGame->InitNetDriver(); //-V595

    //
}

创建驱动

void UPendingNetGame::InitNetDriver()
{
    // ...

    GEngine->CreateNamedNetDriver(this, NAME_PendingNetDriver, NAME_GameNetDriver)

    // ...

    NetDriver->InitConnect( this, URL, ConnectionError )

    // ...
}

NetDriver->InitConnect( this, URL, ConnectionError )

bool UIpNetDriver::InitConnect( FNetworkNotify* InNotify, const FURL& ConnectURL, FString& Error )

bool UIpNetDriver::InitConnect( FNetworkNotify* InNotify, const FURL& ConnectURL, FString& Error )
{
    using namespace UE::Net::Private;

    ISocketSubsystem* SocketSubsystem = GetSocketSubsystem();
    if (SocketSubsystem == nullptr)
    {
        UE_LOG(LogNet, Warning, TEXT("Unable to find socket subsystem"));
        return false;
    }

    if( !InitBase( true, InNotify, ConnectURL, false, Error ) )
    {
        UE_LOG(LogNet, Warning, TEXT("Failed to init net driver ConnectURL: %s: %s"), *ConnectURL.ToString(), *Error);
        return false;
    }

    // Create new connection.
    ServerConnection = NewObject<UNetConnection>(GetTransientPackage(), NetConnectionClass);

    ServerConnection->InitLocalConnection(this, SocketPrivate.Get(), ConnectURL, USOCK_Pending);

    Resolver->InitConnect(ServerConnection, SocketSubsystem, GetSocket(), ConnectURL);

    UIpConnection* IpServerConnection = Cast<UIpConnection>(ServerConnection);

    if (FNetConnectionAddressResolution* ConnResolver = FNetDriverAddressResolution::GetConnectionResolver(IpServerConnection))
    {
        if (ConnResolver->IsAddressResolutionEnabled() && !ConnResolver->IsAddressResolutionComplete())
        {
            SocketState = ESocketState::Resolving;
        }
    }

    UE_LOG(LogNet, Log, TEXT("Game client on port %i, rate %i"), ConnectURL.Port, ServerConnection->CurrentNetSpeed );
    CreateInitialClientChannels();

    return true;
}

ISocketSubsystem* SocketSubsystem = GetSocketSubsystem();
InitBase( true, InNotify, ConnectURL, false, Error )

ServerConnection->InitLocalConnection(this, SocketPrivate.Get(), ConnectURL, USOCK_Pending);

void UIpConnection::InitLocalConnection(UNetDriver* InDriver, class FSocket* InSocket, const FURL& InURL, EConnectionState InState, int32 InMaxPacket, int32 InPacketOverhead)
{

}

UIpConnection* IpServerConnection = Cast(ServerConnection);

CreateInitialClientChannels actor,voice,control

CreateInitialClientChannels()
创建若干channel,actor,voice,control

void UNetDriver::CreateInitialClientChannels()
{
    if (ServerConnection != nullptr)
    {
        for (const FChannelDefinition& ChannelDef : ChannelDefinitions)
        {
            if (ChannelDef.bInitialClient && (ChannelDef.ChannelClass != nullptr))
            {
                ServerConnection->CreateChannelByName(ChannelDef.ChannelName, EChannelCreateFlags::OpenedLocally, ChannelDef.StaticChannelIndex);
            }
        }
    }
}

UChannel UNetConnection::CreateChannelByName(const FName& ChName, EChannelCreateFlags CreateFlags, int32 ChIndex)
里面找到
UChannel
Channel = Driver->GetOrCreateChannelByName(ChName);

UChannel* UNetDriver::GetOrCreateChannelByName(const FName& ChName)
{
    UChannel* RetVal = nullptr;
    if (ChName == NAME_Actor && CVarActorChannelPool.GetValueOnAnyThread() != 0)
    {
        while (ActorChannelPool.Num() > 0 && RetVal == nullptr)
        {
            RetVal = ActorChannelPool.Pop();
            if (RetVal && RetVal->GetClass() != ChannelDefinitionMap[ChName].ChannelClass)
            {
                // Channel type Changed since this channel was added to the pool. Throw it away.
                RetVal->MarkAsGarbage();
                RetVal = nullptr;
            }
        }
        if (RetVal)
        {
            check(RetVal->GetClass() == ChannelDefinitionMap[ChName].ChannelClass);
            check(IsValid(RetVal));
            RetVal->bPooled = false;
        }
    }

    if (!RetVal)
    {
        RetVal = InternalCreateChannelByName(ChName);
    }

    return RetVal;
}

这里可以看到UE的预开辟channel策略

我们回退到
void UPendingNetGame::InitNetDriver()

        if( NetDriver->InitConnect( this, URL, ConnectionError ) )
        {
            UNetConnection* ServerConn = NetDriver->ServerConnection;

            FNetDelegates::OnPendingNetGameConnectionCreated.Broadcast(this);

            // Kick off the connection handshake
            if (ServerConn->Handler.IsValid())
            {
                ServerConn->Handler->BeginHandshaking(
                    FPacketHandlerHandshakeComplete::CreateUObject(this, &UPendingNetGame::SendInitialJoin));
            }
            else
            {
                SendInitialJoin();
            }
        }

BeginHandshaking 这里开始握手了

BeginHandshaking

void PacketHandler::BeginHandshaking(FPacketHandlerHandshakeComplete InHandshakeDel/*=FPacketHandlerHandshakeComplete()*/)
{
    check(!bBeganHandshaking);

    bBeganHandshaking = true;

    HandshakeCompleteDel = InHandshakeDel;

    for (int32 i=HandlerComponents.Num() - 1; i>=0; --i)
    {
        HandlerComponent& CurComponent = *HandlerComponents[i];

        if (CurComponent.RequiresHandshake() && !CurComponent.IsInitialized())
        {
            CurComponent.NotifyHandshakeBegin();
            break;
        }
    }
}

HandlerComponent
里面存了一个 PackerHander 包数据

PacketHandler.h

    /** Used for packing outgoing packets */
    FBitWriter OutgoingPacket;

    /** Used for unpacking incoming packets */
    FBitReader IncomingPacket;

包的具体数据
有个具体印象,这个是用于处理数据包的一个组件

UE Client Server 握手阶段

ServerConn->Handler->BeginHandshaking(FPacketHandlerHandshakeComplete::CreateUObject(this, &UPendingNetGame::SendInitialJoin));

void PacketHandler::BeginHandshaking(FPacketHandlerHandshakeComplete InHandshakeDel/*=FPacketHandlerHandshakeComplete()*/)
{
    check(!bBeganHandshaking);

    bBeganHandshaking = true;

    HandshakeCompleteDel = InHandshakeDel;

    for (int32 i=HandlerComponents.Num() - 1; i>=0; --i)
    {
        HandlerComponent& CurComponent = *HandlerComponents[i];

        if (CurComponent.RequiresHandshake() && !CurComponent.IsInitialized())
        {
            CurComponent.NotifyHandshakeBegin();
            break;
        }
    }
}

CurComponent.NotifyHandshakeBegin();

void StatelessConnectHandlerComponent::NotifyHandshakeBegin()
{
    using namespace UE::Net;

    SendInitialPacket(static_cast<EHandshakeVersion>(CurrentHandshakeVersion));
}
void StatelessConnectHandlerComponent::SendInitialPacket(EHandshakeVersion HandshakeVersion)
{
    using namespace UE::Net;

    if (Handler->Mode == UE::Handler::Mode::Client)
    {
        UNetConnection* ServerConn = (Driver != nullptr ? ToRawPtr(Driver->ServerConnection) : nullptr);

        if (ServerConn != nullptr)
        {
            const int32 AdjustedSize = GetAdjustedSizeBits(HANDSHAKE_PACKET_SIZE_BITS, HandshakeVersion);
            FBitWriter InitialPacket(AdjustedSize + (BaseRandomDataLengthBytes * 8) + 1 /* Termination bit */);

            BeginHandshakePacket(InitialPacket, EHandshakePacketType::InitialPacket, HandshakeVersion, SentHandshakePacketCount, CachedClientID,
                                    (bRestartedHandshake ? EHandshakePacketModifier::RestartHandshake : EHandshakePacketModifier::None));

            uint8 SecretIdPad = 0;
            uint8 PacketSizeFiller[28];

            InitialPacket.WriteBit(SecretIdPad);

            FMemory::Memzero(PacketSizeFiller, UE_ARRAY_COUNT(PacketSizeFiller));
            InitialPacket.Serialize(PacketSizeFiller, UE_ARRAY_COUNT(PacketSizeFiller));

            SendToServer(HandshakeVersion, EHandshakePacketType::InitialPacket, InitialPacket);
        }
        else
        {
            UE_LOG(LogHandshake, Error, TEXT("Tried to send handshake connect packet without a server connection."));
        }
    }
}

UNetConnection* ServerConn = (Driver != nullptr ? ToRawPtr(Driver->ServerConnection) : nullptr); 先拿服务端链接
客户端的Driver 用 ServerConnection 去连服务端,服务端有多个 ClientConnections 去连客户端

    /** Connection to the server (this net driver is a client) */
    UPROPERTY()
    TObjectPtr<class UNetConnection> ServerConnection;

    /** Array of connections to clients (this net driver is a host) - unsorted, and ordering changes depending on actor replication */
    UPROPERTY()
    TArray<TObjectPtr<UNetConnection>> ClientConnections;

BeginHandshakePacket
InitialPacket.WriteBit(SecretIdPad);
InitialPacket.Serialize(PacketSizeFiller, UE_ARRAY_COUNT(PacketSizeFiller));
SendToServer(HandshakeVersion, EHandshakePacketType::InitialPacket, InitialPacket);
写数据然后发送
在 SendToServer 里面 跳过去了,socket这时候是无效的

if (Driver->IsNetResourceValid())
{
    FOutPacketTraits Traits;

    Driver->ServerConnection->LowLevelSend(Packet.GetData(), Packet.GetNumBits(), Traits);
}

void UIpConnection::LowLevelSend(void* Data, int32 CountBits, FOutPacketTraits& Traits)

结束后走代理,然后到了 SendInitialJoin

几个类

  • NetDriver

    • IPNetDriver 派生类驱动
      • Connection
      • UDPSocket
      • 同步Actor
      • 属性记录表
      • RPC
  • Connection

    • ClientConnection
    • ServerConnection
  • LocalPlayer

  • 通道

    • ControlChannel 客户端只有一个
    • VoiceChannel 客户端只有一个
    • ActorChannel
    • ActorChannel ...
    • ActorChannel ...
  • 通信

    • 数据包
      • InBunch
        • NGUID
        • Channel 信息
      • OutBunch
    • UPackageMap
    • 读写器
      • FBitWriter
      • FBitRead

UE 客户端,服务器对象交互

服务器有一个ServerConnect,客户端有一个ClientConnect
Connect 里面有很多Channel。channel里面可以互相发包

客户端登陆流程

SendInitialJoin

握手完成会跑到 SendInitialJoin
上面客户端初始化有提到过

ServerConn->Handler->BeginHandshaking(FPacketHandlerHandshakeComplete::CreateUObject(this, &UPendingNetGame::SendInitialJoin));
void UPendingNetGame::SendInitialJoin()
{
    if (NetDriver != nullptr)
    {
        UNetConnection* ServerConn = NetDriver->ServerConnection;

        if (ServerConn != nullptr)
        {

            // ...

            if (!bEncryptionRequirementsFailure)
            {
                uint32 LocalNetworkVersion = FNetworkVersion::GetLocalNetworkVersion();

                UE_LOG(LogNet, Log, TEXT("UPendingNetGame::SendInitialJoin: Sending hello. %s"), *ServerConn->Describe());

                EEngineNetworkRuntimeFeatures LocalNetworkFeatures = NetDriver->GetNetworkRuntimeFeatures();
                FNetControlMessage<NMT_Hello>::Send(ServerConn, IsLittleEndian, LocalNetworkVersion, EncryptionToken, LocalNetworkFeatures);

                ServerConn->FlushNet();
            }
            else
            {
                // ...
            }
        }
    }
}

FNetControlMessage::Send(ServerConn, IsLittleEndian, LocalNetworkVersion, EncryptionToken, LocalNetworkFeatures);
这里客户端给服务器Hello包,这一步只是把包放到缓冲区,还没发出去

这个Hello定义在 DataChannel.h 里面
DEFINE_CONTROL_CHANNEL_MESSAGE(Hello, 0, uint8, uint32, FString, uint16); // initial client connection message

ServerConn->FlushNet();
才是发送
void UNetConnection::FlushNet(bool bIgnoreSimulation)

ValidateSendBuffer(); 先验证一下

if (SendBuffer.GetNumBits() || HasDirtyAcks || ( Driver->GetElapsedTime() - LastSendTime > Driver->KeepAliveTime && !IsInternalAck() && GetConnectionState() != USOCK_Closed))
判断一下能不能发包了,这里不会没帧都发

写数据
WritePacketHeader(SendBuffer);
WriteFinalPacketInfo(SendBuffer, PacketSentTimeInS);

LowLevelSend((char*) SendBuffer.GetData(), SendBuffer.GetNumBits(), Traits); 发送数据,里面能找到socket->sendto

此时服务端

void UWorld::NotifyControlMessage(UNetConnection* Connection, uint8 MessageType, class FInBunch& Bunch)
开头判断 if( NetDriver->ServerConnection ) 如果是客户端,会有一个ServerConnection 去连接服务端,经常用这个来判断是客户端还是服务器

搜索 case NMT_Hello: 字段。在world.cpp里面
if (FNetControlMessage::Receive(Bunch, IsLittleEndian, RemoteNetworkVersion, EncryptionToken, RemoteNetworkFeatures)) 接受数据

如果版本不一致
就发送要升级

const bool bIsCompatible = FNetworkVersion::IsNetworkCompatible(LocalNetworkVersion, RemoteNetworkVersion) && FNetworkVersion::AreNetworkRuntimeFeaturesCompatible(LocalNetworkFeatures, RemoteNetworkFeatures);
if (!bIsCompatible)
{
    // ...
    FNetControlMessage<NMT_Upgrade>::Send(Connection, LocalNetworkVersion, LocalNetworkFeatures);
    Connection->FlushNet(true);
    Connection->Close(ENetCloseResult::Upgrade);
    // ...
}

然后是密钥
不对还是啥的就是 fail

Connection->SendCloseReason(ENetCloseResult::EncryptionTokenMissing);
FNetControlMessage<NMT_Failure>::Send(Connection, FailureMsg);
Connection->FlushNet(true);
Connection->Close(ENetCloseResult::EncryptionTokenMissing);

如果验证都通过会调用, 发送一个 NMT_Challenge

FNetDelegates::OnReceivedNetworkEncryptionToken.Execute(EncryptionToken, FOnEncryptionKeyResponse::CreateUObject(Connection, &UNetConnection::SendChallengeControlMessage));
void UNetConnection::SendChallengeControlMessage()
{
    if (GetConnectionState() != USOCK_Invalid && GetConnectionState() != USOCK_Closed && Driver)
    {
        Challenge = FString::Printf(TEXT("%08X"), FPlatformTime::Cycles());
        SetExpectedClientLoginMsgType(NMT_Login);
        FNetControlMessage<NMT_Challenge>::Send(this, Challenge);
        FlushNet();
    }
    else
    {
        UE_LOG(LogNet, Log, TEXT("UWorld::SendChallengeControlMessage: connection in invalid state. %s"), *Describe());
    }
}

服务器进入 SetExpectedClientLoginMsgType(NMT_Login); NMT_Login 状态
发送 NMT_Challenge 去客户端

又到客户端

void UPendingNetGame::NotifyControlMessage(UNetConnection* Connection, uint8 MessageType, class FInBunch& Bunch)
case NMT_Challenge:

准备了一堆数据后,send

FNetControlMessage<NMT_Login>::Send(Connection, Connection->ClientResponse, URLString, Connection->PlayerId, OnlinePlatformNameString);
NetDriver->ServerConnection->FlushNet();

又又到服务端

world.cpp
case NMT_Login:

bool bReceived = FNetControlMessage<NMT_Login>::Receive(Bunch, Connection->ClientResponse, RequestURL, UniqueIdRepl, OnlinePlatformName);

设置URL,包含地图信息
预登录

                    AGameModeBase* GameMode = GetAuthGameMode();
                    AGameModeBase::FOnPreLoginCompleteDelegate OnComplete = AGameModeBase::FOnPreLoginCompleteDelegate::CreateUObject(
                        this, &UWorld::PreLoginComplete, TWeakObjectPtr<UNetConnection>(Connection));
                    if (GameMode)
                    {
                        GameMode->PreLoginAsync(Tmp, Connection->LowLevelGetRemoteAddress(), Connection->PlayerId, OnComplete);
                    }
                    else
                    {
                        OnComplete.ExecuteIfBound(FString());
                    }

如果没问题就 PreLoginComplete
然后WelcomePlayer(Connection);

void UWorld::WelcomePlayer(UNetConnection* Connection)
设置关卡

然后发给客户端
FNetControlMessage::Send(Connection, LevelName, GameName, RedirectURL);

又又又到客户端

case NMT_Welcome:

收到数据
FNetControlMessage::Send(Connection, Connection->CurrentNetSpeed);
bSuccessfullyConnected = true
这个true之后

        else if (Context.PendingNetGame && Context.PendingNetGame->bSuccessfullyConnected && !Context.PendingNetGame->bSentJoinRequest && !Context.PendingNetGame->bLoadedMapSuccessfully && (Context.OwningGameInstance == NULL || !Context.OwningGameInstance->DelayPendingNetGameTravel()))
        {

            else if (!Context.PendingNetGame->bLoadedMapSuccessfully)
            {
                // Attempt to load the map.
                FString Error;

                const bool bLoadedMapSuccessfully = LoadMap(Context, Context.PendingNetGame->URL, Context.PendingNetGame, Error);

                if (Context.PendingNetGame != nullptr)
                {
                    if (!Context.PendingNetGame->LoadMapCompleted(this, Context, bLoadedMapSuccessfully, Error))
                    {

                    }
                }
                else
                {
                }
            }
        }

在这里加载地图

又又又到服务器

            case NMT_Netspeed:
            {
                int32 Rate;

                if (FNetControlMessage<NMT_Netspeed>::Receive(Bunch, Rate))
                {
                    Connection->CurrentNetSpeed = FMath::Clamp(Rate, 1800, NetDriver->MaxClientRate);
                    UE_LOG(LogNet, Log, TEXT("Client netspeed is %i"), Connection->CurrentNetSpeed);
                }

                break;
            }

服务器收到join

case NMT_Join:

可以看到这里 spawn了 player controller
Connection->PlayerController = SpawnPlayActor( Connection, ROLE_AutonomousProxy, InURL, Connection->PlayerId, ErrorMsg );

如何发包的

主要是搞清楚这个是在干嘛的
准备缓冲区和发送

FNetControlMessage<NMT_Hello>::Send(ServerConn, IsLittleEndian, LocalNetworkVersion, EncryptionToken, LocalNetworkFeatures);
ServerConn->FlushNet();

// ===========================

FNetControlMessage<NMT_Challenge>::Send(this, Challenge);


FNetControlMessage::Send(this, Challenge); 为例
由于这个定义 DataChannel.h
DEFINE_CONTROL_CHANNEL_MESSAGE(Challenge, 3, FString); // server sends client challenge string to verify integrity

看这个宏的send
最后会跑到
Conn->Channels[0]->SendBunch(&Bunch, true);

#define DEFINE_CONTROL_CHANNEL_MESSAGE(Name, Index, ...) \
enum { NMT_##Name = Index }; \
template<> class FNetControlMessage<Index> \
{ \
public: \
    static uint8 Initialize() \
    { \
        FNetControlMessageInfo::SetName(Index, TEXT(#Name)); \
        return 0; \
    } \
    /** sends a message of this type on the specified connection's control channel \
        * @note: const not used only because of the FArchive interface; the parameters are not modified \
        */ \
    template<typename... ParamTypes> \
    static void Send(UNetConnection* Conn, ParamTypes&... Params) \
    { \
        static_assert(Index < FNetControlMessageInfo::MaxNames, "Control channel message must be a byte."); \
        checkSlow(!Conn->IsA(UChildConnection::StaticClass())); /** control channel messages can only be sent on the parent connection */ \
        if (Conn->Channels[0] != NULL && !Conn->Channels[0]->Closing) \
        { \
            FControlChannelOutBunch Bunch(Conn->Channels[0], false); \
            uint8 MessageType = Index; \
            Bunch << MessageType; \
            FNetControlMessageInfo::SendParams(Bunch, Params...); \
            Conn->Channels[0]->SendBunch(&Bunch, true); \
        } \
    } \
    /** receives a message of this type from the passed in bunch */ \
    template<typename... ParamTypes> \
    UE_NODISCARD static bool Receive(FInBunch& Bunch, ParamTypes&... Params) \
    { \
        FNetControlMessageInfo::ReceiveParams(Bunch, Params...); \
        return !Bunch.IsError(); \
    } \
    /** throws away a message of this type from the passed in bunch */ \
    static void Discard(FInBunch& Bunch) \
    { \
        TTuple<__VA_ARGS__> Params; \
        VisitTupleElements([&Bunch](auto& Param) \
        { \
            Bunch << Param; \
        }, \
        Params); \
    } \
};

FPacketIdRange UChannel::SendBunch( FOutBunch* Bunch, bool Merge )

FOutBunch 数据准备好了,要给远端
。。。
if( Bunch->GetNumBits() > MAX_SINGLE_BUNCH_SIZE_BITS ) 包太长要分包了

如何接收

接受是在tick里面
void UIpNetDriver::TickDispatch(float DeltaTime)

在这个之间

DDoS.PreFrameReceive(DeltaTime);

DDoS.PostFrameReceive();

for (FPacketIterator It(this); It; ++It) 迭代器

看这个迭代器的构造函数,处理初始化了一些值以外
AdvanceCurrentPacket
找到接受单个包 ReceiveSinglePacket
单线程情况
bool bReceivedPacket = Driver->GetSocket()->RecvFrom(CurrentPacket.Data.GetData(), MAX_PACKET_SIZE, BytesRead, *CurrentPacket.Address);

构造函数跑完,到了for里面

    for (FPacketIterator It(this); It; ++It)
    {
        FReceivedPacketView ReceivedPacket;
        FInPacketTraits& ReceivedTraits = ReceivedPacket.Traits;
        bool bOk = It.GetCurrentPacket(ReceivedPacket);

        // ...

如何解析

客户端
if (MyServerConnection->RemoteAddr->CompareEndpoints(*FromAddr))

Connection->ReceivedRawPacket((uint8*)ReceivedPacket.DataView.GetData(), ReceivedPacket.DataView.NumBytes());

void UIpConnection::ReceivedRawPacket(void* Data, int32 Count)
{
    UE_CLOG(SocketError_RecvDelayStartTime > 0.0, LogNet, Log,
            TEXT("UIpConnection::ReceivedRawPacket: Recovered from socket errors. %s Connection"), ToCStr(Describe()));

    // We received data successfully, reset our error counters.
    SocketError_RecvDelayStartTime = 0.0;

    // Set that we've gotten packet from the server, this begins destruction of the other elements.
    if (Resolver->IsAddressResolutionEnabled() && !Resolver->IsAddressResolutionComplete())
    {
        // We only want to write this once, because we don't want to waste cycles trying to clean up nothing.
        Resolver->NotifyAddressResolutionConnected();
    }

    Super::ReceivedRawPacket(Data, Count);
}

void UNetConnection::ReceivedRawPacket( void* InData, int32 Count )

ReceivedPacket(Reader);

上一篇
下一篇