Coder Social home page Coder Social logo

darkriftnetworking / darkrift Goto Github PK

View Code? Open in Web Editor NEW
206.0 15.0 63.0 2.55 MB

DarkRift Networking by Unordinal

Home Page: http://darkriftnetworking.com

License: Other

C# 98.71% PowerShell 0.01% Gherkin 1.01% Python 0.07% Shell 0.21%
darkrift networking server multiplayer unordinal

darkrift's Introduction

Hello!

Welcome to the DarkRift 2 open source project!

DarkRift 1 and 2 were originally written by Jamie Read. DarkRift 2 has since been open sourced under the care of Unordinal AB and the DarkRift community. Unfortunately, Unordinal is no longer able to maintain the project, so it is being cared for by the DarkRift 2 community. Support for DarkRift is 100% community driven, and we encourage new users to join our community discord.

Features

DarkRift is an extremely performant multithreaded networking library best used for creating multiplayer experiance that require authoritative server.

High Performance Unlimited CCU Extremely Low Overhead Full TCP & UDP Support
DarkRift was designed to be as fast, efficient and as powerful as you could ever need. Our multithreading expertise helps you take advantage of every CPU core and thread in your servers. DarkRift has no CCU limits. Scale to thousands of players without worrying about CCU restrictions. DarkRift overhead can go as low as just 3 bytes on UDP. DarkRift 2 introduces bi-channel communication allowing you to mix and send UDP and TCP messages quickly and reliably depending on your needs.
Embedded or Standalone Free, Forever Authoritative Flexible
DarkRift provides support for both Unity embedded and standalone servers allowing you to take advantage of existing Unity features or push for extreme performance with a standalone build. DarkRift 2 is and will remain free and open source. DarkRift servers are fully authoritative. They own the communication and control exactly what the client can and cannot do on your system. You write the logic, DarkRift will handle the messaging. DarkRift is a message passing framework. It doesn't provide opinionated ways of doing things or try to force you into a programming paradigm that doesn't fit your needs
Loves your protocol Scalability Deep Metrics Chat Filter (Bad Word Filter) and other goodies
Got a favourite low level networking library you want to continue using? Swap out DarkRift's Bichannel Network Listener for any library you like. With DarkRift's state of the art server clustering, you can build a backend capable of seamlessly scaling with your player base. Built in support for Prometheus metrics means you can directly integrate with your existing metrics and monitoring solution like Grafana or Datadog. DarkRift comes with some quality of life features including a chat filter, basic matchmaking support, custom metrics, and more.

Getting Started

Grab the latest stable version from the download page.

You can find an example of a minimal embedded .NET server and client here, or follow Any of the below community tutorials to get started.

Bottom to Top Multiplayer with DarkRift - @Robodoig Source Code

FPS style tutorial - @lukesta Source Code

Lets make An "MMO" series tutorial - @Ace Source Code

How To Make A Multiplayer Game With DarkRift - Video Tutorial - @Dexter

Tic Tac Toe Tutorial - Video Tutorial by - @HappySpider

For more resources and other guides related to DarkRift and multiplayer development in general, join the community discord and see #resources.

Building

This project requires Microsoft Visual Studio 2022 (the free Community edition is fine) or at least one Visual C# project will fail to build in VS2019 and below. See detailed exposition in BUILDING.md

Source Code License

Most source files are licensed under MPL 2.0, with some exceptions where MIT applies. See LICENSE.md

Contributing

We are happy to see community contributions to this project. See CONTRIBUTING.md

Code of Conduct

Be civil. See CODE_OF_CONDUCT.md

Wiki

The wiki is publicly editable and is a place for anyone to add content, code snippets, tutorials and anything that would be useful to other members of the DarkRift Networking community.

Feel free to add pages and use the space as you wish. You are more than welcome (and even encouraged) to cross post from personal blogs and link to external sites (as long as it's relevant)!

DarkRift Networking is not responsible for any content or links on the wiki, although we will monitor it nevertheless.

Minimal Example

Examples are using plain .NET with C# 9.0 top level statements for clarity. You can also use similar code in Unity.

First, we start a server that gets its settings from the local file server.config.

using DarkRift;
using DarkRift.Server;

ServerSpawnData spawnData = ServerSpawnData.CreateFromXml("Server.config");

var server = new DarkRiftServer(spawnData);

void Client_MessageReceived(object? sender, MessageReceivedEventArgs e)
{
    using Message message = e.GetMessage();
    using DarkRiftReader reader = message.GetReader();
    Console.WriteLine("Received a message from the client: " + reader.ReadString());
}

void ClientManager_ClientConnected(object? sender, ClientConnectedEventArgs e)
{
    e.Client.MessageReceived += Client_MessageReceived;

    using DarkRiftWriter writer = DarkRiftWriter.Create();
    writer.Write("World of Hel!");

    using Message secretMessage = Message.Create(666, writer);
    e.Client.SendMessage(secretMessage, SendMode.Reliable);
}

server.ClientManager.ClientConnected += ClientManager_ClientConnected;

server.StartServer();

Console.ReadKey(); // Wait until key press. Not necessary in Unity.

The XML file server.config looks like this (hard to make shorter).

<?xml version="1.0" encoding="utf-8" ?>
<!--
  Configuring DarkRift server to listen at ports TCP 4296 and UDP 4297.
-->
<configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://www.darkriftnetworking.com/DarkRift2/Schemas/2.3.1/Server.config.xsd">
  <server maxStrikes="5" />
  
  <pluginSearch/>
 
  <logging>
    <logWriters>
      <logWriter name="ConsoleWriter1" type="ConsoleWriter" levels="trace, info, warning, error, fatal">
        <settings useFastAnsiColoring="false" />
      </logWriter>
    </logWriters>
  </logging>

  <plugins loadByDefault="false"/>

  <data directory="Data/"/>

  <listeners>
    <listener name="DefaultNetworkListener" type="BichannelListener" address="0.0.0.0" port="4296">
      <settings noDelay="true" udpPort="4297" />
    </listener>
  </listeners>
</configuration>

And finally, here is a client that connects to the server and sends "Hello world!" whilst receiving a string that should be "World of Hel!" (just be mindful about pressing any key since that terminates the program early).

using DarkRift;
using DarkRift.Client;
using System.Net;

var client = new DarkRiftClient();

void Client_MessageReceived(object? sender, MessageReceivedEventArgs e)
{
    using Message message = e.GetMessage();
    using DarkRiftReader reader = message.GetReader();
    Console.WriteLine("Received a message from the server: " + reader.ReadString());
}

client.MessageReceived += Client_MessageReceived;

client.Connect(IPAddress.Loopback, tcpPort:4296, udpPort:4297, noDelay:true);

Console.WriteLine("Connected!");

using DarkRiftWriter writer = DarkRiftWriter.Create();
writer.Write("Hello world!");

using Message secretMessage = Message.Create(1337, writer);
client.SendMessage(secretMessage, SendMode.Reliable);

Console.ReadKey(); // Wait until key press. Not necessary in Unity.

Do note that "Connected!" message can be printed even after "World of Hel!" since DR2 is multithreaded.

This was an example of embedding DarkRift into your own programs. You can instead choose to implement DarkRift.Server.Plugin (see the manual) for looser coupling.

darkrift's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

darkrift's Issues

Disconnect with success

Sometime client disconnect without any reason and it throw that the error is "Success". When this bug occure callback "Disconnect" on client dosen't fire.
unknown

Access to byte arrays of Readers/Writers

Sometimes it would be nice to have access to the byte arrays of DarkriftWriters and Readers to copy or manipulate them. At the moment they are not accesible, id it has no technical reasons to hide them then it would be nice if we could access them.

Methods in DarkRiftWriter and DarkRiftReader causing GC.Alloc

Quick Description

Many of the Write and Read methods in DarkRiftWriter and DarkRiftReader are creating garbage.
The Writer and Reader are integral to DarkRift, and I feel having them creating any sort of garbage compromises usability. This issue will list all of the methods and any potential fixes I can see just from looking at the deep profiler. I would be able to help more if I had source code access :P

Methods That Create Garbage

DarkRiftWriter.Write (char)
DarkRiftReader.ReadChar (char)
DarkRiftWriter.Write (double)
DarkRiftReader.ReadDouble (double)
DarkRiftWriter.Write (float)
DarkRiftReader.ReadSingle (float)
DarkRiftWriter.Write (string)
DarkRiftReader.ReadString (string)

Literally all of DarkRiftReader.ReadArray methods

In depth Reasoning and Potential Solutions (where applicable)

Write char

According to the profiler, there are two sources of garbage in DarkRiftWriter.Write (char), something in the root of the method, and Encoding.GetBytes. Considering Encoding.GetBytes takes in a char array, I am assuming a char array is being made in the method and passed in. A simple fix for this is to cache the array and reuse it, or to use the pointer version of the method.
image

Read char

According to the profiler, this method calls DarkRiftReader.ReadChars, which takes in an array, this further implies that DarkRiftWriter.Write (char) creates an array and calls DarkRiftWriter.Write (char[]).
A potential fix for this is the same as for Write (char), to simply cache the array and reuse it.
image

Write double

According to the profiler, DarkRiftWriter.Write (double) calls BitConverter.GetBytes, and Array.Reverse.
All BitConverter.GetBytes methods create garbage, and there is no non-alloc versions of them. Looking at the source code for BitConverter, however, shows that you should be able to make your own non-allocating versions of the functions.

The worst of these is by far Array.Reverse. Looking at the C# source code, Array.Reverse uses an implicit conversion to System.Object, causing tons of boxing for structs. It is advisable to make and use your own version of Array.Reverse that will only take in byte arrays.
image
Another much nicer solution is to use functions like these

public static ushort SwapBytes (ushort value)
{
	return (ushort)(((value & 0x00FF) << 8) |
	       ((value & 0xFF00) >> 8));
}
public static uint SwapBytes (uint value)
{
	return ((value & 0x000000FF) << 24) |
	       ((value & 0x0000FF00) << 08) |
	       ((value & 0x00FF0000) >> 08) |
	       ((value & 0xFF000000) >> 24);
}
public static ulong SwapBytes (ulong value)
{
	return ((value & 0x00000000000000FF) << 56) |
	       ((value & 0x000000000000FF00) << 40) |
	       ((value & 0x0000000000FF0000) << 24) |
	       ((value & 0x00000000FF000000) << 08) |
	       ((value & 0x000000FF00000000) >> 08) |
	       ((value & 0x0000FF0000000000) >> 24) |
	       ((value & 0x00FF000000000000) >> 40) |
	       ((value & 0xFF00000000000000) >> 56);
}

A simple bit of pointer logic can easily turn a double value into a ulong value, same logic applies for float to uint. Casting signed integers to unsigned keeps the bits the same, so casting to from int to uint, then swapping, and casting back is completely fine.

public unsafe ulong ToUInt64 (double value)
{
	double* ptr = &value;
	return *(ulong*)ptr;
}

Read double

According to the profiler, this method suffers from the same boxing due to Array.Reverse that its Write counterpart does. Any of the fixes for Write (double) can be applied here.
image

Write float

According to the profiler, this method has boxing due to Array.Reverse. And an array being created from BitConverter.GetBytes. The fixes for Write (double) can be applied here.
image

Read float

According to the profiler, this method has boxing due to Array.Reverse. The fix for Read (double) can be applied here.
image

Write string

According to the profiler, this method is creating an array due to Encoding.GetBytes. There is a non-alloc string version that can be used instead.
https://msdn.microsoft.com/en-us/library/c20ss51h(v=vs.110).aspx
image

Read string

This one is a bit tricky, but according to the profiler, a byte array is being created due to a call to ReadBytes. There is little way to avoid the garbage from Encoding.GetString though. A good place to start would be to remove the call to ReadBytes. There is a version of Encoding.GetString that takes in a byte pointer and a length, perhaps that can be used instead.
https://msdn.microsoft.com/en-us/library/dn823314(v=vs.110).aspx
image

DarkRiftReader.ReadArray

A massive flaw with all of these methods is that there is no non-allocation versions of them. Simple enough to fix though.

End

Like I said earlier, I may be able to help more if I had source, even if its just the main classes involved.

Dark Rift 2: System.OverflowException: "Array dimensions exceeded supported range."

Quick Description

The error happens in the Deserialize method of our custom class, but it causes by DarkRiftReader.ReadBytes() method.

System.OverflowException: "Array dimensions exceeded supported range."

Explaination

I created something like stress test. c# console server and Unity client with your dlls.
Client sending a message with long string (173 chars) to the server and server just sending this string back to the client, then client trying to match both strings
and strings are same each time, during 1M packets (~ 1.5 hours with 200packets per second) with 0% CPU usage and about 18M of RAM and this is really cool.

But server crushing each time I'm trying to increase frequency of packets up to the 300 per second.
The error happens in the Deserialize method of our custom class, but it causes by DarkRiftReader.ReadBytes() method.

System.OverflowException: "Array dimensions exceeded supported range."

For Issues

  • server is local machine
  • it occured on the server side and we have never seen this on the client side despite both of them handling the same amount of packets in this test.

exeption

Cannot run server from Unity build

Quick Description

The server can be run from the Unity editor but cannot be run from a Unity build.

Explaination

When running the server from a Unity build Clients cannot connect wiith this error:
at System.Net.Sockets.Socket.Receive (System.Byte[] buffer) [0x0004d] in /Users/builduser/buildslave/mono/build/mcs/class/System/System.Net.Sockets/Socket.cs:2373 at DarkRift.NetworkClientConnection.Connect (System.Net.IPAddress ipAddress, Int32 port) [0x00000] in <filename unknown>:0 at DarkRift.Client.DarkRiftClient.Connect (System.Net.IPAddress ip, Int32 port, IPVersion ipVersion) [0x00000] in <filename unknown>:0 at DarkRift.Client.Unity.UnityClient.Connect (System.Net.IPAddress ip, Int32 port, IPVersion ipVersion) [0x0000a] in F:\Documents\GitHub\UnityCSharpSpaceGame\Assets\DarkRift\DarkRift\Plugins\Client\UnityClient.cs:156 at DarkRift.Client.Unity.UnityClient.Start () [0x0001f] in F:\Documents\GitHub\UnityCSharpSpaceGame\Assets\DarkRift\DarkRift\Plugins\Client\UnityClient.cs:128 (Filename: /Users/builduser/buildslave/mono/build/mcs/class/System/System.Net.Sockets/Socket.cs Line: 2373)
The server logs nothing.

This only occurs with the 3.5 runtime, using 4.6 fixes the problem and everything works as expected.

Multiple Connection Disconnection - Unity Crashes

Main Issue

Using either of the connection methods (doesn't matter which one) try multi connecting and disconnecting from the UnityCllient. This causes unity to crash when next pressing play or something before that.

Secondary Issue

Trying to connect for a second time has also been causing multi dispatcher message events even when only a single message was sent back from the server side plugin. It was like the dispatcher had not released its queue properly from the previous disconnect. Which kinda supports the Main Issue above.

Possible Issue

I do not believe UnityClient is properly being disposed when disconnecting.

Here is a UnityClient i modified which stopped the unity crash but has not solved the multi message requests.

////////////////////////////////////////////////////////////////////////////////
//  D A R K  R I F T  2
//
//  Module      :   UnityClient.cs
//  Description :   Fix for the original UnityClient.cs (beta 8)
////////////////////////////////////////////////////////////////////////////////

using DarkRift.Dispatching;
using System;
using System.Net;
using System.Net.Sockets;
using UnityEngine;

namespace DarkRift.Client.Unity
{
    [AddComponentMenu("DarkRift/Client")]
	public sealed class UnityClient : MonoBehaviour
	{
        /// <summary>
        ///     The IP address this client connects to.
        /// </summary>
        public IPAddress Address
        {
            get { return IPAddress.Parse(address); }
            set { address = value.ToString(); }
        }

        [SerializeField]
        [Tooltip("The address of the server to connect to.")]
        string address = IPAddress.Loopback.ToString();                 //Unity requires a serializable backing field so use string

        /// <summary>
        ///     The port this client connects to.
        /// </summary>
        public ushort Port
        {
            get { return port; }
            set { port = value; }
        }

		[SerializeField]
		[Tooltip("The port the server is listening on.")]
		ushort port = 4296;

        /// <summary>
        ///     The IP version to connect with.
        /// </summary>
        public IPVersion IPVersion
        {
            get { return ipVersion; }
            set { ipVersion = value; }
        }

        [SerializeField]
        [Tooltip("The IP protocol version to connect using.")]          //Declared in custom editor
        IPVersion ipVersion = IPVersion.IPv4;

        [SerializeField]
        [Tooltip("Indicates whether the client will connect to the server in the Start method.")]
        bool autoConnect = true;

        [SerializeField]
        [Tooltip("Specifies that DarkRift should take care of multithreading and invoke all events from Unity's main thread.")]
        volatile bool invokeFromDispatcher = true;

        [SerializeField]
        [Tooltip("Specifies whether DarkRift should log all data to the console.")]
        volatile bool sniffData = false;

        #region Cache settings

        /// <summary>
        ///     The maximum number of <see cref="DarkRiftWriter"/> instances stored per thread.
        /// </summary>
        public int MaxCachedWriters
        {
            get
            {
                return maxCachedWriters;
            }
        }

        [SerializeField]
        [Tooltip("The maximum number of DarkRiftWriter instances stored per thread.")]
        int maxCachedWriters = 2;

        /// <summary>
        ///     The maximum number of <see cref="DarkRiftReader"/> instances stored per thread.
        /// </summary>
        public int MaxCachedReaders
        {
            get
            {
                return maxCachedReaders;
            }
        }

        [SerializeField]
        [Tooltip("The maximum number of DarkRiftReader instances stored per thread.")]
        int maxCachedReaders = 2;

        /// <summary>
        ///     The maximum number of <see cref="Message"/> instances stored per thread.
        /// </summary>
        public int MaxCachedMessages
        {
            get
            {
                return maxCachedMessages;
            }
        }

        [SerializeField]
        [Tooltip("The maximum number of Message instances stored per thread.")]
        int maxCachedMessages = 8;

        /// <summary>
        ///     The maximum number of <see cref="System.Net.Sockets.SocketAsyncEventArgs"/> instances stored per thread.
        /// </summary>
        public int MaxCachedSocketAsyncEventArgs
        {
            get
            {
                return maxCachedSocketAsyncEventArgs;
            }
        }

        [SerializeField]
        [Tooltip("The maximum number of SocketAsyncEventArgs instances stored per thread.")]
        int maxCachedSocketAsyncEventArgs = 32;

        #endregion

        /// <summary>
        ///     Event fired when a message is received.
        /// </summary>
        public event EventHandler<MessageReceivedEventArgs> MessageReceived;

        /// <summary>
        ///     Event fired when we disconnect form the server.
        /// </summary>
        public event EventHandler<DisconnectedEventArgs> Disconnected;

        /// <summary>
        ///     The ID the client has been assigned.
        /// </summary>
        public ushort ID
        {
            get
            {
                return Client.ID;
            }
        }

        /// <summary>
        ///     Returns whether or not this client is connected to the server.
        /// </summary>
        public bool Connected
        {
            get
            {
                if (Client == null)
                    return false;

                return Client.Connected;
            }
        }

		/// <summary>
		/// 	The actual client connecting to the server.
		/// </summary>
		/// <value>The client.</value>
        public DarkRiftClient Client
        {
            get
            {
                return client;
            }
        }

        DarkRiftClient client;

        /// <summary>
        ///     The dispatcher for moving work to the main thread.
        /// </summary>
        public Dispatcher Dispatcher { get; private set; }
        
        void Start()
		{
            // If auto connect is true then connect to the server
            if (autoConnect)
			    Connect(Address, port, ipVersion);
		}

        void Update()
        {
            // If client exists then Execute all the queued dispatcher tasks
            if (client != null)
                Dispatcher.ExecuteDispatcherTasks();
        }

        void OnDestroy()
        {
            //Remove resources
            Disconnect();
        }

        void OnApplicationQuit()
        {
            Disconnect();
        }

        /// <summary>
        ///     Connects to a remote server.
        /// </summary>
        /// <param name="ip">The IP address of the server.</param>
        /// <param name="port">The port of the server.</param>
        public void Connect(IPAddress ip, int port, IPVersion ipVersion)
        {
            // Don't allow connection while already connected
            if (client != null)
            {
                if (client.Connected)
                    return;
            }

            client = new DarkRiftClient(maxCachedWriters, maxCachedReaders, maxCachedMessages, maxCachedSocketAsyncEventArgs);

            //Setup dispatcher
            Dispatcher = new Dispatcher(true);

            //Setup routing for events
            Client.MessageReceived += Client_MessageReceived;
            Client.Disconnected += Client_Disconnected;

            Client.Connect(ip, port, ipVersion);

            if (Connected)
                Debug.Log("Connected to " + ip + " on port " + port + " using " + ipVersion + ".");
            else
                Debug.Log("Connection failed to " + ip + " on port " + port + " using " + ipVersion + ".");
        }

        /// <summary>
        ///     Connects to a remote asynchronously.
        /// </summary>
        /// <param name="ip">The IP address of the server.</param>
        /// <param name="port">The port of the server.</param>
        /// <param name="callback">The callback to make when the connection attempt completes.</param>
        public void ConnectInBackground(IPAddress ip, int port, IPVersion ipVersion, DarkRiftClient.ConnectCompleteHandler callback = null)
        {
            // Don't allow connection while already connected
            if (client != null)
            {
                if (client.Connected)
                    return;
            }

            client = new DarkRiftClient(maxCachedWriters, maxCachedReaders, maxCachedMessages, maxCachedSocketAsyncEventArgs);

            //Setup dispatcher
            Dispatcher = new Dispatcher(true);

            //Setup routing for events
            Client.MessageReceived += Client_MessageReceived;
            Client.Disconnected += Client_Disconnected;

            Client.ConnectInBackground(
                ip,
                port, 
                ipVersion, 
                delegate (Exception e)
                {
                    if (callback != null)
                    {
                        if (invokeFromDispatcher)
                            Dispatcher.InvokeAsync(() => callback(e));
                        else
                            callback.Invoke(e);
                    }

                    if (Connected)
                        Debug.Log("Connected to " + ip + " on port " + port + " using " + ipVersion + ".");
                    else
                        Debug.Log("Connection failed to " + ip + " on port " + port + " using " + ipVersion + ".");
                }
            );
        }

        /// <summary>
        ///     Sends a message to the server.
        /// </summary>
        /// <param name="message">The message template to send.</param>
        /// <returns>Whether the send was successful.</returns>
        public bool SendMessage(Message message, SendMode sendMode)
        {
            return Client.SendMessage(message, sendMode);
        }

        /// <summary>
        ///     Invoked when DarkRift receives a message from the server.
        /// </summary>
        /// <param name="sender">THe client that received the message.</param>
        /// <param name="e">The arguments for the event.</param>
        void Client_MessageReceived(object sender, MessageReceivedEventArgs e)
        {
            //If we're handling multithreading then pass the event to the dispatcher
            if (invokeFromDispatcher)
            {
                if (sniffData)
                    Debug.Log("Message Received");      //TODO more information!

                Dispatcher.InvokeAsync(
                    () => 
                        {
                            EventHandler<MessageReceivedEventArgs> handler = MessageReceived;
                            if (handler != null)
                            {
                                handler.Invoke(sender, e);
                            }
                        }
                );
            }
            else
            {
                if (sniffData)
                    Debug.Log("Message Received");      //TODO more information!

                EventHandler<MessageReceivedEventArgs> handler = MessageReceived;
                if (handler != null)
                {
                    handler.Invoke(sender, e);
                }
            }
        }

        void Client_Disconnected(object sender, DisconnectedEventArgs e)
        {
            //If we're handling multithreading then pass the event to the dispatcher
            if (invokeFromDispatcher)
            {
                if (!e.LocalDisconnect)
                    Debug.Log("Disconnected from server, error: " + e.Error);

                Dispatcher.InvokeAsync(
                    () =>
                    {
                        EventHandler<DisconnectedEventArgs> handler = Disconnected;
                        if (handler != null)
                        {
                            handler.Invoke(sender, e);
                        }
                    }
                );
            }
            else
            {
                if (!e.LocalDisconnect)
                    Debug.Log("Disconnected from server, error: " + e.Error);
                
                EventHandler<DisconnectedEventArgs> handler = Disconnected;
                if (handler != null)
                {
                    handler.Invoke(sender, e);
                }
            }
        }

        /// <summary>
        ///     Disconnects this client from the server.
        /// </summary>
        /// <returns>Whether the disconnect was successful.</returns>
        public bool Disconnect()
        {
            bool result = false;

            // Disconnect the client
            if (Client != null)
                result = Client.Disconnect();

            // Dispose the resources
            Close();

            return result;
        }

        /// <summary>
        ///     Closes this client.
        /// </summary>
        private void Close()
        {
            // Client must exist to dispose from
            if (Client != null)
                Client.Dispose();
        }
	}
}

Suggestions for the documentations

Quick Description

https://darkriftnetworking.com/DarkRift2/Docs/2.0.0/html/24f9d464-af6c-479f-a14f-d91781a60579.htm

At the bottom, please change
"Finally add this line to the bottom of your SpawnPlayer method:"

to
"Finally add this line at the bottom of your last call in the SpawnPlayer method:"

also "Have a go at writing the logic for this yourself, "
to "Have a go at writing the logic to receive the position data yourself, "

Some small adjustments to make it just a little more newb friendly.

Thanks!

Regards,
Rakinare

IP Address Reset

Quick Description

IP address set in inspector gets ignored / reset, and isn't used.

Explaination

This is for embedded server, UnityServer.cs address field.
When I press play, the component appears to be reloaded and the IP goes back to 0.0.0.0.

I worked around it by hardcoding the IP into line 327:
ServerSpawnData spawnData = new ServerSpawnData(IPAddress.Parse("127.0.0.1"), port, ipVersion);

For Issues

  • Embedded Server Unity 2017.4.1f1 LTS
  • Set an IP address in the field and press play

Custom ClientManager messages

So this feature would let to enable/disable/change ClientManager messages like "New client [0] connected [127.0.0.1:63712|54043].", "Client [0] disconnected.".
It would be cool to change messages type from "Info" to "Trace" so there would be no console spam if you have some amount of players, but it would be stored in the log what could be pretty useful.

It could work with additional config file where you could change all of this...

Memory Leak in Client Side Event Handlers

Quick Description

In the client, events are subscribed to but never unsubscribed from causing a memory leak.

Fix

In DarkRift.Client.NetworkClientConnections.cs:
Add "e.Completed -= TcpSendCompleted;" at line 346
Add "e.Completed -= UdpSendCompleted;" at line 359

ConnectInBackground issue

Quick Description

When we try to connect and disconnect using ConnectInBackground and try to send reliable packages between the game will eventually freeze with exception "Operation in progress". Also after n'th connection via ConnectInBackground the reliable packages are not coming.
Edit:
Maybe it also matters that I'm sending reliable message in a caller (ConnectCompleteHandler). So it is send immediately after caller is executed with null exception.

Feature Request: Writing/reading arrays of IDarkRiftSerializable

Hey Jam, could you consider adding an overload to DarkRiftWriter.Write() and DarkRiftReader.Read() for writing/reading arrays of IDarkRiftSerializable objects too? Would be really quite handy, and I don't think its too much work to implement considering you already have them for the other data types and could probably do it with a simple for loop in the overload.
Thanks!

Write(bool[]) to ReadBools() results in array of bools being reversed.

When I create a message and send an array of bools to a client the client gets a reversed version of the array. Same thing happens with message from client to server. This is testing on the same PC and does not happen with any other array types.

For Issues

-Embedded server
-Windows PC
-Seems like it may be a bit packing or endian-ness issue.

Dark Rift 2 - Out of Memory Bug

Main Issue

Deployed the Dark Rift 2 (version 2.0) on my VPS and got an Out Of Memory Exception causing the
server to crash. Please see the attached screen shot.

dark rift 2 out of memory fault

Extended Notes

Testing the server for 10-20 mins does not yield this out of memory issue. Testing the server for over
an hour constantly running / syncing characters positions using reliable send receive messages causes a memory buildup / leak to occur. All cache settings are as per standard default on both client and server.

Access Message Tag directly from MessageReceivedEventArgs

Quick Description

Instead of having to do a MessageReceivedEventArgs.GetMesssage just to get the message's tag, MessageReceivedEventArgs should expose the tag by itself.

Explaination

The main reason for this change would be so that x amount of Message objects are not being created for every MessageReceived just to check the tag, but instead the Message object is only created where and when it needs to be.

I cannot speak for others, but my current use cases go something like this for every MessageReceived method:

private void MessageReceived (object sender, MessageReceivedEventArgs e)
{
	using (Message message = e.GetMessage() as Message)
	{
		switch (message.Tag)
		{
			case Tags.useMessageInSwitch:
				UseMessageInMethod (message);
				break;
		}
	}
}

The proposed change would result in usage like this:

private void MessageReceived (object sender, MessageReceivedEventArgs e)
{
	switch (e.Tag)
	{
		case Tags.useMessageInSwitch:
			using (Message message = e.GetMessage())
			{
				// message logic 
			}
			break;
		case Tags.useMessageInMethod:
			using (Message message = e.GetMessage ())
			{
				MessageInMethod (message);
			}
		case Tags.useMessageReceivedEventArgsInMethod:
			UseMessageReceivedEventArgs ();
			break;
	}
}
private void UseMessageInMethod (Message message)
{
	// message logic
}
private void UseMessageReceivedEventArgs InMethod (MessageReceivedEventArgs e)
{
	using (Message message = e.GetMessage())
	{
		// message logic
	}
}

`ReadRawInto` produces incorrect data

Quick Description

ReadRawInto produces incorrect results.

Explanation

I asked on Discord if the following should be equivalent, Jamster confirmed that they should:

for (var i = 0; i < length; i++)
    buffer[i] = reader.ReadByte();
reader.ReadRawInto(buffer, 0, length);

However, using the ReadRawInto version produces different results.

From looking at the decompilation of DarkRiftReader it looks like this may be because the BlockCopy parameter for position does not take into account the dataoffset field.

This seems to happen completely reliably in my own code when swapping between the two (an integration for Dissonance Voice Chat). I haven't reduced that down to a smaller test case, but I think reading from a DarkRiftReader with an offset should produce the issue.

Plugins do not load in .NET Core

Quick Description

.Net core console app fails to load plugins if they reference other dlls.

Explanation

The .NET Core console app will only load plugins that reference DLLs if it also references those DLLs and has loaded them when started.

It's worth mentioning that this only happens when building against .NET Core & it does not happen in .NET Framework builds of the server.

Reproduce it

Fails to load

Create an empty Plugin X that references DLL Y.
Place X & Y in /Plugins
Run console app.

Loads successfully

Create an empty Plugin X that references DLL Y.
Have the console app also reference DLL Y.
Place X in /Plugins
Place Y in /Lib
Run console app.

Info

This might just be a .NET Core issue unrelated to DR2, I have not done further research.

SSLStream for TCP

Quick Description

Implement SSLStream for TCP (reliable) connections for both server and client certs.

Explanation

Encryption has several known benefits, including compression.

For Feature Requests

SSLStream on MSDN

[Feature Request] SendMessage-Callback-API

Quick Description

SendMessage-Callback-API:
client.SendMessage(Tag, Message, SendMode, (onResponse) => { ... switch(onResponse.Status) ...});
OR
client.SendMessage(MyTag, MyMessage, MySendMode, (onSuccess, onError) => { ... });

Explanation

Instead of handling responses in the MessageReceived-Event, this feature leads to a cleaner and easy-to-read response-handling. The SendMessage gets a delegate which handles the response to the corresponding message. Because two callbacks "onSuccess" and "onError" can be application-specific (I guess?) there is another option of "onResponse" which contains a status-enumerable.

The status should contain at least:
Success,
Error,
Timeout <-- default timeout configured in Server.config

Sending a response:
public void HandleClientRequest(Message message)
{
//Check message
message.Respond(ResponseStatus.Success, myIDarkRiftSerializable)
}

TCP Header Contains Corrupted Value

Quick Description

As noted by Mogull on Discord.

Sending rapid messages to the server is causing the TCP header to contain corrupt length values and hence the backing array for the message cannot be created.

Explaination

The lines

int count = BigEndianHelper.ReadInt32(e.Buffer, 0);
e.SetBuffer(new byte[count], 0, count);

Are causing an exception ("Array dimensions exceeded supported range") as count is being read as a negative number.

This is after sending a large number of calls per second to the server.

Conditions to Reproduce

Console Server, version 2.0.1 or lower.

Feature: DarkRiftReader change reading position

(very similar to others, but not exactly same, so I opened a new)
It would be nice to be able to change the "position" of DarkRiftReader, so you can skip without using ReadRawInto. Something like a SetPosition(position), or SkipBytes(bytes) would work. If you have conditional buffered data in your messages it would be nice to skip reading it :)

Request: Add Server Status Feedback

Quick Description

The DarkRiftServer class needs some way to determine if it successfully started and/or if it is currently listening for connections.

Possible ideas:

  1. Make DarkRiftServer.Start() return bool, or some kind of new enum like ServerStartErrorCode { Success, Failure, GenericError}
  2. If server starting might ever need to be asynchronous, instead or also add a new StartInBackground(...) method signature with an OnComplete callback
  3. Implement something like bool DarkRiftServer.Listening { get; } which returns whether or not the server is currently listening for connections

Use cases:

  1. If the server fails to start (e.g. because the port was already in use) I want to inform the user with more information
  2. I have a "start server" button which I want to disable whenever the server is successfully started and listening for connections, and maybe also when it is in the process of starting (if I'm somehow starting the server process asynchronously, if that might ever be worth implementing)

`DarkRiftWriter` does not hold type safety.

Thanks for creating an issue/feature request! Please use the template below to ensure you have all the necessary information.

Quick Description

DarkRiftWriter.Write<T>(T value) : where T : IDarkRiftSerializable does not hold type safety.

Explaination

Generic type constraints are not part of the method signature, as such, using a where clause does not hold type safety when creating Extension Methods for DarkRiftWriter.

Reproduction

Create an extension method for DarkRiftWriter & try to compile.

Possible solution:

Revert back to the type safe DarkRiftWriter.Write(IDarkRiftSerializable serializable)

Feature Request: Packet Writer Reserve Space

Quick Description

Add the ability to reserve space in a packet writer which can be written into later

Explaination

In other systems when writing variable length data into a packet I will reserve some space for a length prefix, write the data, and then go back and write the length prefix. This appears to be impossible with Dark Rift.

I imagine an API like this:

DarkRiftWriter writer;

//Reserve some space, this returns a struct which can write back into the correct position in the buffer
//Possibly there could be a counter in the writer which causes an exception if you try to send a message with unused reserved space
var reserved = writer.ReserveInt();

//write other stuff
...

//Write into the reserved space
reserved.Write(an_integer);

Message.Serialize has unpredicatable behaviour

Quick Description

Invoking Message.Serialize(DarkRiftWriter) only sets a reference to the backing array of the DarkRiftWriter.

Explaination

Given the above, if the writer was then written to the data in the message would change. However, if enough data was written to the writer to cause the backing array to resize these changes would not propagate to the message and the message would hold a reference to out of date data.

Consistency needs to be formed here however this is resolved.

Failing to load one type within a plugin assembly cause other types not to be loaded

Quick Description

Loading a plugin assembly completely fails if one type within it cant be loaded.

For Issues

Fix:
-> PluginFactory.AddFile
-> See : enclosedTypes = assembly.GetTypes();
-> See : catch (ReflectionTypeLoadException e)

ReflectionTypeLoadException.Types provides all types which were loadable and could therefore be set as "enclosedTypes" in order to have them be added.

Loaders being initialized only after plugins are loaded

Quick Description

Its impossible to receive any logs from initializing plugins.

Explanation

Loaders are initialized only after plugins are loaded which causes anything happening beforehand to fail silently. This makes it hard to debug anything happening beforehand.

Bitwriter/reader

Adding a bitwriter reader and "finisher" that rounds up to nearest byte.

Basically
WriteBits(uint, numBits) and a ReadBits(numBits) and then something like BitRoundup()

You can create some neat optimizations with tools like this for all kinds and purposes, and would be nice to be included in the API.

The socket is not connected error

Quick Description

When users increase for a period of time:the server is shutdown and hava “the socket is not connected “error

Explaination

Unhandled Exception:
System.Net.Sockets.SocketException (0x80004005): The socket is not connected
at System.Net.Sockets.Socket.get_RemoteEndPoint () [0x0003e] in <5071a6e4a4564e19a2eda0f53e42f9bd>:0
at DarkRift.Server.NetworkListener.ConnectionTimeoutHandler (System.Object state) [0x00062] in <39d1105326e9434f8a273baafee6795d>:0
at System.Threading.Timer+Scheduler.TimerCB (System.Object o) [0x00007] in <1fd744d936be4a229b315d57c3b96c68>:0
at System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem () [0x00019] in <1fd744d936be4a229b315d57c3b96c68>:0
at System.Threading.ThreadPoolWorkQueue.Dispatch () [0x00096] in <1fd744d936be4a229b315d57c3b96c68>:0
at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback () [0x00000] in <1fd744d936be4a229b315d57c3b96c68>:0

Getting the ip/port server is currently open on in unity embedded server

Quick Description

Could you please add in the current ip/port the server is open on as readonly fields/properties in the unity embedded server script? Its been rather annoying to get it automatically since you switched over to the xml config based system and removed ip and port fields from the scripts.

Documentation typos

Landing page: the last word of the second sentence should be “fullest” and not “full”.

Unity based servers article: the second paragraph should be “subdirectory” and not “subdiectory” and in the third paragraph it should be “privileges” and not “privillages”.

ClientSendMessage return value inverted

Client.SendMessage currently returns true on error and false on success which is inverted to what it should be. The sent messages counter is also incremented on the wrong condition.

Typo in Agar Tutorial

In the Agar tutorial, Server Basics section part of the code references an AgarPluginManager where it should reference AgarPlayerManager.

Excessive Garbage Generation

Quick Description

Large amounts of garbage are being allocated with each send and receive as shown by the profiler screenshots.

Explaination

image
image
Some of the garbage issues are known about and are on the Optimization project board here however the information captured here gives more detail about where the allocations are occuring and a useful reference on what needs fixing. Some of the garbage highlighted is inevitable as it is internally generated by .NET.

Captured using WriteRaw and the writer is created at the exact size needed.

Cliend ID and Server ID mismatch

Quick Description

Client IDs in some cases get mismatched.

Explanation

I have two unity instances running, one for the Client and one for the Server. When I get e.Client.ID on the server the first time, it's 0 as it should be. So is on the client. However, if I disconnect the client and connect again, on client side the ID will again be 0 and on the server side it will be 1.

To reproduce:

  • Send ClientID from the client to the server, Debug.Log it before sending.
  • Debug.Log it once again on server side upon receiving the message.
  • They should both be 0
  • Disconnect the client and connect again
  • Do the same thing with the logging
  • They are 0 and 1 now

Cheers

Feature Request - Multiple SendMessage Overloads

  • Whats your feature is
    A: The feature I'm requesting is for the ability to have a multiple of Overloads for SendMessage

  • How it would work (you can leave this to us though!)
    A: You would overload the SendMessage function to allow sending a smaller tag as a ushort is pretty big compared to what anyone really needs.

  • How it would benefit users
    A: It will benefit the users like myself where our projects are of a scale as such that we need micro optimisation. It will allow us to send packets with less head room for a tag that might not be fully utilized.

Issues in the documentation

Quick Description

"And change the tag parameter in both constructor calls to Tags.SpawnPlayerTag."
https://darkriftnetworking.com/DarkRift2/Docs/2.0.0/html/3fa31013-daf8-49eb-a875-fdbc715cbf70.htm

"That's actually poor phrasing, it's refering to the Message.Create calls" - Jamster

"DarkRiftReader reader = e.Message.GetReader();" - reader is declared twice at the spawn packets code
https://darkriftnetworking.com/DarkRift2/Docs/2.0.0/html/3fa31013-daf8-49eb-a875-fdbc715cbf70.htm

Darkrift Socket Exception: Existing Connection was closed

Quick Description

I face Socket Exception on global server... "Existing Connection was forcefully closed by remote host."

Explaination

UnHandle Socket Exception occur "

A connection Attempt failed because connection party did not respond
Existing Connection was forcefully closed by remote host

then game in mobile is stuck and server not response any client.

How fix this darkrift bug

For Feature Requests

socket error
whatsapp image 2018-06-18 at 1 26 45 pm

Exceptions in Dispatcher Tasks Lose Stack Traces

Quick Description

If an exception arises in a Dispatcher task the stack trace when spat out will not contain detail about the actual location of the exception.

Solution

From here, using ExceptionDispatchInfo.Capture(myEx).Throw(); should allow the stack trace to be preserved.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.