Skip to content

Commit

Permalink
Removed async voids, code cleanup, rooms now check for duplicate peer…
Browse files Browse the repository at this point in the history
… id, documentation
  • Loading branch information
leonidumanskiy committed Dec 6, 2021
1 parent 6b5f5d9 commit 8d72cca
Show file tree
Hide file tree
Showing 22 changed files with 530 additions and 68 deletions.
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ Fenrir provides a great balance between performance and ease of development and
- [Quick Start](/docs/QuickStart.md)
- [Networking](/docs/NetworkingBasics.md)
- [Connection](/docs/Connection.md)
- [Server Info](/docs/ServerInfo.md)
- [Connection Request](/docs/ConnectionRequest.md)
- [Peer Object](/docs/PeerObject.md)
- [Reliability](/docs/Reliability.md)
- [Serialization](/docs/Serialization.md)
Expand Down
143 changes: 140 additions & 3 deletions docs/Connection.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,145 @@

This section explains details of client to server connections.

Previous Section: [Networking](/NetworkingBasics.md)
Previous Section: [Networking](NetworkingBasics.md)

TODO
## Connection Basics

Next Section: [Server Info](/ServerInfo.md)
Fenrir Multiplayer uses UDP as a primary protocol for communication between client and server.
Since UDP is a connectionless protocol, a concept of "connection" is proided by the SDK.

## Server Info

In order to establish a server-client connection, a client need to be provided a **Server Info** object.

Client can either obtain that object from an http(s) endpoint, or be directly provided with it.

By default, all Fenrir Servers expose the **Server Info http endpoint** which binds to the same port as UDP.
Server Info endpoint is very useful for local testing and connecting to any specific server instance directly.

In production setups, very often the Server Info will be provided by the matchmaking server. In that case, TCP port does not need to be exposed.

** TODO: Endpoint vs UDP infographic **

**Example connecting via http endpoint**:

Server code:

```csharp
using var networkServer = new NetworkServer() { BindPort = 27016 }; // Binds both UDP and TCP (http)
networkServer.Start();
```

You can now test the http endpoint:

```bash
$ curl localhost:27016
{
"Hostname":"127.0.0.1",
"ServerId":"8284ba32-4b36-4b31-89ad-ae3cb412d3ab",
"Protocols": [
{
"ProtocolType": "LiteNet",
"ConnectionData": {
"Port":27016,
"IPv6Mode":"DualMode"
}
}
]
}
```

Connect using http endpoint:

```csharp
using var networkClient = new NetworkClient();
var connectionResponse = await networkClient.Connect("http://localhost:27016");
```

Connect using ServerInfo object directly:

```csharp
// Example code, in the real-world this info should come from a matchmaker or a master server (e.g. service discovery)
var serverInfo = new ServerInfo()
{
Hostname = "127.0.0.1",
ServerId = "8284ba32-4b36-4b31-89ad-ae3cb412d3ab", // Should match the server GUID
Protocols = new ProtocolInfo[]
{
// Add allowed protocols
new ProtocolInfo(ProtocolType.LiteNet, new LiteNetProtocolConnectionData(27016))
}
};

using var networkClient = new NetworkClient(logger);
await networkClient.Connect(serverInfo);
```

## Custom Connection Request

Custom connection request can be used to provide additional means of authentication, validation of connecting players etc.

First, define your custom connection request object. This should be defined in your **Shared** data models project.

```csharp
class MyConnectionRequestData : IByteStreamSerializable
{
public string UserName;
public string AuthToken;

public void Serialize(IByteStreamWriter writer)
{
writer.Write(UserName);
writer.Write(AuthToken);
}

public void Deserialize(IByteStreamReader reader)
{
UserName = reader.ReadString();
AuthToken = reader.ReadString();
}
}
```

Next, add your custom connection request handler in the server code:

```csharp
// Custom asynchronous Connection Request Handler
async Task<ConnectionResponse> MyConnectionRequestHandler(IServerConnectionRequest<MyConnectionRequestData> connectionRequest)
{
string userName = connectionRequest.Data.UserName;
string authToken = connectionRequest.Data.AuthToken;

bool result = await ValidateUserAsync(userName, authToken); // e.g. send to some web api for validation
if(result)
return new ConnectionResponse(true);
else
return new ConnectionResponse(false, "Failed to check auth token");
}

// Create server
using var networkServer = new NetworkServer() { BindPort = 27016 };

// Set connection request handler
networkServer.SetConnectionRequestHandler<MyConnectionRequestData>(MyConnectionRequestHandler);

//Start
networkServer.Start();
```

Finally, you can connect to your server using custom connection request:

```csharp
// Create custom connection request data
var connectionRequest = new MyConnectionRequestData() { UserId = "test_user", AuthToken = "test_token" };

// Create client
using var networkClient = new NetworkClient();

// Connect using custom data
var connectionResponse = await networkClient.Connect("http://127.0.0.1:27016", connectionRequest);
```

Next Section: [Peer Object](PeerObject.md)
Table of Contents: [Documentation Home](DocumentationIndex.md)
9 changes: 0 additions & 9 deletions docs/ConnectionRequest.md

This file was deleted.

3 changes: 1 addition & 2 deletions docs/DocumentationIndex.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ This section contains Table of Contents for the Fenrir Multiplayer SDK documenta
- [Quick Start](QuickStart.md)
- [Networking](NetworkingBasics.md)
- [Connection](Connection.md)
- [Server Info](ServerInfo.md)
- [Connection Request](ConnectionRequest.md)
- [Peer Object](PeerObject.md)
- [Reliability](Reliability.md)
- [Serialization](Serialization.md)
Expand All @@ -18,3 +16,4 @@ This section contains Table of Contents for the Fenrir Multiplayer SDK documenta
- [Docker Setup](DockerBasics.md)
- [Thread Safety](ThreadSafety.md)
- [Client and Server Lifecycle](Lifecycle.md)
- [FAQ](FAQ.md)
4 changes: 4 additions & 0 deletions docs/FAQ.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
- Type not found in hash
TODO
- MTU
TODO
1 change: 1 addition & 0 deletions docs/Installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@ dotnet add package Fenrir.Multiplayer
```

Next Section: [Quick Start](QuickStart.md)
Table of Contents: [Documentation Home](DocumentationIndex.md)
39 changes: 39 additions & 0 deletions docs/Logging.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,42 @@ using var networkServer = new NetworkServer(eventBasedLogger);
```

Next Section: [Docker Setup](/DockerSetup.md)

## Message Debugging

When turned on, will include additional text information to each message (Request, Response or Event) when sending over the wire.

This can be useful specifically for debugging issues with unknown message type hashes, etc.

Enable in client (Client will add debug information to all Requests sent to a Server):

```csharp
using var networkClient = new NetworkClient();
var connectionResponse = await networkClient.Connect("http://localhost:27016");
networkClient.Peer.WriteDebugInfo = true;
```

Enable in server (Server will add debug information to all Responses and Events sent back to the specific client):

```csharp
using var networkServer = new NetworkServer();
networkServer.PeerConnected += (sender, e) => {
e.Peer.WriteDebugInfo = true;
};
networkServer.Start();
```

**⚠ Warning: Enabling Message Debugging negatively affects performance and adds significant amount of data to each UDP message, and could easily bring message size over the MTU.**
You should never use this option in Release builds. Only use when needed.

Without Message Debugging:

```csharp
Unexpected message with type hash 9223372036854775807
```

With Message Debugging:

```csharp
Unexpected message with type hash 9223372036854775807. Message debug info: MessageType=Event, MessageDataType=RoundStartEvent, RequestId=1412 Channel=1, Flags=MessageFlags.IsDebug, DeliveryMethod=MessageDeliveryMethod.ReliableOrdered
```
7 changes: 4 additions & 3 deletions docs/NetworkingBasics.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

This section explains Fenrir networking model.

Previous Section: [Quick Start](/QuickStart.md)
Previous Section: [Quick Start](QuickStart.md)

Fenrir Multiplayer uses basic messaging primitives: **Requests**, **Responses**, and **Events**.

![Request Response Event](/images/RequestResponseEvent.png)
![Request Response Event](images/RequestResponseEvent.png)

**Request**
Very much like HTTP, requests are sent from the client to a server. **Request** can optionally have a **Response**.
Expand Down Expand Up @@ -231,4 +231,5 @@ await networkClient.Connect("http://127.0.0.1:27016");
// After a successful connection, client prints "Hello from Mr.Server"
```

Next Section: [Connection](/Connection.md)
Next Section: [Connection](Connection.md)
Table of Contents: [Documentation Home](DocumentationIndex.md)
95 changes: 92 additions & 3 deletions docs/PeerObject.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,97 @@

This section explains Server and Client Peer objects

Previous Section: [Connection Request](/Connection Request.md)
Previous Section: [Connection](Connection.md)

TODO
## Multiplayer Peers

Next Section: [Reliability](/Reliability.md)
**Peer** is simply a player that's being connected to a server.

**Client Peer** is a player connected on the client side. In the client code, there is always only one Client Peer.

**Server Peer** is a player connected on the server side. One server could have thousands of connected ServerPeers.

## Identifying Server Peer

There are two main ways of uniquely identifying each Peer.

## Server Peer Data

Each peer has an arbitrary `PeerData` object that you can assign. The `PeerData` object can work as a tag or an id, or may contain any reference of your choice.

A common patern is to assign custom "Player" object to `PeerData`:

```csharp
class MyPlayer
{
public string Name;
}

using var networkServer = new NetworkServer() { BindPort = 27016 };
networkServer.PeerConnected += (sender, e) =>
{
e.Peer.PeerData = new MyPlayer(){ Name = "name" };
};
networkServer.
```


### Unique Peer Id

On the `NetworkClient`, you can set a property named `ClientId`. This property will match `Peer.Id` on the server:

```csharp
using var networkClient = new NetworkClient(logger) { ClientId = "player1" };
await networkClient.Connect("http://127.0.0.1:27016");
```

When accessing server peer on the server, it will now have a given id:

```csharp
using var networkServer = new NetworkServer() { BindPort = 27016 };
networkServer.PeerConnected += (sender, e) => {
Console.WriteLine(e.Peer.Id); // prints: "player1"
};
networkServer.Start();
```

**⚠ Warning: By default, Peer Id is not validated for uniqueness or in any way.**

In many cases, clients "disconnect" by timing out.
Depending on the timeout, it is possible that the same client (with the same Peer Id) will attempt to connect while previous connection on the server has not timed out yet.
It is up to a developer to decide how to handle this case.

A common solution is to check if peer with the same id is already connected and prompt to kick them. However you may want to keep both peers connected at the same time and not rely on Peer Id for communication.

**Rooms, however, do rely on the Peer Id to be unique.**
By default, room will not allow anyone with the same Peer Id to join while peer with the same ID is still in that room.

You can override this behavior by overriding `ServerRoom.OnBeforePeerJoin` and kicking the previous player:

```csharp
class MyRoom : ServerRoom
{
...

protected override RoomJoinResponse OnBeforePeerJoin(IServerPeer peer, string token)
{
// Get already connected peer
if(Peers.TryGetValue(peer.Id, out IServerPeer connectedPeer))
{
// Kick, send them an example "KickEvent" that you might want to create
peer.SendEvent(new KickEvent() { Reason = "Connected from elsewhere" });
RemovePeer(connectedPeer);
}

// Allow new peer to join
return new RoomJoinResponse(true);
}

...
}
```

See also: [RoomManagement](RoomManagement.md)


Next Section: [Reliability](Reliability.md)
9 changes: 5 additions & 4 deletions docs/QuickStart.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,19 @@

This section contains Quick Start Guide for Unity Package of the Fenrir Multiplayer SDK.

Previous Section: [Installation](/Installation.md)
Previous Section: [Installation](Installation.md)

## Server Project

Fenrir Multiplayer Unity Package comes with a server template that is recommended (but is not strictly required) to use.

To generate a server project, click **Window****Fenrir****Open Server Project**.

![Server Project](/images/OpenServerProject.png)
![Server Project](images/OpenServerProject.png)

If a project server has never been generated, dialogue window will open asking to generate the package. Select **Generate**.

![Generate Server Project](/images/GenerateServerProject.png)
![Generate Server Project](images/GenerateServerProject.png)

Editor script will generate and open a .NET Solution in the folder next to Assets:

Expand Down Expand Up @@ -60,4 +60,5 @@ else
Debug.Log("Failed to connect:" + connectionResponse.Reason);
```

Next Section: [Networking Basics](/NetworkingBasics.md)
Next Section: [Networking Basics](NetworkingBasics.md)
Table of Contents: [Documentation Home](DocumentationIndex.md)
Loading

0 comments on commit 8d72cca

Please sign in to comment.