本文主要剖析 UE5 中客户端是如何与DS建立连接,构建基础 Channel,以及无状态握手流程。
连接建立 服务端监听流程 UGameInstance::EnableListenServer
调用 UWorld::Listen
去创建 UNetDriver 对象。
UNetDriver 默认有两个子类, IpNetDriver 和 DemoNetDriver 后面一个用于回放。
根据要创建的 NetDriver 名, 查找并构造该 Driver, 并存入 World->Context.ActiveNetDrivers,这里面很多地方会用到,比如需要设置某个 Actor 冻结时,就需要通知各个 NetDriver。
1 2 3 4 5 6 7 8 UNetDriver* CreateNetDriver_Local (UEngine* Engine, FWorldContext& Context, FName NetDriverDefinition, FName InNetDriverName) { Definition = Engine->NetDriverDefinitions.FindByPredicate (FindNetDriverDefPred); UClass* NetDriverClass = StaticLoadClass (UNetDriver::StaticClass (), nullptr , *Definition->DriverClassName.ToString (), nullptr , LOAD_Quiet); ReturnVal = NewObject <UNetDriver>(GetTransientPackage (), NetDriverClass); new (Context.ActiveNetDrivers) FNamedNetDriver (ReturnVal, Definition); }
随后将所有的 NetActor 加入到相同 NetDriverName 的几个集合里去,
1 2 3 4 void UNetDriver::SetWorld (class UWorld* InWorld) { GetNetworkObjectList ().AddInitialObjects (InWorld, this ); }
开始监听端口 NetDriver->InitListen( this, InURL, bReuseAddressAndPort, Error )
此处使用的是 IpNetDriver::InitListen。
1 2 3 4 5 6 7 8 9 10 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 (); }
NetDriver 的 InitBase 仅仅只是处理一下参数,创建 NetConnectionClass 对象(注意这里不是NetConnection实例),在此处是IpConnectionClass,以及若有 ReplicationDrvier 则将其构造出来,Replication Graph 插件就是基于此实现的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 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 ; } ISocketSubsystem* SocketSubsystem = GetSocketSubsystem (); const int32 BindPort = bInitAsClient ? GetClientPort () : URL.Port; 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); }
绑定端口的时候会不断递增端口号,直到绑定成功,然后将 Socket 存储到BoundSockets
,注意服务端只能存在一个 绑定的Socket。
重要的是 InitConnectionlessHandler
,创建 PacketHandler,这个 Handler 能添加一系列的 Handler,所有 Packet 都会先进这个 handler 过一遍筛子,里面默认添加了一个 StatelessConnectHandlerComponent
用于无状态网络连接。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 void UNetDriver::InitConnectionlessHandler () { ConnectionlessHandler = MakeUnique <PacketHandler>(&DDoS); if (ConnectionlessHandler.IsValid ()) { ConnectionlessHandler->NotifyAnalyticsProvider (AnalyticsProvider, AnalyticsAggregator); ConnectionlessHandler->Initialize (UE::Handler::Mode::Server, MAX_PACKET_SIZE, true , nullptr , nullptr , NetDriverDefinition); TSharedPtr<HandlerComponent> NewComponent = ConnectionlessHandler->AddHandler (TEXT ("Engine.EngineHandlerComponentFactory(StatelessConnectHandlerComponent)" ), true ); StatelessConnectComponent = StaticCastSharedPtr <StatelessConnectHandlerComponent>(NewComponent); if (StatelessConnectComponent.IsValid ()) { StatelessConnectComponent.Pin ()->SetDriver (this ); } ConnectionlessHandler->InitializeComponents (); } }
StatelessConnectComponent.Pin()->SetDriver(this);
此处会触发 UpdateSecret
操作,更新两个 Secret,用于后续握手,当然这个值是会随着时间更新的,之所以需要两个,是因为更新很频繁,需要存一下旧值来比对,主要是为了避免重放攻击。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 void StatelessConnectHandlerComponent::UpdateSecret () { LastSecretUpdateTimestamp = Driver != nullptr ? Driver->GetElapsedTime () : 0.0 ; if (ActiveSecret == 255 ) { HandshakeSecret[0 ].AddUninitialized (SECRET_BYTE_SIZE); HandshakeSecret[1 ].AddUninitialized (SECRET_BYTE_SIZE); TArray<uint8>& CurArray = HandshakeSecret[1 ]; for (int32 i=0 ; i<SECRET_BYTE_SIZE; i++) { CurArray[i] = FMath::Rand () % 255 ; } ActiveSecret = 0 ; } else { ActiveSecret = (uint8)!ActiveSecret; } TArray<uint8>& CurArray = HandshakeSecret[ActiveSecret]; for (int32 i=0 ; i<SECRET_BYTE_SIZE; i++) { CurArray[i] = FMath::Rand () % 255 ; } }
到此为止,服务端的监听网络流程结束,简单来说就是根据平台创建 NetDriver,准备好 NetConnectionClass 但没有用它创建实例,因为此时还没有客户端连上来,创建 Socket,然后创建 PacketHandler 并为它添加 StatelessConnectHandlerComponent 用于无状态网络连接,避免重放攻击。
调用栈如下:
1 2 3 4 5 UWorld::Listen () UIpNetDriver::InitListen () UIpNetDriver::InitBase () UNetDriver::InitBase () UNetDriver::InitConnectionlessHandler ()
客户端连接流程 UEngine::Browse
处理网络连接的基础内容,包括对 URL 的处理,最后会创建一个 UPendingNetGame
对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 EBrowseReturnVal::Type UEngine::Browse ( FWorldContext& WorldContext, FURL URL, FString& Error ) { if ( URL.IsInternal () && GIsClient ) { if (WorldContext.World () && ShouldShutdownWorldNetDriver ()) { ShutdownWorldNetDriver (WorldContext.World ()); } WorldContext.PendingNetGame = NewObject <UPendingNetGame>(); WorldContext.PendingNetGame->Initialize (URL); WorldContext.PendingNetGame->InitNetDriver (); } }
它也会和服务端一样创建一个 NetDriver,只不过它的名字叫 PendingNetDriver
而不是 GameNetDriver
,接着初始化连接。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 void UPendingNetGame::InitNetDriver () { if (!GDisallowNetworkTravel) { if (GEngine->CreateNamedNetDriver (this , NAME_PendingNetDriver, NAME_GameNetDriver)) { NetDriver = GEngine->FindNamedNetDriver (this , NAME_PendingNetDriver); } if ( NetDriver->InitConnect ( this , URL, ConnectionError ) ) { FNetDelegates::OnPendingNetGameConnectionCreated.Broadcast (this ); ULocalPlayer* LocalPlayer = GEngine->GetFirstGamePlayer (this ); if (LocalPlayer) { LocalPlayer->PreBeginHandshake (ULocalPlayer::FOnPreBeginHandshakeCompleteDelegate::CreateWeakLambda (this , [this ]() { BeginHandshake (); })); } else { BeginHandshake (); } } } }
这一部分逻辑和服务端几乎一样,根据URL设置变量,创建 Socket,以及准备好 NetConnectionClass,但它是立刻根据 NetConnectionClass 来构造一个 UIpConnection
对象,而服务端是等到握手认证通过后才创建。 CreateInitialClientChannels
还会初始化 Channels 数据,包括可靠传输之类的内容,一条连接的 Channel 个数最多为 DefaultMaxChannelSize(32767)
。
InitLocalConnection
会一路调用到 UNetConnection::InitBase
中,构造 PacketHandler、StatelessConnectHandlerComponent 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 bool UIpNetDriver::InitConnect ( FNetworkNotify* InNotify, const FURL& ConnectURL, FString& Error ) { ISocketSubsystem* SocketSubsystem = GetSocketSubsystem (); 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 ; } ServerConnection = NewObject <UNetConnection>(GetTransientPackage (), NetConnectionClass); ServerConnection->InitLocalConnection (this , SocketPrivate.Get (), ConnectURL, USOCK_Pending); Resolver->InitConnect (ServerConnection, SocketSubsystem, GetSocket (), ConnectURL); CreateInitialClientChannels (); return true ; }
这里还要注意一点,每条 Connection 都会创建一个 PackageMapClient,用于序列化 Actor,主要功能是把 GUID 和 指针绑定起来。
1 2 3 4 5 6 7 8 9 10 11 void UNetConnection::InitBase (UNetDriver* InDriver,class FSocket* InSocket, const FURL& InURL, EConnectionState InState, int32 InMaxPacket, int32 InPacketOverhead) { UPackageMapClient* PackageMapClient = NewObject <UPackageMapClient>(this , PackageMapClass); if (ensure (PackageMapClient != nullptr )) { PackageMapClient->Initialize (this , Driver->GuidCache); PackageMap = PackageMapClient; } }
创建客户端 Channels,需要注意 UE5 多了个 DataStream Channel,用于 Iris 新的网络功能。
1 2 3 4 5 [/Script/Engine.NetDriver] +ChannelDefinitions=(ChannelName=Control, ClassName=/Script/Engine.ControlChannel, StaticChannelIndex=0 , bTickOnCreate=true , bServerOpen=false , bClientOpen=true , bInitialServer=false , bInitialClient=true ) +ChannelDefinitions=(ChannelName=Voice, ClassName=/Script/Engine.VoiceChannel, StaticChannelIndex=1 , bTickOnCreate=true , bServerOpen=true , bClientOpen=true , bInitialServer=true , bInitialClient=true ) +ChannelDefinitions=(ChannelName=DataStream, ClassName=/Script/Engine.DataStreamChannel, StaticChannelIndex=2 , bTickOnCreate=true , bServerOpen=true , bClientOpen=true , bInitialServer=true , bInitialClient=true ) +ChannelDefinitions=(ChannelName=Actor, ClassName=/Script/Engine.ActorChannel, StaticChannelIndex=-1 , bTickOnCreate=false , bServerOpen=true , bClientOpen=false , bInitialServer=false , bInitialClient=false )
1 2 3 4 5 6 7 8 9 10 11 12 13 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); } } } }
初始化完连接,此时就可以开始握手了,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 void UPendingNetGame::BeginHandshake () { UNetConnection* ServerConn = NetDriver->ServerConnection; if (ServerConn->Handler.IsValid ()) { ServerConn->Handler->BeginHandshaking ( FPacketHandlerHandshakeComplete::CreateUObject (this , &UPendingNetGame::SendInitialJoin)); } else { SendInitialJoin (); } }
调用栈如下:
1 2 3 4 5 6 7 8 UEngine::Browse () UPendingNetGame::InitNetDriver () UIpNetDriver::InitConnect () UIpConnection::InitLocalConnection () UIpConnection::InitBase () UNetConnection::InitBase () UNetDriver::CreateInitialClientChannels () UPendingNetGame::BeginHandshake ()
客户端总体流程也是先创建 NetDriver,这里叫 PendingNetDriver,和服务端不同的是会立即创建 IpConnection,毕竟客户端不需要省资源,创建 PacketHandler 和 StatelessConnectHandlerComponent 存于 Connection(而服务端存于 NetDriver) 用于和服务端无状态连接,同时创建必要的 Channels,目前是 Control、Voice、DataStream 三个Channel,最后开始握手。
握手 服务端、客户端建立好 Socket 之后,就要开始握手了,握手包有以下几类。
1 2 3 4 5 6 7 8 9 10 11 12 enum class EHandshakePacketType : uint8{ InitialPacket = 0 , Challenge = 1 , Response = 2 , Ack = 3 , RestartHandshake = 4 , RestartResponse = 5 , VersionUpgrade = 6 , Last = VersionUpgrade };
PacketHandler::BeginHandshaking
最终会通知到其下的各个 Handler Component 去执行握手,在这里当然只有 StatelessConnectHandlerComponent
。
1 2 3 4 void StatelessConnectHandlerComponent::NotifyHandshakeBegin () { SendInitialPacket (static_cast <EHandshakeVersion>(CurrentHandshakeVersion)); }
InitialPacket 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 void StatelessConnectHandlerComponent::SendInitialPacket (EHandshakeVersion HandshakeVersion) { 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 ) ; 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); } } }
设置 RawSend
避免被 Handler 处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 void StatelessConnectHandlerComponent::SendToServer (EHandshakeVersion HandshakeVersion, EHandshakePacketType PacketType, FBitWriter& Packet) { if (UNetConnection* ServerConn = (Driver != nullptr ? Driver->ServerConnection : nullptr )) { CapHandshakePacket (Packet, HandshakeVersion); Handler->SetRawSend (true ); if (Driver->IsNetResourceValid ()) { FOutPacketTraits Traits; Driver->ServerConnection->LowLevelSend (Packet.GetData (), Packet.GetNumBits (), Traits); } Handler->SetRawSend (false ); } }
此处发送 InitialPacket
握手包时,用的也是 UDP,因此存在丢失的风险,解决办法是通过StatelessConnectHandlerComponent::Tick
每帧都发一次握手包,后续握手流程都是通过这种方式进行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 void StatelessConnectHandlerComponent::Tick (float DeltaTime) { if (Handler->Mode == UE::Handler::Mode::Client) { if (State != UE::Handler::Component::State::Initialized && LastClientSendTimestamp != 0.0 ) { double LastSendTimeDiff = FPlatformTime::Seconds () - LastClientSendTimestamp; if (LastSendTimeDiff > UE::Net::HandshakeResendInterval) { const bool bRestartChallenge = Driver != nullptr && ((Driver->GetElapsedTime () - LastChallengeTimestamp) > MIN_COOKIE_LIFETIME); if (bRestartChallenge) { SetState (UE::Handler::Component::State::UnInitialized); } if (State == UE::Handler::Component::State::UnInitialized) { UE_LOG (LogHandshake, Verbose, TEXT ("Initial handshake packet timeout - resending." )); EHandshakeVersion ResendVersion = static_cast <EHandshakeVersion>(CurrentHandshakeVersion); if (!!CVarNetDoHandshakeVersionFallback.GetValueOnAnyThread () && FMath::RandBool ()) { const int32 MinVersion = FMath::Max (MinSupportedHandshakeVersion, CurrentHandshakeVersion - SentHandshakePacketCount); if (MinVersion != CurrentHandshakeVersion) { ResendVersion = static_cast <EHandshakeVersion>(FMath::RandRange (MinVersion, CurrentHandshakeVersion)); } } SendInitialPacket (ResendVersion); } else if (State == UE::Handler::Component::State::InitializedOnLocal && LastTimestamp != 0.0 ) { UE_LOG (LogHandshake, Verbose, TEXT ("Challenge response packet timeout - resending." )); SendChallengeResponse (LastRemoteHandshakeVersion, LastSecretId, LastTimestamp, LastCookie); } } } } else { const bool bConnectionlessHandler = Driver != nullptr && Driver->StatelessConnectComponent.HasSameObject (this ); if (bConnectionlessHandler) { static float CurVariance = FMath::FRandRange (0.f , SECRET_UPDATE_TIME_VARIANCE); if (((Driver->GetElapsedTime () - LastSecretUpdateTimestamp) - (SECRET_UPDATE_TIME + CurVariance)) > 0.0 ) { CurVariance = FMath::FRandRange (0.f , SECRET_UPDATE_TIME_VARIANCE); UpdateSecret (); } } } }
轮到服务端接受握手包,服务端接受 Packet
都在 UIpNetDriver::TickDispatch
中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 void UIpNetDriver::TickDispatch (float DeltaTime) { UNetConnection* Connection = nullptr ; for (FPacketIterator It (this ); It; ++It) { if (Connection == nullptr ) { auto * Result = MappedClientConnections.Find (FromAddr); if (Result != nullptr ) { UNetConnection* ConnVal = *Result; if (ConnVal != nullptr ) { Connection = ConnVal; } else { ReceivedTraits.bFromRecentlyDisconnected = true ; } } } Connection = ProcessConnectionlessPacket (ReceivedPacket, WorkingBuffer); if (Connection != nullptr && !bIgnorePacket) { Connection->ReceivedRawPacket ((uint8*)ReceivedPacket.DataView.GetData (), ReceivedPacket.DataView.NumBytes ()); } } }
ProcessConnectionlessPacket
是用于处理还未有连接的 Packet 包,因为只有完全握手通过之后才会为客户端创建连接。 FPacketIterator
也只是一个简单的迭代器,用于从 Socket 读取内容。
服务端所有收到的握手包都会到 StatelessConnectHandlerComponent::IncomingConnectionless
Challenge 服务端收到 InitialPacket
后 就会发送 Challenge
,此时才会使用服务端的 HandshakeSecret
用当前的时间戳和客户端地址生成一个 Cookie,发送给客户端。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 void StatelessConnectHandlerComponent::SendConnectChallenge (FCommonSendToClientParams CommonParams, uint8 ClientSentHandshakePacketCount) { if (Driver != nullptr ) { const int32 AdjustedSize = GetAdjustedSizeBits (HANDSHAKE_PACKET_SIZE_BITS, CommonParams.HandshakeVersion); FBitWriter ChallengePacket (AdjustedSize + (BaseRandomDataLengthBytes * 8 ) + 1 ) ; BeginHandshakePacket (ChallengePacket, EHandshakePacketType::Challenge, CommonParams.HandshakeVersion, ClientSentHandshakePacketCount, CommonParams.ClientID); double Timestamp = Driver->GetElapsedTime (); uint8 Cookie[COOKIE_BYTE_SIZE]; GenerateCookie (CommonParams.ClientAddress, ActiveSecret, Timestamp, Cookie); ChallengePacket.WriteBit (ActiveSecret); ChallengePacket << Timestamp; ChallengePacket.Serialize (Cookie, UE_ARRAY_COUNT (Cookie)); SendToClient (CommonParams, EHandshakePacketType::Challenge, ChallengePacket); } }
客户端的读取流程也类似,不过要记住 客户端此时是有连接的,所以会直接进入
1 2 3 4 void UIpNetDriver::TickDispatch (float DeltaTime) { Connection->ReceivedRawPacket ((uint8*)ReceivedPacket.DataView.GetData (), ReceivedPacket.DataView.NumBytes ()); }
1 2 3 4 5 6 7 8 9 10 11 void UNetConnection::ReceivedRawPacket ( void * InData, int32 Count ) { if (Handler.IsValid ()) { FReceivedPacketView PacketView; PacketView.DataView = {Data, Count, ECountUnits::Bytes}; EIncomingResult IncomingResult = Handler->Incoming (PacketView); } }
在握手阶段 bConnectionlessPacket
为 true 为服务端流程,为 false 为客户端流程。而握手结束后,双方都会走 Incoming
流程。
1 2 3 4 5 6 7 8 9 10 11 EIncomingResult PacketHandler::Incoming_Internal (FReceivedPacketView& PacketView) { if (PacketView.Traits.bConnectionlessPacket) { CurComponent.IncomingConnectionless (PacketRef); } else { CurComponent.Incoming (PacketRef); } }
Challenge 的 Ack 也是 Challenge 类型的握手包,因此区分方式是通过判断 timestamp 是否小于等于 0 判断是否为 Ack。
ChallengeResponse ChallengeResponse 也只是将服务端发过来的数据重新发回去,让服务端校验。Secret 过期时间为 40秒。
服务端收到 ChallengeResponse 后 若验证 Cookie 通过则保存该 Cookie,用于后续断线重连的校验,同时从 Cookie 中算出 发送和接收序列号,序列号是第一篇讲的用于 Packet 可靠传输的ID,随机化是为了避免攻击。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 void StatelessConnectHandlerComponent::IncomingConnectionless (FIncomingPacketRef PacketRef) { else if (Driver != nullptr ) { bool bChallengeSuccess = false ; const double CookieDelta = Driver->GetElapsedTime () - HandshakeData.Timestamp; const double SecretDelta = HandshakeData.Timestamp - LastSecretUpdateTimestamp; const bool bValidCookieLifetime = CookieDelta >= 0.0 && (MAX_COOKIE_LIFETIME - CookieDelta) > 0.0 ; const bool bValidSecretIdTimestamp = (HandshakeData.SecretId == ActiveSecret) ? (SecretDelta >= 0.0 ) : (SecretDelta <= 0.0 ); if (bValidCookieLifetime && bValidSecretIdTimestamp) { uint8 RegenCookie[COOKIE_BYTE_SIZE]; GenerateCookie (Address, HandshakeData.SecretId, HandshakeData.Timestamp, RegenCookie); bChallengeSuccess = FMemory::Memcmp (HandshakeData.Cookie, RegenCookie, COOKIE_BYTE_SIZE) == 0 ; if (bChallengeSuccess) { if (HandshakeData.bRestartHandshake) { FMemory::Memcpy (AuthorisedCookie, HandshakeData.OrigCookie, UE_ARRAY_COUNT (AuthorisedCookie)); } else { int16* CurSequence = (int16*)HandshakeData.Cookie; LastServerSequence = *CurSequence & (MAX_PACKETID - 1 ); LastClientSequence = *(CurSequence + 1 ) & (MAX_PACKETID - 1 ); FMemory::Memcpy (AuthorisedCookie, HandshakeData.Cookie, UE_ARRAY_COUNT (AuthorisedCookie)); } bRestartedHandshake = HandshakeData.bRestartHandshake; LastChallengeSuccessAddress = Address->Clone (); LastRemoteHandshakeVersion = TargetVersion; CachedClientID = ClientID; if (TargetVersion < MinClientHandshakeVersion && static_cast <uint8>(TargetVersion) >= MinSupportedHandshakeVersion) { MinClientHandshakeVersion = TargetVersion; } SendChallengeAck (FCommonSendToClientParams (Address, TargetVersion, ClientID), HandshakeData.RemoteSentHandshakePacketCount, AuthorisedCookie); } } } }
服务端此时已经通过了握手,为客户端创建连接。
ChallengeAck 客户端收到 ChallengeAck后,也是设置好发送和接收的序列号,用于后续 Packet 可靠传输。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 void StatelessConnectHandlerComponent::Incoming (FBitReader& Packet) { else if (HandshakeData.HandshakePacketType == EHandshakePacketType::Ack && HandshakeData.Timestamp < 0.0 ) { if (!bRestartedHandshake) { UNetConnection* ServerConn = (Driver != nullptr ? ToRawPtr (Driver->ServerConnection) : nullptr ); if (ensure (ServerConn != nullptr )) { int16* CurSequence = (int16*)HandshakeData.Cookie; int32 ServerSequence = *CurSequence & (MAX_PACKETID - 1 ); int32 ClientSequence = *(CurSequence + 1 ) & (MAX_PACKETID - 1 ); ServerConn->InitSequence (ServerSequence, ClientSequence); } FMemory::Memcpy (AuthorisedCookie, HandshakeData.Cookie, UE_ARRAY_COUNT (AuthorisedCookie)); } SetState (UE::Handler::Component::State::Initialized); Initialized (); bRestartedHandshake = false ; SentHandshakePacketCount = 0 ; } }
总结 服务端和客户端都会在启动之后创建 NetDriver,只不过服务端不会创建 NetConnection 因为担心被重放攻击,而客户端无所谓,创建了 NetConnection,之后彼此通过 PacketHandler 中的 StatelessConnectHandlerComponent
进行握手,握手方式主要是通过生成 Cookie,同时由于是 UDP 传输,可能丢包,彼此会通过 tick 不断重发握手包,当彼此握手通过之后,双端都会将 Cookie 保存下来,同时服务端为该客户端创建连接,同时对齐两端的 Packet 发送接收序列号,用于未来的可靠传输。
断线重连 当客户端出现切换网络时,可能会带着新的地址连接至服务端,服务端发现无法根据该地址找到连接,因此会下发重新握手的请求。
1 2 3 4 5 6 7 8 9 10 11 12 13 void StatelessConnectHandlerComponent::IncomingConnectionless (FIncomingPacketRef PacketRef) { else if (bHasValidSessionID) { if (!Packet.IsError () && !PacketRef.Traits.bFromRecentlyDisconnected) { SendRestartHandshakeRequest (FCommonSendToClientParams (Address, static_cast <EHandshakeVersion>(MinSupportedHandshakeVersion), ClientID)); } } }