网易首页 > 网易号 > 正文 申请入驻

《Exploring in UE4》网络同步原理深入(上)

0
分享至

这是侑虎科技第520篇文章,感谢作者Jerish供稿。欢迎转发分享,未经作者授权请勿转载。如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨。(QQ群:793972859)

作者主页:https://www.zhihu.com/people/chang-xiao-qi-86,作者也是U Sparkle活动参与者,UWA欢迎更多开发朋友加入U Sparkle开发者计划,这个舞台有你更精彩!

前言

UE同步是一块比较复杂而庞大的模块,里面设计到了很多设计思想、技巧、技术。我这里主要是从同步的流程分析,以同步的机制为讲解核心,给大家描述里面是怎么同步的,会大量涉及UE同步模块的底层代码,稍微涉及一点计算机网络底层(Socket、TCP、UDP)相关的知识。

PS:如果只是想知道怎么使用同步,建议阅读这篇文章:https://zhuanlan.zhihu.com/p/34721113 (关于网络同步的理解与思考[概念理解])。网络同步深入这块我拆成了两篇文章,上篇主要围绕基本架构与概念、网络连接简建立、Actor同步来讲解,下篇针对属性同步、RPC、可靠数据传输等细节做进一步分析。(下篇文章后续会进行推送,记得关注哦~)

目录(上篇)

一、基本概念与架构特点(更新)

二、通信的基本流程(更新)

1、数据包格式

三、连接的建立(更新)

1、服务器网络模块初始化流程

2、客户端网络模块初始化流程

3、服务器与客户端建立连接流程

四、Actor的同步细节

1、组件(子对象)同步

一、基本概念与架构特点

UE网络是一个相当复杂的模块,这篇文章主要是针对Actor同步,属性同步,RPC等大致的阐述一些流程以及关键的一些类。这里我尽可能将我的理解写下来。

在UE里面有一些和同步相关的概念与类,这里逐个列举一下,并做解释:

基本网络通信:

  • NetDriver

    网络驱动,实际上我们创建使用的是他的子类IPNetDriver,里面封装了基本的同步Actor的操作,初始化客户端与服务器的连接,建立属性记录表,处理RPC函数,创建Socket,构建并管理当前Connection信息,接收数据包等等基本操作。NetDriver与World一一对应,在一个游戏世界里面只存在一个NetDriver。UE里面默认的都是基于UDPSocket进行通信的。

  • Connection

    表示一个网络连接。服务器上,一个客户端到一个服务器的一个连接叫一个ClientConnection。在客户端上,一个服务器到一个客户端的连接叫一个ServerConnection。

  • LocalPlayer

    本地玩家,一个客户端的窗口ViewportClient对应一个LocalPlayer,Localplayer在各个地图切换时不会改变。

  • Channel

    数据通道,每一个通道只负责交换某一个特定类型特定实例的数据信息。

    1、ControlChannel:客户端服务器之间发送控制信息,主要是发送接收连接与断开的相关消息。在一个Connection中只会在初始化连接的时候创建一个该通道实例。

    2、VoiceChannel:用于发送接收语音消息。在一个Connection中只会在初始化连接的时候创建一个该通道实例。

    3、ActorChannel:处理Actor本身相关信息的同步,包括自身的同步以及子组件,属性的同步,RPC调用等。每个Connection连接里的每个同步的Actor都对应着一个ActorChannel实例。

    常见的只有这3种:枚举里面还有FileChannel等类型,不过没有使用。

  • PlayerController

    玩家控制器,对应一个LocalPlayer,代替本地玩家控制游戏角色。同时对应一个Connection,记录了当前的连接信息,这和RPC以及条件属性复制都是密切相关的。另外,PlayerController记录他本身的ViewTarget(就是他控制的Character),通过与ViewTarget的距离(太远的Actor不会同步)来进行其他Actor的同步处理。

  • World

    游戏世界,任何游戏逻辑都是在World里面处理的,Actor的同步也受World控制,World知道哪些Actor应该同步,保存了网络通信的基础设施NetDriver。

  • Actor

    在世界存在的对象,没有坐标。UE4大部分的同步功能都是围绕Actor来实现的。

  • Dormant

    休眠,对于休眠的Actor不会进行网络同步。

底层通信:

  • Packet

    从Socket读出来/输出的数据,一个Packet里面可能有多个Bunch数据或者Ack数据。

  • Bunch

    一个Bunch里面主要记录了Channel信息,NGUID。同时包含其他的附属信息如是否是完整的Bunch,是否是可靠等,可以简单理解为一个从逻辑上层分发下来的同步数据包,该数据包的数据可能不完整,Bunch分为属性Bunch以及RPCBunch。继承自FNetBitWriter InBunch:从Channel接收的数据流串 ,UNetConnection::ReceivedPacket的时候创建 OutBunch:从Channel产生的数据流串,UActorChannel::ReplicateActor()的时候创建。

  • Ack

    Ack是与Bunch同级别概念的网络数据串,用于实现UDP的可靠数据传输。

  • FBitWriter

    字节流书写器,可以临时写入比特数据用于传输,存储等,继承自FArchive。

  • FSocket

    所有平台Socket的基类。FSocketBSD:使用winSocket的Socket封装。

  • UPackageMap

    生成与维护Object与NGUID的映射,负责Object的序列化。每一个Connection对应一个UPackageMap。

  • PacketHandler

    网络包预处理,比如加密,前向纠错,握手等。里面有一个或多个HandlerComponents来执行特殊的数据处理。目前内置的包括加密组件RSA,AES,以及必备的握手组件StatelessConnectHandlerComponent。

(Packet与Bunch的区别:Bunch是Packet子集,Packet里面可能不包含Bunch信息,只包含Ack数据。)

属性同步相关:

  • FObjectReplicator

    属性同步的执行器,每个Actorchannel对应一个FObjectReplicator,每一个FObjectReplicator对应一个对象实例。设置ActorChannel通道的时候会创建出来。

  • FRepState

    针对每个连接同步的历史数据,记录同步前用于比较的Object对象信息,存在于FObjectReplicator里面。

  • FRepLayOut

    同步的属性布局表,记录所有当前类需要同步的属性,每个类或者RPC函数有一个。

  • FRepChangedPropertyTracker

    属性变化轨迹记录,一般在同步Actor前创建,Actor销毁的时候删掉。

  • FReplicationChangelistMgr

    存放当前的Object对象,保存属性的变化历史记录。

架构特点:

  • 客户端服务器共用一套代码;

  • 服务器为游戏逻辑服务器,单个服务器为核心,多个客户端连接;

  • 默认通信协议为UDP(应用层实现数据可靠的UDP);

  • 收发UDP数据包都在主线程(GameThread)执行。

二、通信的基本流程

如果我们接触过网络通信,应该了解只要知道对方的IP地址以及端口号,服务器A上进程M_1_Server可以通过套接字向客户端B上的进程M_1_Client发送消息,大致的效果如下:

图2-1 远程进程通信图

而对于UE4进程内部服务器Server与客户端Client1的通信,与上面的模型基本相似。

图2-2 UE4远程进程通信图

那这个里面的Channel是什么意思呢?简单理解起来就是一个通信轨道。为了实现规范与通信效率,我们的一个服务器针对某个对象定义了Channel通道,这个通道只与客户端对应的Channel通道互相发送与接收消息。这个过程抽象起来与TCP/UDP套接字的传输过程很像,套接字是在消息发送到进程前就进行处理,来控制客户端进程A只会接收到服务器对应进程A的消息,而这里是在UnrealEditor.exe进程里面处理,让通道1只接收到另一端通道1发送的消息。

上面的只是针对一个服务器到客户端的传输流程,那么如果是多个客户端呢?

图2-3 Channel通信图

每一个客户端叫做一个Connection,如图,就是一个Server连接到两个客户端的效果。对于每一个客户端,都会建立起一个Connection。在服务器上这个Connection叫做ClientConnection,对于客户端这个Connection叫做ServerConnection。每一个Channel都会归属于一个Connection,这样这个Channel才知道他对应的是哪个客户端上的对象。

接下来我们继续细化,图中的Channel只标记了1、2、3,那么实际上都有哪些Channel?这些Channel对应的都是什么对象?其实,在第一部分的概念里我已经列举了常见的3中Channel,分别是ControlChannel,ActorChannel以及VoiceChannel。一般来说,ControlChannel与VoiceChannel在游戏中只存在一个,而ActorChannel则对应每一个需要同步的Actor,所以我们再次细化上面的示意图:

图2-4 Connection下的Channel通信图

到这里我们基本上就了解了UE4的基本通信架构了,下面我们进一步分析网络传输数据的流程。首先我们要知道,UE4的数据通信是建立在UDP-Socket的基础上的,与其他的通信程序一样,我们需要对Socket的信息进行封装发送以及接收解析。流程如图所示:

图2-5 发送同步信息流程图

图2-6 接收同步信息流程图

数据包格式概述

关于网络包的封装与解析,主要涉及到Bunch、RawBunch、Packet等概念,可以参考第一部分的基本概念去理解,最后一部分可靠数据传输还会讲到。每次通道最后发出去的包都成为一个Packet,Packet里面的信息是通过Bunch或者Ack塞进去的(也就是UNetConnection的Sendbuffer),里面可能有多个bunch与Ack,数量取决于你Flush的频率,默认情况下是在NetDriver Tick的时候或者buffer到达一定值的时候发送。

在Channel、Connection类里面有许多具体的函数通过处理Bunch/Packet实现了可靠传输,文章的最后一部分会做分析。

三、连接的建立

前面的内容已经提到过,UE的网通通信是基于Channel的,而ControlChannel就是负责

控制客户端与服务器建立连接的通道,所以客户端与服务器的连接信息都是通过UControlChannel执行NotifyControlMessage函数处理的。下面首先从服务器与客户端的网络模块初始化说起,然后描述二者连接建立的详细流程:

从创建GameInstance开始,首先创建NetDriver来驱动网络初始化,进而根据平台创建对应的Socket,之后在World里面监听客户端的消息。

图3-1 服务器网络模块初始化流程图

客户端前面的初始化流程与服务器很相似,也是首先构建NetDriver,然后根据平台创建对应的Socket,同时他还会创建一个到服务器的ServerConnection。由于客户端没有World信息,所以要使用一个新的类来检测并处理连接信息,这个类就是UpendingNetGame。

图3-2 客户端网络模块初始化流程图

二者都完成初始化后,客户端就会开始发送一个Hello类型的ControlChannel消息给服务器(上面客户端初始化最后一步)。服务器接收到消息之后开始处理,然后会根据条件再给客户端发送对应的消息,如此来回处理几个回合,完成连接的建立,详细流程参考下图:

(该流程是本地局域网的连接流程,与在线查找服务器列表并加入有差异。)

图3-3 客户端服务器连接建立流程图

Actor的同步可以说是UE4网络里面最大的一个模块了,里面包括属性同步,RPC调用等,这里为了方便我将他们拆成了3个部分来分别叙述。

有了前面的描述,我们已经知道NetDiver负责整个网络的驱动,而ActorChannel就是专门用于Actor同步的通信通道。

这里对Actor同步做一个比较细致的描述:服务器在NetDiver的TickFlush里面,每一帧都会去执行ServerReplicateActors来同步Actor的相关内容。在这里我们需要做以下处理:

1、获取到所有连接到服务器的ClientConnections,首先获取引擎每帧可以同步的最大Connection的数量,超过这个限制的忽略。然后对每个Connection几乎都要进行下面所有的操作。

2、找到要同步的Actor,只有被放到World.NetworkActors里面的Actor才会被考虑,Actor在被Spawn时候就会添加到这个NetworkActors列表里面。(新的版本里面已经把需要同的ACtor放到了NetDriver的NetworkObjects列表里面了。)

3、找到客户端玩家控制的角色ViewTarget(ViewTaget与摄像机绑定在一起),这个角色的位置是决定其他Actor是否同步的关键。

4、验证Actor,对于要销毁的以及所有权Role为ROLE_NONE的Actor不会同步。

5、是否到达Actor同步时间,Actor的同步是有一定频率的,Actor身上有一个NetUpdateTime,每次同步前都会通过下面这个公式来计算下一次Actor的同步时间,如果没有到达这个时间就会放弃本次同步。Actor->NetUpdateTime = World->TimeSeconds + FMath::SRand() * ServerTickTime + 1.f/Actor->NetUpdateFrequency;

6、如果这个Actor设置OnlyRelevantToOwner,那么就会放到一个特殊的列表里面OwnedConsiderList然后只同步给属于他的客户端。否则会把Actor放到ConsiderList里面。

7、对于休眠状态的Actor不会进行同步,对于要进入休眠状态的Actor也要特殊处理关闭同步通道。

8、查看当前的Actor是否有通道Channel,如果没有,还要看看Actor是否已经加在了场景,没有加载就跳过同步。

9、接第8个条件——没有Channel的情况下,还会执行Actor::IsNetRelevantFor判断是否网络相关,对于不可见的或者太远的Actor会返回false,不会同步。

10、Actor的同步数量可能非常大,所以有必要对所有的Actor进行一个优先级的排列 。

处理完上面的逻辑后会对优先级表里的所有Actor进行排序。

11、排序后,如果连接没有加载此 actor 所在的关卡,则关闭通道(如果存在)并继续

每1秒钟调用一次AActor::IsNetRelevantFor,确定Actor是否与连接相关,如果不相关的时间达到5秒钟,则关闭通道 。

如果要同步的Actor没有ActorChannel就给其创建一个并绑定Actor,执行同步并更新:NetUpdateTime = Actor->GetWorld()->TimeSeconds + 0.2f * FMath::FRand();

如果此连接出现饱和剩下的Actor会根据连接相关时间判断是否在下一个时钟更新。

12、执行UActorChannel::ReplicateActor执行真正的Actor同步以及内部数据的同步,这里会将Actor(PackageMap->SerializeNewActor),Actor子对象以及其属性序列化(ReplicateProperties)封装到OutBunch并发送给客户端 。

(备注:我们当前版本下面的逻辑都是写在UNetDriver::ServerReplicateActors里面,4.12以后的UE4已经分别把Connection预处理,获取同步Actor列表,优先级处理等逻辑封装到单独的函数里了,详见ServerReplicateActors_BuildConsiderlist,ServerReplicateActors_PrioritizedActors,ServerReplicateActors_ProsessPrioritizedActors等函数。优先级排序规则是什么?答案是按照是否有Controller,距离以及是否在视野。通过FActorPriority构造代码可以定位到APawn::GetNetPriority,这里面会计算出当前Actor对应的优先级,优先级越高同步越靠前,是否有Controller的权重最大。)

总之,大体上Actor同步的逻辑就是在TickFlush里面去执行ServerReplicateActors,然后进行前面说的那些处理。最后对每个Actor执行ActorChannel::ReplicateActor将Actor本身的信息,子对象的信息,属性信息封装到Bunch并进一步封装到发送缓存中,最后通过Socket发送出去。

下面是服务器的同步Actor的发送Bunch堆栈:(代码修改过,与UE默认的有些不同。)

图4-1 服务器同步Actor堆栈图

下面描述客户端是如何接收到服务器同步过来的Actor的。首先客户端TickDispatch检测服务器的消息,收到消息后通过Connection以及Channel进行解析,最后一步解析出完整数据的操作在UActorChannel::ProcessBunch执行,在这个函数里面:

1、如果发现当前的ActorChannel对应的Actor为NULL,就对当前的Bunch进行反序列化Connection->PackageMap->SerializeNewActor(Bunch, this, NewChannelActor);解析出Actor的内容并执行PostInitializeComponents。如果Actor不为NULL,跳过这一步。(参考下图4-2堆栈)

2、随后根据Bunch信息找到同步过来的属性值并对当前Actor对应的属性进行赋值。

3、最后执行PostNetInit调用Actor的BeginPlay。(参考下图堆栈)

下面截取了客户端接收到同步Actor并初始化的调用堆栈:

图4-2 客户端接收并序列化同步的Actor堆栈图

图4-3 客户端初始化同步过来Actor堆栈图

从上面的描述来看,基本上我们可以很容易的分析出当前的Actor是否被同步,比如在UActorChannel::ReceivedBunch里面打个断点,看看当前通道里有没有你要的Actor就可以了。

组件(还有其他子对象)是挂在Actor上面的,所以组件的同步与Actor同步是紧密相连的,当一个Actor进行同步的时候会判断所有的子对象是否标记了Replicate,如果标记了,就对其以及其属性进行同步。

这些子对象同步方式(RPC等)也与Actor相差无几,实际上他们想要同步,需要借助ActorChannel创建自己的FObjectReplicator以及属性同步的相关数据结构。简单来说,就是一个Actor身上的组件同步需要借用这个Actor的通道来进行。下面3段代码是服务器序列化子对象准备发送的逻辑:

//UActorChannel::ReplicateActor() DataChannel.cpp
// The Actor
WroteSomethingImportant |= ActorReplicator->ReplicateProperties( Bunch, RepFlags );
// 子对象的同步操作
WroteSomethingImportant |= Actor->ReplicateSubobjects(this, &Bunch, &RepFlags);
//ActorReplication.cpp
boolAActor::ReplicateSubobjects(UActorChannel *Channel, FOutBunch *Bunch, FReplicationFlags *RepFlags)
{
check(Channel);
check(Bunch);
check(RepFlags);
bool WroteSomething = false;
for (int32 CompIdx =0; CompIdx < ReplicatedComponents.Num(); ++CompIdx )
{
UActorComponent * ActorComp = ReplicatedComponents[CompIdx].Get();
//如果组件标记同步
if (ActorComp && ActorComp->GetIsReplicated())
{
WroteSomething |= ActorComp->ReplicateSubobjects(Channel, Bunch, RepFlags); // Lets the component add subobjects before replicating its own properties.检测组件否还有子组件
WroteSomething |= Channel->ReplicateSubobject(ActorComp, *Bunch, *RepFlags); // (this makes those subobjects 'supported', and from here on those objects may have reference replicated) 同步该组件
}
}
return WroteSomething;
}
//DataChannel.cpp
boolUActorChannel::ReplicateSubobject(UObject *Obj, FOutBunch&Bunch, constFReplicationFlags&RepFlags)
{
if ( !Connection->Driver->GuidCache->SupportsObject( Obj ) )
{
FNetworkGUID NetGUID = Connection->Driver->GuidCache->AssignNewNetGUID_Server(Obj ); //Make sure he gets a NetGUID so that he is now 'supported'
}
bool NewSubobject = false;
if (!ObjectHasReplicator(Obj))
{
Bunch.bReliable = true;
NewSubobject = true;
}
//组件的属性同步需要先在当前的ActorChannel里面创建新的FObjectReplicator
bool WroteSomething = FindOrCreateReplicator(Obj).Get().ReplicateProperties(Bunch, RepFlags);
if (NewSubobject && !WroteSomething)
{
......
}
return WroteSomething;
}

下面一段代码是客户端接收服务器同步过来的子对象逻辑:

// void UActorChannel::ProcessBunch( FInBunch & Bunch )DataChannel.cpp
// 该函数前面的代码主要是是进行反序列化当前Actor的相关操作
while ( !Bunch.AtEnd() && Connection != NULL&& Connection->State != USOCK_Closed )
{
bool bObjectDeleted = false;
//当前通道的Actor以及反序列化成功,这里开始继续从Bunch里面寻找子对象进行反序列化
//如果当前Actor没有子组件,这里返回的就是Actor自身
......
TSharedRef<FObjectReplicator>& Replicator = FindOrCreateReplicator( RepObj );
bool bHasUnmapped = false;
// 找到当前子对象(或当前Actor)的Replicator以后,这里开始进行属性值的读取了
if ( !Replicator->ReceivedBunch( Bunch, RepFlags, bHasUnmapped ) )
{
......
}
......
}

前面Actor同步有提到,当从ActorChannel解析Bunch信息的时候就可以尝试对该数据流进行Actor的反序列化。从这段代码可以进一步看出,Actor反序列化之后会立刻开始判断Bunch里面是否存在其子对象,如果存在还会进一步读取子对象同步过来的属性值。如果没有子对象,就读取自身同步过来的属性。

关于子组件的反序列化还分为两种情况。要想理解这两种情况,还需要清楚两个概念——动态组件与静态组件。

对于静态组件:一旦一个Actor被标记为同步,那么这个Actor身上默认所挂载的组件也会随Actor一起同步到客户端(也需要序列化发送)。什么是默认挂载的组件?就是C++构造函数里面创建的默认组件或者在蓝图里面添加构建的组件。所以,这个过程与该组件是否标记为Replicate是没有关系的。

对于动态组件:就是我们在游戏运行的时候,服务器创建或者删除的组件。比如,当玩家走进一个洞穴时,给洞穴里面的火把生成一个粒子特效组件,然后同步到客户端上,当玩家离开的时候再删除这个组件,玩家的客户端上也随之删除这个组件。

对于动态组件,我们必须要设置他的Replicate属性为true,即通过函数 AActorComponent::SetIsReplicated(true)来操作。而对于静态组件,如果我们不想同步组件上面的属性,我们就没有必要设置Replicate属性。下面截取了函数ReadContentBlockHeader部分代码来区分这两种情况:

//静态组件,不需要客户端Spawn
FNetworkGUID NetGUID;
UObject * SubObj = NULL;
Connection->PackageMap->SerializeObject(Bunch, UObject::StaticClass(), SubObj, &NetGUID );
//动态组件,需要在客户端Spawn出来
FNetworkGUID ClassNetGUID;
UObject * SubObjClassObj = NULL;
Connection->PackageMap->SerializeObject(Bunch, UObject::StaticClass(), SubObjClassObj, &ClassNetGUID );

我们在这两段代码看到了FNetworkGUID的使用,因为这里涉及到UObject的引用(指针)同步。对于不同端的同一个对象,他们的内存地址肯定是不同的,那服务器上指向A的指针同步到客户端上如何也能正确的指向A呢?这就需要通过FNetworkGUID来解析,具体细节在下一节属性同步里面分析。

文末,再次感谢Jerish的分享,如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨。

QQ群:793972859

也欢迎大家来积极参与U Sparkle开发者计划,简称“US”,代表你和我,代表UWA和开发者在一起!

UWA DAY 2019完整议题公布!

近期精彩回顾
【博物纳新】Impostor Baker开源库评测!
【厚积薄发】如何规划UI图标图集!
【厚积薄发】骨骼数和骨骼层级数的美术规范!
【博物纳新】XR合辑推荐&新功能上线!

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
剧本杀“恋爱陪伴”爆火,主持人压在女玩家身上亲吻,还有人现场解皮带,同行玩家中有14岁女生,一商家却声称不接待未成年人

剧本杀“恋爱陪伴”爆火,主持人压在女玩家身上亲吻,还有人现场解皮带,同行玩家中有14岁女生,一商家却声称不接待未成年人

大风新闻
2026-09-19 17:36:12
7万亿砸向“六张网”,下一轮基建机会在哪

7万亿砸向“六张网”,下一轮基建机会在哪

吴晓波频道
2026-08-10 17:05:27
“为了点补贴,把孩子未来毁了!”单亲妈妈奇葩操作,网友:他长大肯定恨你!

“为了点补贴,把孩子未来毁了!”单亲妈妈奇葩操作,网友:他长大肯定恨你!

林林先生
2026-09-19 10:00:06
亚运会:国乒开门红!首战孙颖莎、王曼昱休息,陈熠打头阵轰出11-1

亚运会:国乒开门红!首战孙颖莎、王曼昱休息,陈熠打头阵轰出11-1

全言作品
2026-09-20 09:31:20
问界这次的瓜,真的有点大啊!

问界这次的瓜,真的有点大啊!

芝麻科技讯官方号
2026-09-18 00:06:07
释延鲁近况:管着两万多人的武校,一年收入超过三点七八亿元,妻子是第三者上位

释延鲁近况:管着两万多人的武校,一年收入超过三点七八亿元,妻子是第三者上位

草莓解说体育
2026-09-20 12:21:59
2026年最大颜值骗局:被吹成神颜的孙千,到底哪里美?

2026年最大颜值骗局:被吹成神颜的孙千,到底哪里美?

时事娱闻杂记
2026-09-20 11:06:14
何超琼清仓9.5亿,没去澳门,在深圳开了一家没有赌场的美高梅

何超琼清仓9.5亿,没去澳门,在深圳开了一家没有赌场的美高梅

未来已来风云变幻
2026-09-14 19:47:56
中方拒给面子,日高官破防,话音刚落遭到晴天霹雳,中国专治嘴硬

中方拒给面子,日高官破防,话音刚落遭到晴天霹雳,中国专治嘴硬

乌克兰小静
2026-09-19 09:08:41
不上班,不创业,43岁刘翔结婚十年坚决不要孩子,每天和老婆街头遛狗

不上班,不创业,43岁刘翔结婚十年坚决不要孩子,每天和老婆街头遛狗

体坛风之子
2026-09-20 05:00:05
离大谱!印度卡车司机南加撞死2人后逃逸,全美通缉半年,竟还开着大卡车穿州过市

离大谱!印度卡车司机南加撞死2人后逃逸,全美通缉半年,竟还开着大卡车穿州过市

大洛杉矶LA
2026-09-20 06:45:45
不公平!国乒被曝禁用1号球台训练,孙颖莎回应!亚运女团首战莎莎曼昱轮休,三小将轰出11-1取开门红!

不公平!国乒被曝禁用1号球台训练,孙颖莎回应!亚运女团首战莎莎曼昱轮休,三小将轰出11-1取开门红!

好乒乓
2026-09-20 13:02:35
哇塞!6亿豪阵四巨头!西部超级新贵球队诞生

哇塞!6亿豪阵四巨头!西部超级新贵球队诞生

篮球实战宝典
2026-09-19 20:29:38
费城恐怖屋 82翁死后搜出百万不雅影像 58名女受害者中5人恐已身亡

费城恐怖屋 82翁死后搜出百万不雅影像 58名女受害者中5人恐已身亡

环球趣闻分享
2026-09-19 14:10:10
属兔人请注意 十天后你的家宅 将迎来两场风暴

属兔人请注意 十天后你的家宅 将迎来两场风暴

户外阿崭
2026-09-19 00:33:15
中国男篮VS伊朗,开球时间公布,郭士强不会下课,全力带队争铜牌

中国男篮VS伊朗,开球时间公布,郭士强不会下课,全力带队争铜牌

体育大学僧
2026-09-19 11:47:27
女人见面的最高礼仪,就是穿裙子,这是对你的重视,对约会的重视

女人见面的最高礼仪,就是穿裙子,这是对你的重视,对约会的重视

加油丁小文
2026-09-01 07:09:58
电梯大局已定!老旧小区最难的,真正麻烦不是换新,是这3个难题

电梯大局已定!老旧小区最难的,真正麻烦不是换新,是这3个难题

白米饭怎么吃
2026-09-19 21:37:29
影视飓风Tim反掰iPhone Duo致闪屏,称并未用力,苹果回应:博主单台设备测评片面

影视飓风Tim反掰iPhone Duo致闪屏,称并未用力,苹果回应:博主单台设备测评片面

齐鲁壹点
2026-09-17 17:02:26
游客称在黄山被猴子围攻咬伤大腿,伤口深3厘米缝了6针要打8针狂犬疫苗,景区:猴子是野生动物,能做的有限,被攻击的游客可拨打救援电话

游客称在黄山被猴子围攻咬伤大腿,伤口深3厘米缝了6针要打8针狂犬疫苗,景区:猴子是野生动物,能做的有限,被攻击的游客可拨打救援电话

大风新闻
2026-09-19 22:28:04
2026-09-20 14:00:49
侑虎科技UWA incentive-icons
侑虎科技UWA
游戏/VR性能优化平台
1624文章数 987关注度
往期回顾 全部

科技要闻

谷歌承认:AI模型“黑”进3家公司

头条要闻

河南60岁高龄产妇生下女儿 2年前曾因失独生下个儿子

头条要闻

河南60岁高龄产妇生下女儿 2年前曾因失独生下个儿子

体育要闻

2026亚运会开幕式 胡明轩吴愉担任旗手

娱乐要闻

马伊琍被曝有新恋情,约会被拍

财经要闻

伪造票据洗白药品 回流药黑色产业链曝光

汽车要闻

大五座布局 车身尺寸不变 吉利银河M8申报信息公布

态度原创

时尚
艺术
手机
亲子
房产

关上浴室门,把世界留在门外。

艺术要闻

大疆全球新总部图纸公示,这造型真敢建!

手机要闻

卢伟冰详解小米18 Pro系列:性能、背屏、屏幕、影像大升级

亲子要闻

小宝宝和小鸭子第一次在水中玩耍,没想到是这种反应

房产要闻

海南公积金,又出新政!

无障碍浏览 进入关怀版