Высокопроизводительная, простая в использовании сетевая библиотека, поддерживающая более 16 тысяч одновременных клиентов во всех предоставленных топологиях.
Предназначен для распределенных одновременных приложений реального времени через локальную сеть или Интернет.
Обеспечивает инфраструктуру для передачи сообщений с высокой пропускной способностью, P2P, Nat Traversal, Reliable Udp.
Этот репозиторий состоит из основной базовой сборки и нескольких подсборок, специфичных для сериализации.
Пожалуйста, посетите страницу Wiki для получения подробной документации.
Сетевая библиотека. Включает всю логику, связанную с сетевыми системами, начиная от необработанных байтов и заканчивая абстракциями, такими как P2P-лобби. Он предоставляет общие шаблоны для использования с любым типом сериализации.
Высокопроизводительные модели Plug&Play, работающие с необработанными байтами. Также используется в качестве основы для моделей более высокого уровня.
Tcp Server/Client model
с динамической буферизацией и подсистемами очередей, байты могут быть фрагментированы, как обычные сокеты.Tcp Byte Message Server/Client
, где байты отправляются с заголовком длиной 4 байта. Это обеспечивает атомарную доставку сообщений без фрагментации.Udp Server/Client
система UDP, в которой сервер эмулируется с оптимизацией производительности.Reliable Udp Client/Server
, где современный протокол TCP реализован поверх Udp.Включает универсальные модели, которые могут работать с любым протоколом сериализации.
Generic Message Server/Client
предназначенный для атомарной отправки и получения сериализованных сообщений.Generic MessageProtocol Server/client
аналогичен приведенному выше, но с добавлением класса носителя «MessageEnvelope», используемого в качестве заголовка/метаданных.P2P Relay Client/Server
где одноранговые узлы (клиенты) обнаруживают друг друга через сервер ретрансляции, могут использовать Udp/Rudp/Tcp для связи. Поддерживает TCP и UDP Holepunch.P2P Room/Lobby Server/Client
расширение модели Relay, в котором одноранговые узлы могут определять комнату, аналогично игровым серверам подбора игроков.Библиотека протестирована с таким количеством клиентов, какое поддерживает ОС (Windows) (около 16 тыс. динамических портов). Надежность данных, включая RUDP, тщательно проверяется через Интернет. Пробивка отверстий Nat Traversal Udp также успешно протестирована через Интернет.
Примечание. Библиотека имеет небезопасное распределение памяти для кода и стека. Небезопасные разделы хорошо протестированы и не подлежат изменению.
Общие модели из основной сборки реализуются с помощью специального сериализатора. Причиной такого разделения является избежание ненужных зависимостей. Все сигнатуры и использование методов идентичны. Он включает в себя:
Доступны пакеты Nuget:
Базовая сетевая библиотека | Протобуф | Пакет сообщений | NetSeralizer | Json |
---|---|---|---|---|
Тесты Infinite Echo выполняются путем отправки набора сообщений на сервер и получения эхо-ответа. Каждый ответ вызывает новый запрос. Каждый ответ сервера считается как 1 эхо.
1000 начальных сообщений (32-байтовое сообщение + 4 заголовка) каждое:
Ноутбук AMD Ryzen 7 5800H
Количество клиентов | TCP-эхо в секунду | SSL-эхо в секунду |
---|---|---|
100 | 53 400 000 | 41 600 000 |
1000 | 43 600 000 | 22 200 000 |
5000 | 43 400 000 | 21 800 000 |
10000 | 42 800 000 | 21 700 000 |
Ноутбук Intel i9 13980HX
Количество клиентов | TCP-эхо в секунду | SSL-эхо в секунду |
---|---|---|
100 | 128 800 000 | 79 400 000 |
1000 | 110 400 000 | 72 100 000 |
5000 | 102 100 000 | 67 500 000 |
10000 | 100 500 000 | 65 500 000 |
1000 конвертов начальных сообщений (32 байта полезной нагрузки, всего 48 байт):
Ноутбук AMD Ryzen 7 5800H
Количество клиентов | Протобуф эхо в секунду | Безопасное эхо Protobuf в секунду |
---|---|---|
100 | 9 440 000 | 8 050 000 |
1000 | 8 780 000 | 7 480 000 |
5000 | 8 360 000 | 7 390 000 |
10000 | 8 340 000 | 7 350 000 |
Ноутбук Intel i9 13980HX
Количество клиентов | Протобуф эхо в секунду | Безопасное эхо Protobuf в секунду |
---|---|---|
100 | 31 200 000 | 20 650 000 |
1000 | 30 500 000 | 19 500 000 |
5000 | 28 200 000 | 17 650 000 |
10000 | 26 400 000 | 16 000 000 |
Этот тест отправляет только конверт сообщения с необработанными байтами полезной нагрузки. Информацию о конкретной производительности сериализации см. в разделе SerializationBenchmarks.
Для получения подробной информации ознакомьтесь с AsyncTcpClient/Server и ByteMessageTcpClient/Server.
Любой фрагмент байтового массива или сегмент массива достигнет места назначения без фрагментации.
private static void ExampleByteMessage ( )
{
ByteMessageTcpServer server = new ByteMessageTcpServer ( 20008 ) ;
server . OnBytesReceived += ServerBytesReceived ;
server . StartServer ( ) ;
ByteMessageTcpClient client = new ByteMessageTcpClient ( ) ;
client . OnBytesReceived += ClientBytesReceived ;
client . Connect ( "127.0.0.1" , 20008 ) ;
client . SendAsync ( Encoding . UTF8 . GetBytes ( "Hello I'm a client!" ) ) ;
void ServerBytesReceived ( Guid clientId , byte [ ] bytes , int offset , int count )
{
Console . WriteLine ( Encoding . UTF8 . GetString ( bytes , offset , count ) ) ;
server . SendBytesToClient ( clientId , Encoding . UTF8 . GetBytes ( "Hello I'm the server" ) ) ;
}
void ClientBytesReceived ( byte [ ] bytes , int offset , int count )
{
Console . WriteLine ( Encoding . UTF8 . GetString ( bytes , offset , count ) ) ;
}
}
output:
Hello I'm a client!
Hello I'm the server
Примечание. Важным параметром производительности здесь является использование буфера или очереди в качестве политики буферизации. Используйте Buffer, если сообщения в основном представляют собой области byte[], например (buffer, offset,count). Используйте очередь, если сообщения имеют полный байт [] (от 0 до конца).
client . GatherConfig = ScatterGatherConfig . UseQueue ;
server . GatherConfig = ScatterGatherConfig . UseBuffer ;
Для вариантов SSL разница составляет:
var ccert = new X509Certificate2 ( "client.pfx" , "greenpass" ) ;
// null certificate or default constructor will generate self signed certificate
client = new SslByteMessageClient ( ccert ) ;
var scert = new X509Certificate2 ( "server.pfx" , "greenpass" ) ;
// null certificate or default constructor will generate self signed certificate
server = new SslByteMessageServer ( 8888 , scert ) ;
// You can override the SSL cerificate validation callback
server . RemoteCertificateValidationCallback += .. .
client . RemoteCertificateValidationCallback += .. .
Для получения дополнительной информации ознакомьтесь с SSLClient/Server и SSLByteMessageClient/Server.
Базовый сервер/клиент, куда передаются необработанные байты. Сигнатуры методов и обратных вызовов идентичны моделям байтовых сообщений. На базовом сервере/клиенте не реализован протокол, поэтому байты могут быть фрагментированы в зависимости от размера вашего MTU.
AsyncTcpServer server = new AsyncTcpServer ( port : 20000 ) ;
AsyncTpcClient client = new AsyncTpcClient ( ) ;
// SSL variant
// null certificate or default constructor will generate self signed certificate
var ccert = new X509Certificate2 ( "client.pfx" , "greenpass" ) ;
var scert = new X509Certificate2 ( "server.pfx" , "greenpass" ) ;
SslServer server = new SslServer ( 2000 , scert ) ;
SslClient client = new SslClient ( ccert ) ;
Сериализованные сети — это реализации универсальных классов, предоставляемых базовой библиотекой. Они применимы ко всем протоколам сериализации.
Для получения дополнительной информации: сериализованная сеть.
Examples here is only given for Protobuf-net, but signature is identical for any other provided serialization protocol(MessagePack, Json etc)
.
Реализует модель серверного клиента, в которой сериализованные сообщения передаются атомарно. Объявите свой тип:
[ ProtoContract ]
class SampleMessage
{
[ ProtoMember ( 1 ) ]
public string sample ;
}
PureProtoServer server = new PureProtoServer ( 11234 ) ;
server . StartServer ( ) ;
server . BytesReceived += ( clientId , bytes , offset , count ) =>
{
SampleMessage msg = server . Serializer . Deserialize < SampleMessage > ( bytes , offset , count ) ;
Console . WriteLine ( msg . sample ) ;
msg . sample = "Jesse Lets cook" ;
server . SendAsync ( clientId , msg ) ;
} ;
PureProtoClient client = new PureProtoClient ( ) ;
client . Connect ( "127.0.0.1" , 11234 ) ;
client . BytesReceived += ( bytes , offset , count ) =>
{
SampleMessage msg = client . Serializer . Deserialize < SampleMessage > ( bytes , offset , count ) ;
Console . WriteLine ( msg . sample ) ;
} ;
client . SendAsync ( new SampleMessage ( ) { sample = "Yo! Mr White" } ) ;
Протокол сообщений реализован для упаковки всех динамических типов сообщений в стандартный заголовок. Пожалуйста, обратитесь к протоколу сообщений для подробного объяснения.
Вы можете объявить типы полезных данных: любой тип, сериализуемый с помощью protobuf.
[ ProtoContract ]
class SamplePayload : IProtoMessage
{
[ ProtoMember ( 1 ) ]
public string sample ;
}
Пример безопасного варианта:
private static async Task ExampleProtoSecure ( )
{
// null certificate or default constructor will generate self signed certificate
var scert = new X509Certificate2 ( "server.pfx" , "greenpass" ) ;
var cert = new X509Certificate2 ( "client.pfx" , "greenpass" ) ;
SecureProtoMessageServer server = new SecureProtoMessageServer ( 20008 , scert ) ;
server . StartServer ( ) ;
server . OnMessageReceived += ServerMessageReceived ;
var client = new SecureProtoMessageClient ( cert ) ;
client . OnMessageReceived += ClientMessageReceived ;
client . Connect ( "127.0.0.1" , 20008 ) ;
var Payload = new SamplePayload ( ) { sample = "Hello" } ;
var messageEnvelope = new MessageEnvelope ( ) ;
messageEnvelope . Header = "PayloadTest" ;
// You can just send a message, get replies on ClientMessageReceived.
client . SendAsyncMessage ( messageEnvelope ) ;
client . SendAsyncMessage ( messageEnvelope , Payload ) ;
// Or you can wait for a reply async.
MessageEnvelope result = await client . SendMessageAndWaitResponse ( messageEnvelope , Payload ) ;
var payload = result . UnpackPayload < SamplePayload > ( ) ;
Console . WriteLine ( $ "Client Got Response { payload . sample } " ) ;
void ServerMessageReceived ( Guid clientId , MessageEnvelope message )
{
Console . WriteLine ( $ "Server Received message { message . Header } " ) ;
server . SendAsyncMessage ( clientId , message ) ;
}
void ClientMessageReceived ( MessageEnvelope message )
{
}
}
ProtoMessageServer
и ProtoMessageClient
имеют идентичные подписи, за исключением того, что конструкторы не принимают сертификат. Эту модель я лично использую в других своих проектах, таких как P2PVideocall и Multiplayer Starfighter Game. По сути, где-то в вашей сети есть ретрансляционный сервер, который может действовать как локальный сетевой концентратор в локальной сети и/или открываться для подключений из Интернета, если включена переадресация портов.
Клиенты ретрансляции (точки) подключаются к серверу ретрансляции и получают уведомления о существовании других узлов. Одноранговые узлы могут отправлять сообщения друг другу через сервер ретрансляции или напрямую друг другу (Udp-дырка).
Ознакомьтесь с P2P. Для получения подробной информации
Сервер полностью пассивен, что позволяет другим узлам обнаруживать и отправлять сообщения друг другу. Кроме того, предусмотрены методы прохождения NAT, такие как перфорация UDP, позволяющие осуществлять прямую связь через Интернет или локальную сеть (пока только UDP, но у нас есть надежный udp).
Relay Server Is Serialization Agnostic
что означает, что любые сериализованные сетевые узлы (Protobuff, MessagePack и т. д.) могут использовать один и тот же сервер ретрансляции.
Чтобы использовать сервер Relay, просто объявите свой сервер как:
var scert = new X509Certificate2 ( "server.pfx" , "greenpass" ) ;
var server = new SecureProtoRelayServer ( 20010 , scert ) ;
server . StartServer ( ) ;
Сервер ретрансляции уже предварительно настроен.
Релейный клиент — это место, где реализуется логика вашего приложения. Вы можете разместить свои клиентские приложения в сети, чтобы находить друг друга и общаться друг с другом.
Чтобы объявить клиента:
// null certificate or default constructor will generate self signed certificate
var cert = new X509Certificate2 ( "client.pfx" , "greenpass" ) ;
var client = new RelayClient ( cert ) ;
client . OnPeerRegistered += ( Guid peerId ) => ..
client . OnPeerUnregistered += ( Guid peerId ) => ..
client . OnMessageReceived += ( MessageEnvelope message ) => ..
client . OnUdpMessageReceived += ( MessageEnvelope message ) => ..
client . OnDisconnected += ( ) => ..
client . Connect ( "127.0.0.1" , 20010 ) ;
Сигнатуры методов и обратные вызовы идентичны прототипу модели клиент/сервер (также с полезными нагрузками). Единственная разница заключается в том, что вам необходимо указать идентификатор Guid узла назначения. Он возникает из события OnPeerRegistered всякий раз, когда новый узел подключается к серверу ретрансляции. Relay Server гарантирует синхронизацию текущего набора одноранговых узлов с конечной согласованностью между всеми одноранговыми узлами. Таким образом, новые одноранговые узлы получат все остальные подключенные одноранговые узлы из этого события, а старые одноранговые узлы получат обновление.
client . SendAsyncMessage ( destinationPeerId , new MessageEnvelope ( ) { Header = "Hello" } ) ;
client . SendUdpMesssage ( destinationPeerId , new MessageEnvelope ( ) { Header = "Hello" } ) ;
// Or with an async reply
MessageEnvelope response = await client . SendRequestAndWaitResponse ( destinationPeerId ,
new MessageEnvelope ( ) { Header = "Who Are You?" } ) ;
Сообщения UDP могут превышать лимит дейтаграмм в 65 527 байт. Система распознает большие сообщения udp как Jumbo-сообщения и отправляет их частями. Принимающая сторона попытается восстановить сообщение. если все детали не доставлены в течение тайм-аута, сообщение удаляется. Максимальный размер сообщения для udp — 16 256 000 байт.
Надежный протокол udp использует в качестве алгоритма TCP реализованный поверх UDP.
client . SendRudpMessage ( peerId , envelope ) ;
client . SendRudpMessage ( peerId , envelope , innerMessage ) ;
client . SendRudpMessageAndWaitResponse ( peerId , envelope , innerMessage ) ;
client . SendRudpMessageAndWaitResponse ( peerId , envelope ) ;
Поддержка Nat Traversal/Holepunch:
// Udp
bool result = await client . RequestHolePunchAsync ( destinationPeerId , timeOut : 10000 ) ;
// Tcp
bool result = await client . RequestTcpHolePunchAsync ( destinationPeerId , timeOut : 10000 ) ;
в случае успеха это позволит вам отправлять прямые udp-сообщения между текущим и целевым узлами для остальных udp-сообщений в обоих направлениях.
Это расширение Relay Server/Client. Дополнением является система комнат, в которой одноранговые узлы могут создавать комнаты или присоединяться к ним, запрашивать доступные комнаты, отправлять сообщения в комнаты (многоадресная рассылка). Кроме того, сохраняется одна и та же система сообщений для отправки 1-1 сообщений между узлами.
Вы можете присоединиться к нескольким комнатам
Room Server Is Serialization Agnostic
что означает, что любые сериализованные сетевые узлы (Protobuf, MessagePack и т. д.) могут использовать один и тот же сервер комнаты.
Объявление Сервера и клиента
var server = new SecureProtoRoomServer ( 20010 , scert ) ;
server . StartServer ( ) ;
var client1 = new SecureProtoRoomClient ( cert ) ;
Чтобы создавать комнаты, присоединяться к ним и выходить из них, просто:
client1 . CreateOrJoinRoom ( "Kitchen" ) ;
client1 . LeaveRoom ( "Kitchen" ) ;
Обратные вызовы номеров следующие. Эти обратные вызовы срабатывают только в том случае, если вы находитесь в одной комнате.
client1 . OnPeerJoinedRoom += ( roomName , peerId ) => ..
client1 . OnPeerLeftRoom += ( roomName , peerId ) => ..
client1 . OnPeerDisconnected += ( peerId ) => ..
В дополнение к стандартному обратному вызову сообщений 1-1 у нас есть обратные вызовы сообщений комнаты.
client1 . OnTcpRoomMesssageReceived += ( roomName , message ) => ..
client1 . OnUdpRoomMesssageReceived += ( roomName , message ) => ..
client1 . OnTcpMessageReceived += ( message ) => ..
client1 . OnUdpMessageReceived += ( message ) => ..
Все безопасные варианты TCP реализуют стандартный сокет SSL с аутентификацией/проверкой TLS. Для получения дополнительной информации посетите: Безопасность.