- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
传统的 C/S 架构下,Client 和 Server 通常会建立一条抽象的 Connection,用来进行两端的通信。 UE 的官方文档中提供了 Client 连接到 Server 的 示例 ,简单来说分为如下几步:
./Binaries/Win64/<PROJECT_NAME>Server.exe -log
./Binaries/Win64/<PROJECT_NAME>Client.exe 127.0.0.1:7777 -WINDOWED -ResX=800 -ResY=450
默认情况下,专用服务器在 localhost Ip 地址( 127.0.0.1 )的端口 7777 处监听。可以添加命令行参数 -port=<PORT_NUMBER> ,更改专用服务器的端口。如果要更改服务器正在使用的端口,则还需要更改将客户端连接到服务器时的端口.
Client 连接到 Server 的前提是 Server 启动完毕,监听完毕端口,准备好接收连接了。UE 中监听的核心接口如下:
bool UWorld::Listen( FURL& InURL );
其接口核心参数为一个 FURL ,UE 中会根据启动参数和配置等构建一个 FURL,其结构如下 (只展示部分变量):
//URL structure.
USTRUCT()
struct FURL
{
// Optional hostname, i.e. "204.157.115.40" or "unreal.epicgames.com", blank if local.
UPROPERTY()
FString Host;
// Optional host port.
UPROPERTY()
int32 Port;
// Map name, i.e. "SkyCity", default is "Entry".
UPROPERTY()
FString Map;
// Options.
UPROPERTY()
TArray<FString> Op;
}
可以看到里面有关键的 Host 和 Port 等信息。 Listen 接口具体做了什么呢?
UEngine:: CreateNamedNetDriver
创建 NetDriver,主要驱动网络同步 UNetDriver::InitListen
解析 FURL,监听端口 UNetDriver
,显然它是一个比较重要的网络管理类,这里简单看下其结构
可以看到主要负责:
同时其派生了不同的类,如:
UWebSocketNetDriver
,可以在 UE4中使用 WebSocket 协议进行网络通信
可以看到其实和普通的 C++ 创建 TCP C/S 连接类似,最终都是创建一个 Socket 并且 Bind 到指定端口.
客户端启动之后,也是类似的流程,创建 NetDriver 驱动网络相关的流程,对比 Server,其多了一个 UPendingNetGame 的对象。 UPendingNetGame 类是一个用于处理网络游戏连接过程的类。它在客户端尝试连接到服务器时创建,并在连接成功或失败后销毁.
关于 UPendingNetGame 。
用处: UPendingNetGame 主要负责处理客户端与服务器之间的连接流程。主要功能包括: a. 处理连接请求:客户端向服务器发起连接请求时,UPendingNetGame 负责处理这个请求,包括创建套接字连接、发送握手请求等。 b. 加载关卡:在连接过程中,若服务器需要客户端加载一个关卡,UPendingNetGame 负责处理这个请求,包括加载关卡资源、同步关卡状态等。 c. 状态同步:在连接过程中,UPendingNetGame 负责与服务器进行状态同步,包括玩家数据、游戏规则等。 d. 错误处理:若连接过程中出现错误,如超时、被拒绝等,UPendingNetGame 负责处理这些错误,通知用户并做出相应处理 。
创建与销毁: a. 创建:当客户端尝试连接到服务器时,会创建一个 UPendingNetGame 实例。 b. 销毁:当客户端成功连接到服务器并完成状态同步后,UPendingNetGame 完成其任务并被销毁。如果连接过程中出现错误,如超时、被拒绝等, UPendingNetGame 也会在处理完错误后被销毁 。
Client 的初始化流程大致如下:
Server 端上 PacketHandler 处理的数据包的结构如下:
/**
* Represents a view of a received packet, which may be modified to update Data it points to and Data size, as a packet is processed.
* Should only be stored as a local variable within functions that handle received packets.
**/
struct FReceivedPacketView
{
/** View of packet data, with Num() representing BytesRead - can reassign to point elsewhere, but don't use to modify packet data */
TArrayView<const uint8> Data;
/** Receive address for the packet */
TSharedPtr<FInternetAddr> Address;
/** Error if receiving a packet failed */
ESocketErrors Error;
};
Server 监听完端口之后就要处理客户端发过来的连接请求,由于是 UDPSocket,所以只需要简单的 Bind + RecvFrom 就能接收数据了。其主流程主要由 NetDriver 的 TickDispatch 驱动,如下:
UIpNetDriver::TickDispatch
UIpNetDriver::AdvanceCurrentPacket
FPacketIterator::ReceiveSinglePacket
迭代器收包
FReceiveThreadRunnable::Run
本身是生产者,可以将 ReceiveQueue 理解为一个数据中间件,IpNetDriver 的 TickDispatch 则是消费者,一直消费 ReceiveQueue 的数据 SocketReceiveThreadRunnable
线程中一直使用 FSocket::RecvFrom
(抽象接口,大部分情况下都是为 FSocketBSD::RecvFrom
)接收数据,其底层实现就是使用 recvfrom
这个操作系统接口
SocketReceiveThreadRunnable 默认是没有打开的,官方说明如下 : // If the cvar is set and the socket subsystem supports it, create the receive thread. CVarNetIpNetDriverUseReceiveThread.GetValueOnAnyThread() != 0 && SocketSubsystem->IsSocketWaitSupported() 。
首先 Server 需要检查这个 Packet 是否已经有连接了,这里引出一个问题,Server 端是如何管理和查询 Connection 的?主要是通过解析 Packet 的 Address,在 UNetDriver 中查询缓存地址映射关系.
// 声明
class UNetDriver {
TMap<TSharedRef<const FInternetAddr>, UNetConnection*, FDefaultSetAllocator, FInternetAddrConstKeyMapFuncs<UNetConnection*>> MappedClientConnections;
}
// 使用
const TSharedRef<const FInternetAddr> FromAddr = ReceivedPacket.Address.ToSharedRef();
UNetConnection** Result = MappedClientConnections.Find(FromAddr);
接下来是处理 Packet 。
UIpNetDriver::ProcessConnectionlessPacket
PacketHandler::IncomingConnectionless
校验 Packet 正确性
PacketHandler::Incoming_Internal
HandlerComponent
对包进行处理 StatelessConnectHandlerComponent::IncomingConnectionless
处理无连接的 Packet
StatelessConnectHandlerComponent::ParseHandshakePacket
检查是否为握手包,根据 Packet 时间戳确定是否是 bInitialConnect StatelessConnectHandlerComponent::SendConnectChallenge
StatelessConnectHandlerComponent::HasPassedChallenge
校验 UIpConnection
UIpConnection::InitRemoteConnection
这里 初始化连接,给客户端发送 NMT_Hello
包,开始正式的握手流程 ,这里开始有一个状态机来驱动连接过程
EClientLoginState::Type::LoggingIn
FNetworkNotify::NotifyAcceptedConnection
通知接收连接 UNetDriver::AddClientConnection
添加 UIpConnection
关于 Challenge Challenge 消息是 Unreal Engine 4(UE4)中的一种网络消息,用于在客户端和服务器之间进行身份验证。在 UE4 中,客户端和服务器之间的通信是通过一种称为 Unreal Network Protocol(简称 UNet)的协议实现的。UNet 通过在客户端和服务器之间发送各种类型的网络消息来管理通信.
在 UE4 中,当客户端第一次连接到服务器时,服务器会向客户端发送一个 Challenge 消息,其中包含一个随机生成的 Challenge 令牌。客户端必须将这个 Challenge 令牌使用预共享密钥(PSK)进行签名,并将签名后的结果发送回服务器。服务器会验证签名是否正确,如果正确,则表示客户端是一个合法的用户,并将向客户端发送一个 ChallengeAck 消息,其中包含服务器的签名和一些其他的验证信息。客户端必须验证 ChallengeAck 消息是否正确,并将消息发送回服务器,以便进行最终的身份验证.
关于 NMT_Hello 可以看到收到客户端连接包之后,除了回复正常的 Ack 包之外,会主动给客户端发送一个 NMT_Hello 包,这里的 NMT_Hello 是一个枚举。UE4 中 NMT 开头的枚举是指 NetworkMessageTypes,是 Unreal Engine 4(UE4)中用于管理网络消息类型的一组枚举。在 UE4 中,网络消息是通过一种称为 Unreal Network Protocol(简称 UNet)的协议进行传输和管理的。UNet 通过在客户端和服务器之间发送各种类型的网络消息来管理通信.
通过接收不同的 NMT 消息,从而在客户端服务器连接过程中,不同阶段执行不同的操作,比如当前收到这个消息应该加载地图或者创建 PlayerController.
至此大致梳理完了 Client 和 Server 的握手流程:
UPendingNetGame
在正式连接前驱动握手过程 PacketHandler
的 HandlerComponent 中的 StatelessConnectHandlerComponent
,其负责整个握手过程,此外 PacketHandler 的 HandlerComponent 可以挂载各种组件来支持对数据包的处理,比如 RSA,加密解密等
握手过程中显然有丢包的可能,在 CS 握手过程中,大致发送的 Packet 如下:
Client 主要发送两个包,Handshake 和 ChallengeResponse,当 Client 没有收到回应时,对应阶段在 StatelessConnectHandlerComponent::Tick 都会有一个重发机制。参考代码如下:
void StatelessConnectHandlerComponent::Tick(float DeltaTime)
{
if (Handler->Mode == Handler::Mode::Client)
{
// ... 省略一些代码
if (LastSendTimeDiff > 1.0)
{
if (State == Handler::Component::State::UnInitialized)
{
NotifyHandshakeBegin();
}
else if (State == Handler::Component::State::InitializedOnLocal && LastTimestamp != 0.0)
{
SendChallengeResponse(LastSecretId, LastTimestamp, LastCookie);
}
}
}
大致如下:
握手完毕后就要准备一些 Gameplay 层的相关操作,比如加载地图等,Packet 对于应用层还是太底层了,UE 为此引入了 Bunch 和 Channel 的概念 。
首先 Bunch 和 Packet 的关系如下:
Bunch 分为 FInBunch 和 FOutBunch,根据这个名字可以看出分别对应收到的 Bunch 结构和 发送的 Bunch 结构,其继承链如下:
FInBunch 的结构如下:
class ENGINE_API FInBunch : public FNetBitReader
{
public:
// 省略一些字段
int32 PacketId; // Note this must stay as first member variable in FInBunch for FInBunch(FInBunch, bool) to work
FInBunch * Next;
UNetConnection * Connection; // 属于哪个 Connection
int32 ChIndex; // channel 的下标
int32 ChType; // channel 的类型
FName ChName; // channel 的名称
int32 ChSequence; // Channel 的 Seqid
uint8 bOpen:1; // 是否是 Channel 的首包
uint8 bClose:1; // 是否是 Channel 的结束包
uint8 bDormant:1; // 是否处于休眠
uint8 bIsReplicationPaused:1; // 复制同步是否被暂停了
uint8 bReliable:1; // 是否为可靠的 Bunch
uint8 bPartial:1; // 该 Bunch 是否被拆分
uint8 bPartialInitial:1; // 是不是分片传输中的第一个 Bunch
uint8 bPartialFinal:1; // 是不是分片传输中的最后一个 Bunch
}
FOutBunch 的结构如下:
class ENGINE_API FOutBunch : public FNetBitWriter
{
public:
// 省略一些字段
FOutBunch * Next;
UChannel * Channel;
double Time;
int32 ChIndex;
int32 ChType;
FName ChName;
int32 ChSequence;
int32 PacketId;
uint8 ReceivedAck:1; // 标记这个数据包是否已经被确认,以避免重复发送
uint8 bOpen:1;
uint8 bClose:1;
uint8 bDormant:1;
uint8 bReliable:1;
uint8 bPartial:1; // Not a complete bunch
uint8 bPartialInitial:1; // The first bunch of a partial bunch
uint8 bPartialFinal:1; // The final bunch of a partial bunch
}
Bunch 的信息中,除了一些分包相关的信息,最主要的便是 Channel 相关的信息了,比如这个 Bunch 属于哪个 Channel?Channel 的类型是什么?那么什么是 Channel ?其用处是什么?
UE 中,Channel 主要分为三种类型:
- 连接建立和断开 :UControlChannel会处理网络连接建立和断开的消息。例如,当客户端与服务器建立连接时,UControlChannel会发送和接收连接请求和响应,以便双方建立通信。同样,当连接断开时,UControlChannel会负责发送断开通知,通知另一方连接已关闭。
- 心跳检测 :为了确保连接保持活跃,UControlChannel会定期发送和接收心跳消息。这些消息用于检测双方是否仍在线,以便在一方掉线时及时处理连接断开事件。
- 通道管理 :UControlChannel负责处理通道的打开和关闭。例如,当需要创建一个新的UActorChannel以传输游戏对象数据时,UControlChannel会发送相应的打开通道请求。同样,当某个通道不再需要时,UControlChannel会负责发送关闭通道请求。
- 控制消息 :UControlChannel还可以处理其他一些控制消息,如暂停、恢复游戏等。这些消息通常对游戏逻辑产生一定影响,但主要用于维护游戏状态和连接。
Client :Client 上 Channel 的创建接口为 UNetDriver::CreateInitialCilentChannels ,其实就是在 InitNetDriver 的时候就创建好了 Channel 。
Server :Server 上 Channel 的创建时机如下:
基本上都是在握手过程中就创建好了 Channel。其关系如下:
Server 端在 InitRemoteConnection 之后,会执行 UNetConnection::SetExpectedClientLoginMsgType(NMT_Hello) ,表示等待 Client 端发送 NMT_Hello 的消息,而 Client 端发送该消息的时机就在握手完毕之后。 Client 端在调用 BeginHandshake 的时候,会传入一个 Delegates,Handshake 完毕之后会调用 Delegates. Broadcast,通知握手完毕,绑定了该 Delegate 的接口都会被执行,大致如下:
// 握手完毕的回调
void UPendingNetGame::InitNetDriver() {
// 省略一些代码
// 发起握手,传入握手完毕的回调
ServerConn->Handler->BeginHandshaking( FPacketHandlerHandshakeComplete::CreateUObject(this, &UPendingNetGame::SendInitialJoin));
}
// SendInit
void UPendingNetGame::SendInitialJoin() {
// 省略一些代码
// 发送 NMT_Hello
FNetControlMessage<NMT_Hello>::Send(ServerConn, IsLittleEndian, LocalNetworkVersion, EncryptionToken);
}
因此握手完毕后,Client 端就会调用 UPendingNetGame::SendInitialJoin ,发送 NMT_Hello 给 Server 端。 这里还有个问题,如何确定这个 Message 会发送给 ControlChannel ?实际上这里由 FNetControlMessage<>::Send 接口处理,其内部实现会直接发送一个 FControlChannelOutBunch ,该 Bunch 会直接使用 Channel[0] 初始化,Channel[0] 默认情况下就是 ControlChannel.
Server 端处理 Bunch 的 CallStack 如下:
其大致流程如下:
ReceivedBunch
(不同的 Channel 会各自重写该接口) UWorld::NotifyControlMessage
接口 Client 端登录过程中主要处理 ControlMessage 的接口为 UPendingNetGame::NotifyControlMessage 。
UPendingNetGame::LoadMapCompleted
-> UPendingNetGame::SendJoin
AGameModeBase::Login
个人将 UE 中,Client 和 Server 建立连接到进入游戏中的过程分为了 2 步:
最后此篇关于UnrealEngine-网络同步之连接篇的文章就讲到这里了,如果你想了解更多关于UnrealEngine-网络同步之连接篇的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
这与 Payubiz payment gateway sdk 关系不大一体化。但是,主要问题与构建项目有关。 每当我们尝试在模拟器上运行应用程序时。我们得到以下失败: What went wrong:
我有一个现有的应用程序,其中包含在同一主机上运行的 4 个 docker 容器。它们已使用 link 命令链接在一起。 然而,在 docker 升级后,link 行为已被弃用,并且似乎有所改变。我们现
在 Internet 模型中有四层:链路 -> 网络 -> 传输 -> 应用程序。 我真的不知道网络层和传输层之间的区别。当我读到: Transport layer: include congesti
很难说出这里要问什么。这个问题模棱两可、含糊不清、不完整、过于宽泛或夸夸其谈,无法以目前的形式得到合理的回答。如需帮助澄清此问题以便重新打开,visit the help center . 关闭 1
前言: 生活中,我们在上网时,打开一个网页,就可以看到网址,如下: https😕/xhuahua.blog.csdn.net/ 访问网站使用的协议类型:https(基于 http 实现的,只不过在
网络 避免网络问题降低Hadoop和HBase性能的最重要因素可能是所使用的交换硬件,在项目范围的早期做出的决策可能会导致群集大小增加一倍或三倍(或更多)时出现重大问题。 需要考虑的重要事项:
网络 网络峰值 如果您看到定期的网络峰值,您可能需要检查compactionQueues以查看主要压缩是否正在发生。 有关管理压缩的更多信息,请参阅管理压缩部分的内容。 Loopback IP
Pure Data 有一个 loadbang 组件,它按照它说的做:当图形开始运行时发送一个 bang。 NoFlo 的 core/Kick 在其 IN 输入被击中之前不会发送其数据,并且您无法在 n
我有一台 Linux 构建机器,我也安装了 minikube。在 minikube 实例中,我安装了 artifactory,我将使用它来存储各种构建工件 我现在希望能够在我的开发机器上做一些工作(这
我想知道每个视频需要多少种不同的格式才能支持所有主要设备? 在我考虑的主要设备中:安卓手机 + iPhone + iPad . 对具有不同比特率的视频进行编码也是一种好习惯吗? 那里有太多相互矛盾的信
我有一个使用 firebase 的 Flutter Web 应用程序,我有两个 firebase 项目(dev 和 prod)。 我想为这个项目设置 Flavors(只是网络没有移动)。 在移动端,我
我正在读这篇文章Ars article关于密码安全,它提到有一些网站“在传输之前对密码进行哈希处理”? 现在,假设这不使用 SSL 连接 (HTTPS),a.这真的安全吗? b.如果是的话,你会如何在
我试图了解以下之间的关系: eth0在主机上;和 docker0桥;和 eth0每个容器上的接口(interface) 据我了解,Docker: 创建一个 docker0桥接,然后为其分配一个与主机上
我需要编写一个java程序,通过网络将对象发送到客户端程序。问题是一些需要发送的对象是不可序列化的。如何最好地解决这个问题? 最佳答案 发送在客户端重建对象所需的数据。 关于java - 不可序列化对
所以我最近关注了this有关用 Java 制作基本聊天室的教程。它使用多线程,是一个“面向连接”的服务器。我想知道如何使用相同的 Sockets 和 ServerSockets 来发送对象的 3d 位
我想制作一个系统,其中java客户端程序将图像发送到中央服务器。中央服务器保存它们并运行使用这些图像的网站。 我应该如何发送图像以及如何接收它们?我可以使用同一个网络服务器来接收和显示网站吗? 最佳答
我正在尝试设置我的 rails 4 应用程序,以便它发送电子邮件。有谁知道我为什么会得到: Net::SMTPAuthenticationError 534-5.7.9 Application-spe
我正在尝试编写一个简单的客户端-服务器程序,它将客户端计算机连接到服务器计算机。 到目前为止,我的代码在本地主机上运行良好,但是当我将客户端代码中的 IP 地址替换为服务器计算机的本地 IP 地址时,
我需要在服务器上并行启动多个端口,并且所有服务器套接字都应在 socket.accept() 上阻塞。 同一个线程需要启动客户端套接字(许多)来连接到特定的 ServerSocket。 这能实现吗?
我的工作执行了大约 10000 次以下任务: 1) HTTP 请求(1 秒) 2)数据转换(0.3秒) 3)数据库插入(0.7秒) 每次迭代的总时间约为 2 秒,分布如上所述。 我想做多任务处理,但我
我是一名优秀的程序员,十分优秀!