概述
客户端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
这里面有初始化 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
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
- IPNetDriver 派生类驱动
-
Connection
- ClientConnection
- ServerConnection
-
LocalPlayer
-
通道
- ControlChannel 客户端只有一个
- VoiceChannel 客户端只有一个
- ActorChannel
- ActorChannel ...
- ActorChannel ...
-
通信
- 数据包
- InBunch
- NGUID
- Channel 信息
- OutBunch
- InBunch
- 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
这里客户端给服务器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
如果版本不一致
就发送要升级
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
又又又到客户端
case NMT_Welcome:
收到数据
FNetControlMessage
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
由于这个定义 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);