Leistungsstarke, benutzerfreundliche Netzwerkbibliothek, die mehr als 16.000 gleichzeitige Clients in allen bereitgestellten Topologien unterstützt.
Entwickelt für verteilte gleichzeitige Echtzeitanwendungen über LAN oder Internet.
Bietet Infrastruktur für die Nachrichtenübermittlung mit hohem Durchsatz, P2P, Nat Traversal und zuverlässiges UDP.
Dieses Repository besteht aus der Hauptkernbaugruppe und mehreren serialisierungsspezifischen Unterbaugruppen.
Eine ausführliche Dokumentation finden Sie auf der Wiki-Seite.
Netzwerkbibliothek: Enthält die gesamte mit den Netzwerksystemen verbundene Logik, angefangen von Rohbytes bis hin zu Abstraktionen wie P2P-Lobbys. Es stellt generische Vorlagen bereit, die mit jeder Art von Serialisierung verwendet werden können.
Plug&Play-Hochleistungsmodelle, die mit Rohbytes arbeiten. Wird auch als Basis für höherwertige Modelle verwendet.
Tcp Server/Client model
mit dynamischen Puffer- und Warteschlangen-Subsystemen, Bytes können wie bei normalen Sockets fragmentiert sein.Tcp Byte Message Server/Client
, bei dem Bytes mit einem 4-Byte-Header gesendet werden. Es stellt eine atomare Nachrichtenübermittlung ohne Fragmentierung sicher.Udp Server/Client
UDP-System, bei dem ein Server mit optimierter Leistung emuliert wird.Reliable Udp Client/Server
, bei dem das moderne TCP-Protokoll über UDP implementiert ist.Beinhaltet generische Modelle, die mit jedem Serialisierungsprotokoll arbeiten können.
Generic Message Server/Client
zum atomaren Senden und Empfangen serialisierter Nachrichten.Generic MessageProtocol Server/client
ähnlich wie oben, jedoch mit zusätzlicher Trägerklasse „MessageEnvelope“, die als Header/Metadaten verwendet wird.P2P Relay Client/Server
bei dem sich Peers (Clients) über einen Relay-Server gegenseitig erkennen und über Udp/Rudp/Tcp kommunizieren können. Unterstützt Tcp und Udp Holepunch.P2P Room/Lobby Server/Client
Erweiterung des Relay-Modells, bei dem Peers einen Raum definieren können, ähnlich wie bei Matchmaking-Servern für Spiele.Die Bibliothek wird mit so vielen Clients getestet, wie das Betriebssystem (Windows) unterstützt (ca. 16.000 dynamische Ports). Die Datenzuverlässigkeit einschließlich RUDP wird im Internet ausführlich getestet. Nat Traversal Udp Holepunching wird auch im Internet mit Erfolg getestet.
Hinweis: Die Bibliothek verfügt über unsichere Code- und Stapelspeicherzuweisungen. Unsichere Abschnitte sind gut getestet und können nicht geändert werden.
Generische Modelle aus der Hauptassembly werden mit dem spezifischen Serialisierer implementiert. Der Grund für diese Aufteilung besteht darin, unnötige Abhängigkeiten zu vermeiden. Alle Methodensignaturen und -verwendungen sind identisch. Es beinhaltet:
Nuget-Pakete sind verfügbar:
Kernnetzwerkbibliothek | Protobuf | MessagePack | NetSeralizer | Json |
---|---|---|---|---|
Infinite-Echo-Benchmarks werden durchgeführt, indem eine Reihe von Nachrichten an den Server gesendet und eine Echo-Antwort erhalten wird. Jede Antwort löst eine neue Anfrage aus. Jede Serverantwort wird als 1 Echo gezählt.
Jeweils 1000 Seed-Nachrichten (32 Byte Nachricht + 4 Header):
AMD Ryzen 7 5800H Laptop
Anzahl der Kunden | TCP-Echo pro Sekunde | SSL-Echo pro Sekunde |
---|---|---|
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 Laptop
Anzahl der Kunden | TCP-Echo pro Sekunde | SSL-Echo pro Sekunde |
---|---|---|
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 Seed-Nachrichtenumschläge (32 Byte Nutzlast, 48 Byte insgesamt):
AMD Ryzen 7 5800H Laptop
Anzahl der Kunden | Protobuf-Echo pro Sekunde | Sicheres Protobuf-Echo pro Sekunde |
---|---|---|
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 Laptop
Anzahl der Kunden | Protobuf-Echo pro Sekunde | Sicheres Protobuf-Echo pro Sekunde |
---|---|---|
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 |
Dieser Benchmark sendet nur einen Nachrichtenumschlag mit Rohbyte-Nutzlast. Informationen zur serialisierungsspezifischen Leistung finden Sie unter: SerializationBenchmarks
Ausführliche Informationen finden Sie unter AsyncTcpClient/Server und ByteMessageTcpClient/Server
Jeder Teil eines Byte-Arrays oder Array-Segments erreicht das Ziel ohne Fragmentierung.
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
Hinweis: Eine wichtige Leistungseinstellung ist hier, ob ein Puffer oder eine Warteschlange als Pufferrichtlinie verwendet werden soll. Verwenden Sie Buffer, wenn Nachrichten hauptsächlich aus Byte[]-Bereichen bestehen, z. B. (Puffer, Offset, Anzahl). Verwenden Sie „Queue“, wenn die Nachrichten ein vollständiges Byte[] sind (0 bis Ende).
client . GatherConfig = ScatterGatherConfig . UseQueue ;
server . GatherConfig = ScatterGatherConfig . UseBuffer ;
Für SSL-Varianten beträgt der Unterschied:
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 += .. .
Weitere Informationen finden Sie unter SSLClient/Server und SSLByteMessageClient/Server
Basisserver/Client, auf dem Rohbytes übertragen werden. Methoden- und Rückrufsignaturen sind identisch mit Byte-Nachrichtenmodellen. Es ist kein Protokoll über den Basisserver/-client implementiert, daher können die Bytes je nach MTU-Größe fragmentiert sein.
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 ) ;
Serialisierte Netzwerke sind Implementierungen generischer Klassen, die von der Core Library bereitgestellt werden. Sie sind auf alle Serialisierungsprotokolle anwendbar.
Weitere Informationen: Serialisiertes Netzwerk
Examples here is only given for Protobuf-net, but signature is identical for any other provided serialization protocol(MessagePack, Json etc)
.
Implementiert ein Server-Client-Modell, bei dem serialisierte Nachrichten atomar übertragen werden. Geben Sie Ihren Typ an:
[ 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" } ) ;
Das Nachrichtenprotokoll ist implementiert, um alle dynamischen Nachrichtentypen mit einem Standardheader zu umschließen. Ausführliche Erläuterungen finden Sie im Nachrichtenprotokoll.
Sie können Ihre Nutzlasttypen deklarieren, wobei es sich um jeden Typ handeln kann, der mit protobuf serialisierbar ist.
[ ProtoContract ]
class SamplePayload : IProtoMessage
{
[ ProtoMember ( 1 ) ]
public string sample ;
}
Beispiel für die sichere Variante:
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
und ProtoMessageClient
haben identische Signaturen, außer dass die Konstruktoren kein Zertifikat benötigen Dieses Modell verwende ich persönlich für meine anderen Projekte wie P2PVideocall und Multiplayer Starfighter Game. Grundsätzlich haben Sie irgendwo in Ihrem Netzwerk einen Relay-Server, der als lokaler Netzwerk-Hub im LAN fungieren und/oder für Verbindungen aus dem Internet geöffnet sein kann, wenn die Portweiterleitung aktiviert ist.
Relay-Clients (Peers) stellen eine Verbindung zum Relay-Server her und erhalten Benachrichtigungen über die Existenz anderer Peers. Peers können sich gegenseitig Nachrichten über Relay Server oder direkt gegenseitig senden (UDP-Holepunch).
Schauen Sie sich P2P Fore für detaillierte Informationen an
Der Server ist vollständig passiv, sodass andere Peers Nachrichten erkennen und sich gegenseitig senden können. Zusätzlich werden NAT-Traversal-Methoden wie UDP-Holepunching bereitgestellt, um eine direkte Kommunikation über das Internet oder LAN zu ermöglichen (bisher nur UDP, aber wir haben zuverlässiges UDP).
Relay Server Is Serialization Agnostic
was bedeutet, dass alle serialisierten Netzwerk-Peers (Protobuff, MessagePack usw.) denselben Relay-Server verwenden können.
Um den Relay-Server zu verwenden, deklarieren Sie Ihren Server einfach als:
var scert = new X509Certificate2 ( "server.pfx" , "greenpass" ) ;
var server = new SecureProtoRelayServer ( 20010 , scert ) ;
server . StartServer ( ) ;
Der Relay-Server ist bereits vorkonfiguriert.
Im Relay-Client wird Ihre Anwendungslogik implementiert. Sie können Ihre Client-Anwendungen online nutzen, um sie zu entdecken und miteinander zu kommunizieren.
So deklarieren Sie einen Kunden:
// 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 ) ;
Methodensignaturen und Rückrufe sind identisch mit dem Proto-Client/Server-Modell (auch mit Payloads). Der einzige Unterschied besteht darin, dass Sie die Guid-ID des Ziel-Peers angeben müssen. Es kommt vom OnPeerRegistered-Ereignis, wenn ein neuer Peer mit dem Relay-Server verbunden wird. Der Relay Server garantiert die Synchronisierung des aktuellen Peer-Sets mit letztlicher Konsistenz zwischen allen Peers. Neue Peers erhalten also alle anderen verbundenen Peers von dieser Veranstaltung und alte Peers erhalten ein Update.
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-Nachrichten können mehr als das Datagramm-Limit von 65.527 Bytes umfassen. Das System erkennt große UDP-Nachrichten als Jumbo-Nachrichten und versendet sie in Blöcken. Der Empfänger versucht, die Nachricht zu rekonstruieren. Wenn nicht alle Teile innerhalb einer Zeitüberschreitung eintreffen, wird die Meldung verworfen. Die maximale Nachrichtengröße für UDP beträgt 16.256.000 Byte.
Das zuverlässige UDP-Protokoll verwendet einen über UDP implementierten TCP-Algorithmus.
client . SendRudpMessage ( peerId , envelope ) ;
client . SendRudpMessage ( peerId , envelope , innerMessage ) ;
client . SendRudpMessageAndWaitResponse ( peerId , envelope , innerMessage ) ;
client . SendRudpMessageAndWaitResponse ( peerId , envelope ) ;
Nat Traversal/Holepunch-Unterstützung:
// Udp
bool result = await client . RequestHolePunchAsync ( destinationPeerId , timeOut : 10000 ) ;
// Tcp
bool result = await client . RequestTcpHolePunchAsync ( destinationPeerId , timeOut : 10000 ) ;
Bei Erfolg können Sie direkte UDP-Nachrichten zwischen aktuellen und Ziel-Peers für den Rest der UDP-Nachrichten in beide Richtungen senden.
Dies ist eine Erweiterung von Relay Server/Client. Die Ergänzung ist das Raumsystem, in dem Peers Räume erstellen oder ihnen beitreten, verfügbare Räume abfragen und Nachrichten an Räume senden können (Multicast). Darüber hinaus wird das gleiche Nachrichtensystem beibehalten, um 1-1-Nachrichten zwischen Peers zu senden.
Sie können mehreren Räumen beitreten
Room Server Is Serialization Agnostic
was bedeutet, dass alle serialisierten Netzwerk-Peers (Protobuf, MessagePack usw.) denselben Room Server verwenden können.
Deklaration von Server und Client
var server = new SecureProtoRoomServer ( 20010 , scert ) ;
server . StartServer ( ) ;
var client1 = new SecureProtoRoomClient ( cert ) ;
Um Räume zu erstellen, ihnen beizutreten und sie zu verlassen, gehen Sie einfach wie folgt vor:
client1 . CreateOrJoinRoom ( "Kitchen" ) ;
client1 . LeaveRoom ( "Kitchen" ) ;
Zimmerrückrufe sind wie folgt. Diese Rückrufe werden nur ausgelöst, wenn Sie sich im selben Raum befinden.
client1 . OnPeerJoinedRoom += ( roomName , peerId ) => ..
client1 . OnPeerLeftRoom += ( roomName , peerId ) => ..
client1 . OnPeerDisconnected += ( peerId ) => ..
Zusätzlich zum standardmäßigen 1-1-Nachrichtenrückruf bieten wir auch Zimmernachrichtenrückrufe an.
client1 . OnTcpRoomMesssageReceived += ( roomName , message ) => ..
client1 . OnUdpRoomMesssageReceived += ( roomName , message ) => ..
client1 . OnTcpMessageReceived += ( message ) => ..
client1 . OnUdpMessageReceived += ( message ) => ..
Alle sicheren TCP-Varianten implementieren einen Standard-SSL-Socket mit TLS-Authentifizierung/-Validierung. Weitere Informationen finden Sie unter: Sicherheit