Это независимая реализация ENet с модифицированным протоколом для C, C++, C# и других языков.
Функции:
Пожалуйста, прочитайте распространенные ошибки, чтобы понять, что может пойти не так.
Для сборки собственной библиотеки требуется соответствующее программное обеспечение:
Для настольных платформ CMake с GNU Make или Visual Studio.
Для мобильных платформ NDK для Android и Xcode для iOS. Убедитесь, что все скомпилированные библиотеки назначены соответствующим платформам и архитектурам ЦП.
Чтобы создать библиотеку для 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 работать в фоновом режиме, включив соответствующую опцию в настройках плеера.
Самая известная стратегия — использовать ENet в независимом потоке ввода-вывода и использовать методы межпотокового обмена сообщениями для передачи данных между потоками/задачами без каких-либо блокировок/мьютексов. Для таких целей были разработаны неблокирующие очереди, такие как Ring Buffer. Абстракции и логику высокого уровня можно распараллелить с помощью рабочих процессов, затем взаимодействовать с потоком ввода-вывода и ставить в очередь/удалять сообщения из очереди для отправки/получения данных по сети.
В целом ENet не является потокобезопасным, но некоторые его функции можно безопасно использовать, если пользователь достаточно осторожен:
Структура Packet
и его функции безопасны до тех пор, пока пакет не перемещается между потоками только по значению и не используется специальный распределитель памяти.
Peer.ID
как только с нативной стороны был получен указатель на пир, идентификатор будет кэшироваться в структуре Peer
для дальнейших действий с объектами, присвоенными этому идентификатору. Peer
структуру можно перемещать между потоками по значению, но ее функции не являются потокобезопасными, поскольку данные в памяти могут быть изменены службой в другом потоке.
Library.Time
внутренне использует атомарные примитивы для управления локальным монотонным временем.
Определения флагов для функции Peer.Send()
:
PacketFlags.None
ненадежная последовательность, доставка пакета не гарантируется.
PacketFlags.Reliable
надежная последовательность, пакет должен быть получен целевым узлом и попытки повторной отправки должны предприниматься до тех пор, пока пакет не будет доставлен.
PacketFlags.Unsequenced
пакет не будет упорядочиваться с другими пакетами и может быть доставлен не по порядку. Этот флаг делает доставку ненадежной.
PacketFlags.NoAllocate
пакет не будет выделять данные, и вместо этого пользователь должен предоставить их. Время жизни пакета следует отслеживать с помощью обратного вызова PacketFreeCallback
.
PacketFlags.UnreliableFragmented
пакет будет ненадежно фрагментирован, если его MTU превышает 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, как следует обрабатывать событие set. Возврат 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 в случае неудачи.
Содержит структуру с типом события, управляемым указателем на одноранговый узел, идентификатор канала, предоставленные пользователем данные и управляемый указатель на пакет.
Event.Type
возвращает тип события.
Event.Peer
возвращает одноранговый узел, сгенерировавший событие подключения, отключения, получения или тайм-аута.
Event.ChannelID
возвращает идентификатор канала на узле, сгенерировавшем событие, если это необходимо.
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 указывает начальную точку данных в массиве, длина — конечную точку данных в массиве. Все параметры являются необязательными. Можно указать несколько флагов пакета одновременно. Вместо ссылки на массив байтов можно использовать указатель IntPtr
на собственный буфер.
Packet.CopyTo(byte[] destination)
копирует полезные данные из пакета в массив назначения.
Содержит управляемый указатель на одноранговый узел и кэшированный идентификатор.
Peer.IsSet
возвращает состояние управляемого указателя.
Peer.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)
настраивает параметр дроссельной заслонки для узла. Ненадежные пакеты отбрасываются ENet в ответ на изменяющиеся условия соединения с одноранговым узлом. Регулирование представляет собой вероятность того, что ненадежный пакет не будет отброшен и, таким образом, отправлен ENet партнеру. Наименьшее среднее время прохождения туда и обратно от отправки надежного пакета до получения его подтверждения измеряется в течение времени, заданного параметром интервала в миллисекундах. Если измеренное время прохождения туда и обратно оказывается значительно меньше среднего времени прохождения туда и обратно, измеренного за интервал, то вероятность дросселирования увеличивается, чтобы разрешить больший трафик на величину, указанную в параметре ускорения, который является отношением к Library.throttleScale
Константа 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()
отправляет запрос проверки связи партнеру. ENet автоматически проверяет все подключенные узлы через определенные промежутки времени, однако эту функцию можно вызывать для обеспечения более частых запросов проверки связи.
Peer.PingInterval(uint interval)
устанавливает интервал, с которым пинги будут отправляться одноранговому узлу. Пинги используются как для мониторинга работоспособности соединения, так и для динамической регулировки дроссельной заслонки в периоды низкого трафика, чтобы дроссельная заслонка имела разумную реакцию во время пиков трафика.
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
, оно не может быть меньше единицы.
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)
вычисляет контрольную сумму для неуправляемых буферов.
Этот проект спонсируется:
Летяга Развлечения
Квадратный корень студии
Странные циклические игры