转载请标明出处:http://www.cnblogs.com/zblade/
一、概要
捣鼓UE4也有两个多月了,从这儿开始,逐步探究UE4中经典的值复制,RPC两种同步方式。想要弄到其复制和调用的原理,就得从根本的网络层开始捋,优秀的文章有:
风蚀之月-UE4网络底层概览-https://blog.ch-wind.com/ue4-network-overview/
知乎专栏-Exploring In UE4- https://zhuanlan.zhihu.com/p/34723199
各位如果想大概了解网络的基本流程,可以认真学习一下这两篇文章,深入浅出讲解的很明晰。
我也是在拜读了这几篇文章后,下定认真查看UE4的源码,从根本上了解UE4的网络原理,实现方案,优化方案等。
整个系列应该比较长,就跟着自己的学习笔记,慢慢探究UE4的网络设计和实现,当作一个别样的旅程
二、基本网络类
在游戏开发中,基本的网络都是通用的,来自于Socket, 在UE中,使用的是其子类BSDSocket(伯克利套接字),而对于BSDSocket的使用,又是封装在SocketSubSystem中,又在其基础上封装了一层SocketSubSystemModule来做模块管理。
所以简单的总结,其基本的层次为:
Socket->BSDSocket->SocketSubSystem->SocketSubSystemModule
那么本文就介绍一下其基本的网络类,为后面的网络连接过程提供一个基本的知识印象。
2.1 基本网络类 Socket
这个类是最基本的类,自然就是和网络最底层挂钩:IP 和 Port,主要用于实现对IP 和 port的连接,用于网络连接的绑定,监听,数据收发。
2.1.1 基本接口
构建接口和析构函数:
inline FSocket() : SocketType(SOCKTYPE_Unknown), SocketDescription(TEXT("")), IsNotSendEncrypt(false), IsNotRecvDecrypt(false) { }
其中SocketType分为三类: None/TCP/UDP, 多一嘴,UE是以UDP为主的。IsNotSendEncrypt/IsNotRecvDecrypt 为收发加密相关。
2.1.2 绑定相关接口
virtual bool Bind(const FInternetAddr& Addr) = 0; virtual bool Connect(const FInternetAddr& Addr) = 0; virtual bool Close() = 0;
2.1.3 数据接受相关接口
virtual bool SetReceiveBufferSize(int32 Size, int32& NewSize) = 0; virtual bool Recv(uint8* Data, int32 BufferSize, int32& BytesRead, ESocketReceiveFlags::Type Flags = ESocketReceiveFlags::None) virtual bool RecvFrom(uint8* Data, int32 BufferSize, int32& BytesRead, FInternetAddr& Source, ESocketReceiveFlags::Type Flags = ESocketReceiveFlags::None)
2.1.4 数据发送相关接口
virtual bool SetSendBufferSize(int32 Size, int32& NewSize) = 0; virtual bool SendTo(const uint8* Data, int32 Count, int32& BytesSent, const FInternetAddr& Destination); virtual bool Send(const uint8* Data, int32 Count, int32& BytesSent);
总结:这个类就是一个基类,大部分实现还是要看子类的override实现。
2.2 基本网络类 BSDSocket
继承自Socket的类,相关接口实现更为完备,类似的类为FSocketBSDIPv6, 就在这儿一起处理了。
2.2.1 基本构造和析构接口
FSocketBSD(SOCKET InSocket, ESocketType InSocketType, const FString& InSocketDescription, ISocketSubsystem * InSubsystem) : FSocket(InSocketType, InSocketDescription) , Socket(InSocket) , LastActivityTime(0) , SocketSubsystem(InSubsystem) , SendEncryptBufLength(DEFAULT_CRYPT_BUFFER_LEN) , RecvEncryptBufLength(DEFAULT_CRYPT_BUFFER_LEN) { SendEncryptBuf = new char[DEFAULT_CRYPT_BUFFER_LEN]; RecvEncryptBuf = new char[DEFAULT_CRYPT_BUFFER_LEN]; } /** * Destructor. * * Closes the socket if it is still open */ virtual ~FSocketBSD() { Close(); delete[] SendEncryptBuf; SendEncryptBuf = nullptr; delete[] RecvEncryptBuf; RecvEncryptBuf = nullptr; }
比较简单,相对父类而言,多了收发buff的构建和析构回收操作。
2.2.2 其他接口
也就是场景的数据收发,Bind, 连接,监听,Wait几个基本操作,也提供了多路操作。相对而言,重点关注几个变量:
* UpdateActivity() 这个函数用于更新最新的存活时间戳,应该是用于心跳检测相关的;
* FDateTime LastActivityTime: 最新的存活时间,用于心跳检测,同上;
* ISocketSubsystem* SocketSubsytem 注释中就说明了,只想创建其的SocketSubSystem, 所以BSDSocket的创建来自于SocketSubsystem的触发,后面会分析到。
2.2.3 具体的实现
在对于的cpp中会有很多具体的实现,我就重点归纳几个要点:
- Close: 内部会调用底层的closesocket来关闭socket
- Bind: 内部会调用底层的bind来Bind socket
- Connect: 同上,内部会调用底层的connect来connect socket
- Listen: 调用底层的listen接口,返回bool表示是否在监听
- SendTo: 如果发送的数据超过当前的buffer,会删除当前Buffer, 扩大2倍用来存储和加密数据, 然后执行发送操作sendto,如果发送数据量超过0,就默认为发送成功,刷新LastActivityTime
- Send: 同上,只是调用的接口为send
- RecvFrom: 接收接口,如果接收数据size超过当前Buffer,会删除当前buffer,扩大2倍用来存放接收的数据,对于接收到的数据,会进行是否接收数量(Byte)小于0且不为BLOCK状态,如果是则说明当前接收的数据不正确,返回。对于正确接受的数据,会进行解密操作,存放在对应Buffer中, 刷新LastActivityTime
- Recv: 同上,只是调用的是recv接口,上面调用的是recvfrom接口,刷新LastActivityTime
- GetConnectionState: 获取当前Socket的状态,根据LastActivityTime,如果当前连接正常,同时数据收发间隔超过5s,则判断是否需要重新刷新链接时间LastActivityTime,还是断开连接
总结:这是项目中具体使用的BSDSocket,重点关注收发数据接口。
2.3 基本网络类 SocketSubsystem
前面介绍了Socket和BSDSocket,相当于介绍了工具,那么如何使用这个工具?UE给我们将这个工具做了一层封装,也就是SocketSubSystem,对于封装好的接口,我们想要调用,一般不会直接去调用,而是包装了一层,构建一个工具的管理器来进行相关的操作,避免和工具直接打交道。当然,UE还在SocketSubsystem的基础上更进一步的封装了Module来管理,后面会分析。
在实际的项目中,SocketSubsystem只是一个积累,具体的SocketSubsystem,还需要根据对于的平台来进行创建,目前理出的主要的继承关系如下:
ISocketSubsystem---FSocketSubsystemBSDCommon---FSocketSubsystemBSD ---FPS4SocketSubsystem ---FSwitchSocketSubsystem ---FSocketSubsystemWindows ---FSocketSubsystemLinux ---FSocketSubsystemMac ---FSocketSubsystemAndroid ---FSocketSubsystemBSDIPv6 ---FSocketSubsystemIOS ---FSocketSubsystemAndroidv6 ---FSocketSubsystemSteam ---FSocketSubsystemHTML5
基本关系:在游戏启动的时候,会先加载SocketSubsystemModule, 然后触发对应平台的SocketSubsystem的创建,再执行对应的BSDSocket的创建,所以接下来看看SocketSubsystemModule的加载过程。
2.4 基本网络类 SocketSubsystemModule的加载
在游戏启动,或者编辑器启动的时候(注意编辑器下不是执行play,而是编辑器启动的时候),会执行Module的加载。在UE4中,各个模块都会统一到Module设计中,对应于网络这部分,就是SocketSubsystemModule,下面来逐步跟随追踪其加载过程:
2.4.1 加载FOnlineSubsystemModule
首先会执行加载FOnlineSubsystemModule,在执行完该Module的加载后,会执行一次StartupModule(Module的虚函数接口,每个Module各自实现),在FOnlineSubsystemModule中实现为:
在加载完成后,会执行GetOnlineSubsystem的操作:
第一次加载的时候,OSSFactory为空,则会触发执行CreateSubsystem的操作:
会在FOnlineFactoryNull中执行创建FOnlineSubsystemNull,然后执行初始化:
初始化的关键是: new FOnlineIdentityNull的创建:
里面会执行Login,而Login会进入到ISocketSubsystem:Get操作:
最终来到了创建静态SocketSubsystem的操作:
静态第一次创建的时候,执行加载SocketSubsystemModule,则其会执行:
对应编辑器为Windows,则会执行:
创建完后的register:
总结:至此,完成了从FOnlineSubsystemModule的初始化,到SocketSubsystemModule的加载,然后完成SocketSubsystem的创建和注册的流程。
本文就写到这儿,主要分析了基本的网络类,网络system以及相关Module的加载,下一篇会重点分析NetDriver 和Connection 这两个UE中的重头类。