这是一个独立的 ENet 实现,具有针对 C、C++、C# 和其他语言的修改协议。
特征:
请阅读常见错误,了解可能出现的问题。
要构建本机库,需要适当的软件:
对于桌面平台 CMake 与 GNU Make 或 Visual Studio。
对于移动平台,适用于 Android 的 NDK 和适用于 iOS 的 Xcode。确保所有编译的库都分配给适当的平台和 CPU 架构。
要构建 Nintendo Switch 库,请遵循本指南。
可以使用支持 C# 3.0 或更高版本的任何可用编译平台来构建托管程序集。
您可以从发布部分或 NuGet 获取已编译的库:
ENet-CSharp
包含用于 .NET 环境(.NET Standard 2.1)的带有本机库的编译程序集。
ENet-Unity
包含带有 Unity 原生库的脚本。
强烈建议删除包含二进制文件的文件夹,而不是替换它来升级。
这些软件包仅适用于传统平台:Windows、Linux 和 macOS (x64)。
支持的操作系统版本:
在开始工作之前,应使用ENet.Library.Initialize();
功能。
工作完成后,使用ENet.Library.Deinitialize();
功能。
using ( Host server = new Host ( ) ) {
Address address = new Address ( ) ;
address . Port = port ;
server . Create ( address , maxClients ) ;
Event netEvent ;
while ( ! Console . KeyAvailable ) {
bool polled = false ;
while ( ! polled ) {
if ( server . CheckEvents ( out netEvent ) <= 0 ) {
if ( server . Service ( 15 , out netEvent ) <= 0 )
break ;
polled = true ;
}
switch ( netEvent . Type ) {
case EventType . None :
break ;
case EventType . Connect :
Console . WriteLine ( "Client connected - ID: " + netEvent . Peer . ID + ", IP: " + netEvent . Peer . IP ) ;
break ;
case EventType . Disconnect :
Console . WriteLine ( "Client disconnected - ID: " + netEvent . Peer . ID + ", IP: " + netEvent . Peer . IP ) ;
break ;
case EventType . Timeout :
Console . WriteLine ( "Client timeout - ID: " + netEvent . Peer . ID + ", IP: " + netEvent . Peer . IP ) ;
break ;
case EventType . Receive :
Console . WriteLine ( "Packet received from - ID: " + netEvent . Peer . ID + ", IP: " + netEvent . Peer . IP + ", Channel ID: " + netEvent . ChannelID + ", Data length: " + netEvent . Packet . Length ) ;
netEvent . Packet . Dispose ( ) ;
break ;
}
}
}
server . Flush ( ) ;
}
using ( Host client = new Host ( ) ) {
Address address = new Address ( ) ;
address . SetHost ( ip ) ;
address . Port = port ;
client . Create ( ) ;
Peer peer = client . Connect ( address ) ;
Event netEvent ;
while ( ! Console . KeyAvailable ) {
bool polled = false ;
while ( ! polled ) {
if ( client . CheckEvents ( out netEvent ) <= 0 ) {
if ( client . Service ( 15 , out netEvent ) <= 0 )
break ;
polled = true ;
}
switch ( netEvent . Type ) {
case EventType . None :
break ;
case EventType . Connect :
Console . WriteLine ( "Client connected to server" ) ;
break ;
case EventType . Disconnect :
Console . WriteLine ( "Client disconnected from server" ) ;
break ;
case EventType . Timeout :
Console . WriteLine ( "Client connection timeout" ) ;
break ;
case EventType . Receive :
Console . WriteLine ( "Packet received from server - Channel ID: " + netEvent . ChannelID + ", Data length: " + netEvent . Packet . Length ) ;
netEvent . Packet . Dispose ( ) ;
break ;
}
}
}
client . Flush ( ) ;
}
Packet packet = default ( Packet ) ;
byte [ ] data = new byte [ 64 ] ;
packet . Create ( data ) ;
peer . Send ( channelID , ref packet ) ;
byte [ ] buffer = new byte [ 1024 ] ;
netEvent . Packet . CopyTo ( buffer ) ;
AllocCallback OnMemoryAllocate = ( size ) => {
return Marshal . AllocHGlobal ( size ) ;
} ;
FreeCallback OnMemoryFree = ( memory ) => {
Marshal . FreeHGlobal ( memory ) ;
} ;
NoMemoryCallback OnNoMemory = ( ) => {
throw new OutOfMemoryException ( ) ;
} ;
Callbacks callbacks = new Callbacks ( OnMemoryAllocate , OnMemoryFree , OnNoMemory ) ;
if ( ENet . Library . Initialize ( callbacks ) )
Console . WriteLine ( "ENet successfully initialized using a custom memory allocator" ) ;
使用方法与.NET环境下几乎相同,只是控制台功能必须替换为Unity提供的功能。如果要在游戏循环中调用Host.Service()
,请确保超时参数设置为 0,这意味着非阻塞。此外,通过在播放器设置中启用适当的选项,使 Unity 在后台运行。
最著名的策略是在独立的 I/O 线程中使用 ENet,并利用线程间消息传递技术在线程/任务之间传输数据,而无需任何锁/互斥锁。像环形缓冲区这样的非阻塞队列就是为此目的而设计的。高级抽象和逻辑可以使用工作线程进行并行化,然后与 I/O 线程进行通信,并将消息入队/出队以通过网络发送/接收数据。
一般来说,ENet不是线程安全的,但是如果用户足够小心的话,它的一些功能是可以安全使用的:
Packet
结构及其功能是安全的,直到数据包仅按值跨线程移动并且不使用自定义内存分配器。
Peer.ID
一旦从本机端获得了指向对等点的指针,该 ID 将被缓存在Peer
结构中,以便对分配给该 ID 的对象进行进一步操作。 Peer
结构可以按值跨线程移动,但其功能不是线程安全的,因为内存中的数据可能会被另一个线程中的服务更改。
Library.Time
在内部利用原子原语来管理本地单调时间。
Peer.Send()
函数的标志定义:
PacketFlags.None
不可靠的排序,不保证数据包的传送。
PacketFlags.Reliable
可靠排序,目标对等方必须接收数据包,并且应尝试重新发送,直到数据包被传送。
PacketFlags.Unsequenced
数据包不会与其他数据包一起排序,并且可能会乱序传送。该标志使交付不可靠。
PacketFlags.NoAllocate
数据包不会分配数据,用户必须提供它。应使用PacketFreeCallback
回调来跟踪数据包生命周期。
PacketFlags.UnreliableFragmented
如果数据包超过 MTU,则数据包将被不可靠地分段。默认情况下,超过MTU的不可靠数据包会被分片并可靠传输。该标志应用于明确指示应保持不可靠的数据包。
PacketFlags.Instant
数据包在下一次服务迭代时不会与其他数据包捆绑在一起,而是立即发送。这种传输类型以多路复用效率换取延迟。同一数据包不能用于多个Peer.Send()
调用。
PacketFlags.Unthrottled
排队发送不可靠的数据包不应因限制而被丢弃,并在可能的情况下发送。
PacketFlags.Sent
数据包已从其进入的所有队列发送。
Event.Type
属性的事件类型定义:
EventType.None
在指定的时间限制内没有发生任何事件。
EventType.Connect
由Peer.Connect()
函数发起的连接请求已完成。 Event.Peer
返回成功连接的对等点。 Event.Data
返回用户提供的描述连接的数据,如果没有可用的数据,则返回 0。
EventType.Disconnect
对等点已断开连接。此事件在成功完成由Peer.Disconnect()
函数发起的断开连接时生成。 Event.Peer
返回已断开连接的对等点。 Event.Data
返回用户提供的描述断开连接的数据,如果没有可用的数据,则返回 0。
EventType.Receive
已从对等方收到数据包。 Event.Peer
返回发送数据包的对等点。 Event.ChannelID
指定接收数据包的通道号。 Event.Packet
返回收到的数据包,使用后必须使用Event.Packet.Dispose()
函数销毁该数据包。
EventType.Timeout
对等方已超时。如果对等方超时或由Peer.Connect()
初始化的连接请求超时,则会发生此事件。 Event.Peer
返回一个超时的对等点。
Peer.State
属性的对等状态定义:
PeerState.Uninitialized
对等点未初始化。
PeerState.Disconnected
对等点已断开连接或超时。
PeerState.Connecting
正在进行中的对等连接。
PeerState.Connected
已成功连接对等点。
PeerState.Disconnecting
正在进行中的对等断开连接。
PeerState.Zombie
对等点未正确断开连接。
提供每个应用程序的事件。
AllocCallback(IntPtr size)
通知何时请求分配内存。需要指向新分配的内存的指针。应保留对委托的引用,以免被垃圾收集。
FreeCallback(IntPtr memory)
通知何时可以释放内存。应保留对委托的引用,以免被垃圾收集。
NoMemoryCallback()
在内存不足时发出通知。应保留对委托的引用,以免被垃圾收集。
提供每包事件。
PacketFreeCallback(Packet packet)
在数据包被销毁时发出通知。指示是否确认了可靠的数据包。应保留对委托的引用,以免被垃圾收集。
提供每个主机的事件。
InterceptCallback(ref Event @event, ref Address address, IntPtr receivedData, int receivedDataLength)
在截获原始 UDP 数据包时发出通知。此回调返回的状态代码指示 ENet 应如何处理设置的事件。返回 1 表示服务调度设置的事件。返回 0 表示 ENet 子系统应该处理接收到的数据。返回-1表示有错误。应保留对委托的引用,以免被垃圾收集。
ChecksumCallback(IntPtr buffers, int bufferCount)
通知何时应在发送和接收时为缓冲区计算校验和。此回调返回的值是 64 位校验和。如果两端都启用了校验和机制,ENet 会自动处理数据包的完整性验证。可与ENet.Library.CRC64()
函数一起使用。应保留对委托的引用,以免被垃圾收集。
包含带有匿名主机数据和端口号的结构。
Address.Port
获取或设置端口号。
Address.GetIP()
获取 IP 地址。
Address.SetIP(string ip)
设置 IP 地址。要在本地网络中使用 IPv4 广播,可以将客户端的地址设置为255.255.255.255 。 ENet将自动响应广播并将地址更新为服务器的实际IP。
Address.GetHost()
尝试从地址进行反向查找。返回具有已解析名称或 IP 地址的字符串。
Address.SetHost(string hostName)
设置主机名或 IP 地址。应用于绑定到网络接口或连接到外部主机。成功时返回 true,失败时返回 false。
包含事件类型的结构、指向对等点的托管指针、通道 ID、用户提供的数据以及指向数据包的托管指针。
Event.Type
返回事件的类型。
Event.Peer
返回生成连接、断开连接、接收或超时事件的对等点。
Event.ChannelID
返回生成事件的对等方的通道 ID(如果适用)。
如果适用, Event.Data
返回用户提供的数据。
如果适用, Event.Packet
返回与事件关联的数据包。
包含指向数据包的托管指针。
Packet.Dispose()
销毁数据包。仅当从EventType.Receive
事件获取数据包时才应调用。
Packet.IsSet
返回托管指针的状态。
Packet.Data
返回指向数据包数据的托管指针。
Packet.UserData
获取或设置用户提供的数据。
Packet.Length
返回数据包中有效负载的长度。
Packet.HasReferences
检查对数据包的引用。
Packet.SetFreeCallback(PacketFreeCallback callback)
设置回调以在适当的数据包被销毁时发出通知。可以使用指向回调的指针IntPtr
来代替对委托的引用。
Packet.Create(byte[] data, int offset, int length, PacketFlags flags)
创建一个可以发送到对等方的数据包。 offset参数表示数组中数据的起始点,length为数组中数据的结束点。所有参数都是可选的。可以一次指定多个数据包标志。可以使用指向本机缓冲区的指针IntPtr
来代替对字节数组的引用。
Packet.CopyTo(byte[] destination)
将有效负载从数据包复制到目标数组。
包含指向对等点的托管指针和缓存的 ID。
Peer.IsSet
返回托管指针的状态。
Peer.ID
返回对等 ID。在客户端它始终为零。
Peer.IP
以可打印的形式返回 IP 地址。
Peer.Port
返回端口号。
Peer.MTU
返回 MTU。
Peer.State
返回PeerState
枚举中描述的对等状态。
Peer.RoundTripTime
返回以毫秒为单位的往返时间。
Peer.LastRoundTripTime
返回自上次确认以来的往返时间(以毫秒为单位)。
Peer.LastSendTime
返回最后一个数据包发送时间(以毫秒为单位)。
Peer.LastReceiveTime
返回最后一个数据包接收时间(以毫秒为单位)。
Peer.PacketsSent
返回连接期间发送的数据包总数。
Peer.PacketsLost
返回根据重传逻辑在连接期间被认为丢失的数据包总数。
Peer.PacketsThrottle
根据与对等点的连接条件返回数据包限制比率。
Peer.BytesSent
返回连接期间发送的字节总数。
Peer.BytesReceived
返回连接期间接收到的字节总数。
Peer.Data
获取或设置用户提供的数据。应与显式转换为适当的数据类型一起使用。
Peer.ConfigureThrottle(uint interval, uint acceleration, uint deceleration, uint threshold)
为peer配置油门参数。为了响应对等点连接条件的变化,ENet 会丢弃不可靠的数据包。节流阀表示不可靠数据包不应该被丢弃并因此由 ENet 发送到对等点的概率。从发送可靠数据包到收到其确认的最短平均往返时间是在间隔参数指定的时间量(以毫秒为单位)内测量的。如果测量的往返时间恰好明显小于在该时间间隔内测量的平均往返时间,则将增加节流概率以允许更多流量,加速参数中指定的量是与Library.throttleScale
比率Library.throttleScale
常量。如果测量的往返时间恰好明显大于在该时间间隔内测量的平均往返时间,则降低节流概率以将流量限制在减速度参数中指定的量,该量是与Library.throttleScale
常数。当节流阀的值为Library.throttleScale
时,ENet 不会丢弃任何不可靠的数据包,因此将发送 100% 的所有不可靠的数据包。当节流阀值为 0 时,所有不可靠数据包都会被 ENet 丢弃,因此将发送所有不可靠数据包的 0%。节流的中间值表示发送不可靠数据包的介于 0% 和 100% 之间的中间概率。考虑本地和外部主机的带宽限制来确定节流概率的合理限制,即使在最佳条件下,节流概率也不应高于该限制。要禁用节流,应将减速度参数设置为零。在具有高抖动和低平均延迟的不稳定网络环境中,阈值参数可用于减少相对于测量的往返时间的数据包限制,这是拥挤场所 Wi-Fi 网络的常见情况。默认情况下,阈值参数设置为Library.throttleThreshold
以毫秒为单位)。
Peer.Send(byte channelID, ref Packet packet)
将要发送的数据包排队。成功时返回 true,失败时返回 false。
Peer.Receive(out byte channelID, out Packet packet)
尝试将任何传入的排队数据包出队。如果数据包已出队,则返回 true;如果没有可用数据包,则返回 false。
Peer.Ping()
向对等方发送 ping 请求。 ENet 会定期自动 ping 所有连接的对等点,但是,可以调用此函数以确保更频繁的 ping 请求。
Peer.PingInterval(uint interval)
设置向对等点发送 ping 的时间间隔。 Ping 既可用于监控连接的活跃度,也可在低流量期间动态调整节流阀,以便节流阀在流量高峰期间具有合理的响应能力。
Peer.Timeout(uint timeoutLimit, uint timeoutMinimum, uint timeoutMaximum)
设置对等点的超时参数。超时参数控制对等方因无法确认可靠流量而超时的方式和时间。半线性机制中使用的超时值,其中如果在平均往返时间加上方差容差内未确认可靠数据包,直到超时达到设定限制。如果超时达到此限制,并且已发送可靠数据包但在某个最小时间段内未得到确认,则对等方将断开连接。或者,如果已发送可靠数据包但在某个最大时间段内未得到确认,则无论当前超时限制值如何,对等方都将断开连接。
Peer.Disconnect(uint data)
请求与对等点断开连接。
Peer.DisconnectNow(uint data)
强制立即断开与对等点的连接。
Peer.DisconnectLater(uint data)
请求与对等点断开连接,但仅在所有排队的传出数据包发送完毕后才进行。
Peer.Reset()
强制断开对等点的连接。对等方代表的外部主机不会收到断开连接的通知,并且与本地主机的连接将超时。
包含指向主机的托管指针。
Host.Dispose()
销毁主机。
Host.IsSet
返回托管指针的状态。
Host.PeersCount
返回已连接对等点的数量。
Host.PacketsSent
返回会话期间发送的数据包总数。
Host.PacketsReceived
返回会话期间收到的数据包总数。
Host.BytesSent
返回会话期间发送的字节总数。
Host.BytesReceived
返回会话期间接收到的字节总数。
Host.Create(Address? address, int peerLimit, int channelLimit, uint incomingBandwidth, uint outgoingBandwidth, int bufferSize)
创建一个用于与对等点通信的主机。带宽参数确定连接的窗口大小,这限制了在任何给定时间传输的可靠数据包的数量。 ENet 将有策略地在主机之间连接的特定端丢弃数据包,以确保主机的带宽不会被淹没。缓冲区大小参数用于设置发送和接收数据报的套接字缓冲区大小。所有参数都是可选的,除了地址和对等限制(在该函数用于创建侦听传入连接的主机的情况下)。
Host.PreventConnections(bool state)
阻止新传入连接访问主机。该功能使主机在网络中完全不可见,任何尝试连接到它的对等点都将超时。
Host.Broadcast(byte channelID, ref Packet packet, Peer[] peers)
将数据包排队,发送到一定范围的对等点或发送到与主机关联的所有对等点(如果未使用可选的对等点参数)。数组中任何归零的Peer
结构都将从广播中排除。可以将单个Peer
传递给将从广播中排除的函数,而不是数组。
Host.CheckEvents(out Event @event)
检查主机上是否有任何排队的事件,并在可用时调度一个事件。如果已调度事件,则返回 > 0;如果没有可用事件,则返回 0;如果失败,则返回 < 0。
Host.Connect(Address address, int channelLimit, uint data)
发起与外部主机的连接。成功时返回代表外部主机的对等点,失败时抛出异常。在Host.Service()
通知EventType.Connect
事件之前,返回的对等方不会完成连接。通道限制和用户提供的数据参数是可选的。
Host.Service(int timeout, out Event @event)
等待指定主机上的事件并在主机与其对等方之间传输数据包。 ENet 使用轮询事件模型来通知用户重大事件。使用此功能对 ENet 主机进行轮询以获取事件,其中可以指定可选的超时值(以毫秒为单位)来控制 ENet 轮询的时间长度。如果指定超时为 0,则如果没有要分派的事件,此函数将立即返回。否则,如果在指定的超时时间内调度了事件,它将返回 1。应定期调用此函数以确保数据包的发送和接收,否则将出现流量峰值,导致延迟增加。超时参数设置为 0 表示非阻塞,这在游戏循环中调用该函数的情况下是必需的。
Host.SetBandwidthLimit(uint incomingBandwidth, uint outgoingBandwidth)
调整主机的带宽限制(以每秒字节数为单位)。
Host.SetChannelLimit(int channelLimit)
限制未来传入连接允许的最大通道数。
Host.SetMaxDuplicatePeers(ushort number)
限制来自同一主机的最大允许重复对等点,如果超过则阻止连接。默认设置为Library.maxPeers
,不能小于 1。
Host.SetInterceptCallback(InterceptCallback callback)
设置回调以在拦截原始 UDP 数据包时发出通知。可以使用指向回调的指针IntPtr
来代替对委托的引用。
Host.SetChecksumCallback(ChecksumCallback callback)
设置回调以通知何时应计算校验和。可以使用指向回调的指针IntPtr
来代替对委托的引用。
Host.Flush()
将指定主机上的任何排队数据包发送到其指定的对等方。
包含常量字段。
Library.maxChannelCount
最大可能的通道数。
Library.maxPeers
最大可能的对等点数量。
Library.maxPacketSize
数据包的最大大小。
Library.version
相对于本机库的当前兼容性版本。
Library.Time
返回当前本地单调时间(以毫秒为单位)。当应用程序保持活动状态时,它永远不会重置。
Library.Initialize(Callbacks callbacks)
初始化本机库。 Callbacks 参数是可选的,并且只能与自定义内存分配器一起使用。应在开始工作之前调用。成功时返回 true,失败时返回 false。
Library.Deinitialize()
取消初始化本机库。工作完成后应调用。
Library.CRC64(IntPtr buffers, int bufferCount)
计算非托管缓冲区的校验和。
该项目由以下机构赞助:
飞鼠娱乐
平方根工作室
奇怪的循环游戏