LiveOrDevTrying / Tcp.NET

Tcp.NET provides an easy-to-use and customizable Tcp Server and Tcp Client. The server is created using a TcpListener. The server and client can be used for non-SSL or SSL connections and authentication (including client and server ssl certification validation) is provided for identifying the clients connected to your server. Both client and server are created in .NET Standard and use async await functionality.
Apache License 2.0
42 stars 10 forks source link

Tcp.NET

Tcp.NET provides a robust, performant, easy-to-use, and extendible Tcp server and Tcp client with included authorization / authentication to identify clients connected to your server. All Tcp.NET packages referenced in this documentation are available on the NuGet package manager as separate packages (Tcp.NET.Client or Tcp.NET.Server) or in 1 aggregate package (Tcp.NET). It has a sister-package called WebsocketsSimple which follows the same patterns but for Websocket Clients and Servers.

Image of Tcp.NET Logo

Table of Contents

Client

TcpNETClient

First install Tcp.NET.Client NuGet package using the NuGet package manager:

install-package Tcp.NET.Client

This will add the most-recent version of Tcp.NET.Client package to your specified project.

Create a variable of type ITcpNETClient with the included implementation TcpNETClient.

Signature:

ParamsTcpClientByes can be used instead to specify EndOfLineCharacters, PingCharacters, and PongCharacters as byte arrays:

Example ParamsTcpClient:

    ITcpNETClient client = new TcpNETClient(new ParamsTcpClient("connect.tcp.net", 8989, "\r\n", isSSL: false);

Example ParamsTcpClientBytes:

    var eol = System.Encoding.UTF8.GetBytes("\r\n");
    ITcpNETClient client = new TcpNETClient(new ParamsTcpClientBytes("connect.tcp.net", 8989, eol, isSSL: false);

Parameters

Token

An optional parameter called Token is included in the constructor for ParamsTcpClient to authorize your client with TcpNETServerAuth or a custom Tcp server. Upon a successful connection to the server, the Token you specify and the EndOfLineCharacters or EndOfLineByteswill immediately and automatically be sent to the server.

If you are creating a manual Tcp connection to an instance of TcpNETServerAuth, the first message you must send to the server is your Token followed by EndOfLineCharacters or EndOfLineBytes. This could look similar to the following:

Example:

    yourOAuthTokenGoesHere\r\n

Events

3 events are exposed on the ITcpNETClient interface: MessageEvent, ConnectionEvent, and *ErrorEvent. These event signatures are below:

Signatures:

Example:

    client.MessageEvent += OMessageEvent;
    client.ConnectionEvent += OnConnectionEvent;
    client.ErrorEvent += OnErrorEvent

SSL

SSL is enabled by default for TcpNETClient, but if you would like to disable SSL, set the IsSSL flag in ParamsTcpClient to true. In order to connect successfully to an SSL server, the server must have a valid, non-expired SSL certificate where the certificate's issued hostname matches the host specified in ParamsTcpClient.

Please note that a self-signed certificate or one from a non-trusted Certified Authority (CA) is not considered a valid SSL certificate.

Connect to a Tcp Server

To connect to a Tcp Server, invoke the ConnectAsync() method.

Signature:

Example:

    await client.ConnectAsync();

Send a Message to the Server

2 functions are exposed to send messages to the server:

Signatures:

Example:

    await client.SendAsync("Hello World");

Ping

A TcpNETServer or TcpNETServerAuth will send a ping message to every client at a specified interval defined to verify which connections are still alive. If a client fails to detect the PingCharacters or PingBytes and/or respond with the PongCharacters or PongBytes, during the the next ping cycle, the connection will be severed and disposed. However, if you are using TcpNETClient, the ping / pong messages are digested and handled and will not be emit by MessageEvent. This means you do not need to worry about ping and pong messages if you are using TcpNETClient.

If you are creating your own Tcp connection, you should incorporate logic to listen for PingCharacters or PingBytes. If received, immediately respond with a message containing PongCharacters or PingBytes followed by the EndOfLineCharacters or EndOfLineBytes. This could look similar to the following:

Sent by Server:

    ping\r\n

Response by Client:

    pong\r\n

Note: Failure to implement this logic will result in a connection being severed in up to approximately 240 seconds.

Disconnect from the Server

To disconnect from the server, invoke the DisconnectAsync() method.

Signature:

Example:

    await client.DisconnectAsync();

Disposal

At the end of usage, be sure to call the Dispose() method on TcpNETClient to free allocated memory and resources.

Signature:

Example:

    client.Dispose();

Server

First install the Tcp.NET.Server package using the NuGet package manager:

install-package Tcp.NET.Server

This will add the most-recent version of the Tcp.NET.Server package to your project.

There are 2 different types of Tcp Servers.


TcpNETServer

Create a variable of type ITcpNETServer with the included implementation TcpNETServer. The included implementation includes multiple constructors for SSL connections and non-SSL connections:

Signatures:

ParamsTcpServerBytes can be used instead to specify EndOfLineCharacters, PingCharacters, and PongCharacters as byte arrays

Example non-SSL server:

    ITcpNETServer server = new TcpNETServer(new ParamsTcpServer(8989, "\r\n", connectionSuccesString:"Connected Successfully"));

Example SSL server:

    byte[] certificate = File.ReadAllBytes("cert.pfx");
    string certificatePassword = "yourCertificatePassword";

    ITcpNETServer server = new TcpNETServer(new ParamsTcpServer(8989, "\r\n", connectionSuccessString: "Connected Successfully"), certificate, certificatePassword);

Example SSL server with ParamsTcpServerBytes:

    byte[]  eol = System.Encoding.UTF8.GetBytes("\r\n");

    byte[] certificate = File.ReadAllBytes("cert.pfx");
    string certificatePassword = "yourCertificatePassword";

    ITcpNETServer server = new TcpNETServer(new ParamsTcpServerBytes(8989, eol, connectionSuccessString: "Connected Successfully"), certificate, certificatePassword);

Parameters

Events

4 events are exposed on the ITcpNETServer interface: MessageEvent, ConnectionEvent, ErrorEvent, and ServerEvent. These event signatures are below:

Signatures:

Examples:

    server.MessageEvent += OnMessageEvent;
    server.ConnectionEvent += OnConnectionEvent;
    server.ErrorEvent += OnErrorEvent;
    server.ServerEvent += OnServerEvent;

SSL

To enable SSL, use the provided SSL server constructor and specify your SSL certificate as a byte array and your certificate's private key as a string.

Signatures:

The SSL Certificate MUST match the domain where the Tcp Server is hosted or clients will not able to connect to the Tcp Server.

Example:

    byte[] certificate = File.ReadAllBytes("cert.pfx");
    string certificatePassword = "yourCertificatePassword";

    ITcpNETServer server = new TcpNETServer(new ParamsTcpServer(8989, "\r\n", connectionSuccessString: "Connected Successfully"), certificate, certificatePassword);

In order to allow successful SSL connections, you must have a valid, non-expired SSL certificate. There are many sources for SSL certificates and some of them are open-source (Let's Encrypt).

Note: A self-signed certificate or one from a non-trusted CA is not considered a valid SSL certificate.

Start the Server

To start the server, call the StartAsync() method to instruct the server to begin listening for messages.

Signature:

Example:

    await server.StartAsync();

Send a Message

3 functions are exposed to send messages to connections:

Signatures:

ConnectionTcpServer represents a connected client to the server. These are exposed in ConnectionEvent, can be retrieved from Connections inside of TcpNETServer, or by extending TcpNETServer and using the this._connectionManager object.

An example to send a message to a specific connection could be:

    ConnectionTcpServer connection = server.Connections.FirstOrDefault(x => x.ConnectionId = "desiredConnectionId");

    if (connection != null) {
        await server.SendToConnectionAsync("YourDataPayload", connection);
    }

Ping

TcpNETServer will send a ping message to every client at a specified interval defined by PingIntervalSec (defaults to 120 sec, in ParamsTcpServer) to verify which connections are still alive. If a client fails to detect the PingCharacters or PingBytes and/or respond with the PongCharacters or PongBytes, during the the next ping cycle, the connection will be severed and disposed. However, if you are using TcpNETClient, the ping / pong messages are digested and handled and will not be emit by MessageEvent. This means you do not need to worry about ping and pong messages if you are using TcpNETClient.

If you would like to disable the ping / pong feature, set the PingIntervalSec defined in ParamsTcpServer to 0.

If you are creating your own Tcp connection and PingIntervalSec is greater than 0, you should incorporate logic to listen for PingCharacters or PingBytes. If received, immediately respond with a message containing PongCharacters or PingBytes followed by the EndOfLineCharacters or EndOfLineBytes. This could look similar to the following:

Sent by Server:

    ping\r\n

Response by Client:

    pong\r\n

Disconnect a Connection

To disconnect a connection from the server, invoke the function DisconnectConnectionAsync(ConnectionTcpServer connection).

Signature:

Example:

    await DisconnectConnectionAsync(connection);

ConnectionTcpServer represents a connected client to the server. These are exposed in ConnectionEvent, can be retrieved from Connections inside of TcpNETServer, or by extending TcpNETServer and using the this._connectionManager object.

Stop the Server

To stop the server, call the StopAsync() method.

Signature:

Example:

    await server.StopAsync();

Disposal

After stopping the server, if you are not going to start the server again, call the Dispose() method to free all allocated memory and resources.

Signatures:

Example:

    server.Dispose();

TcpNETServerAuth<T>

TcpNETServerAuth includes authentication for identifying your connections / users.

You will need to define your UserService, so make a new class that implements IUserService. This object includes a generic, T, which represents the datatype of your user unique Id. For example, T could be an int, a string, a long, or a guid - this depends on the datatype of the unique Id you have set for your user. This generic allows the ITcpNETServerAuth<T> implementation to allow authentication and identification of users for any user systems.

Signature:

    public interface IUserService<UId>
    {
        Task<bool> IsValidTokenAsync(string token, CancellationToken cancellationToken = default);
        Task<UId> GetIdAsync(string token, CancellationToken cancellationToken = default);
    }

Example:

    public class UserService : IUserService<long>
    {
        public Task<long> GetIdAsync(string token, CancellationToken cancellationToken = default)
        {
            return Task.FromResult(1);
        }

        public Task<bool> IsValidTokenAsync(string token, CancellationToken cancellationToken = default)
        {
            return token == "testToken" ? Task.FromResult(true) : Task.FromResult(false);
        }
    }

Implement the GetIdAsync() and IsValidTokenAsync() methods to validate the Token that was passed. Generating and validating a token is outside the scope of this document, but for more information, check out OAuthServer.NET or IdentityServer4 for robust, easy-to-implemnt, and easy-to-use .NET identity servers.

Next, create a variable of type ITcpNETServerAuth<T> with the included implementation TcpNETServerAuth<T> where T is the same type as you defined in IUserService. The included implementation includes multiple constructors for SSL connections and non-SSL connections:

Signatures:

All connection objects will contain an UserId which represents the unique identifier for the authenticated user and are exposed from the included events.

ParamsTcpServerAuthBytes can be used instead to specify EndOfLineCharacters, PingCharacters, and PongCharacters as byte arrays:

Example non-SSL server:

    ITcpNETServerAuth<long> server = new TcpNETServerAuth<long>(new ParamsTcpServerAuth(8989, "\r\n", connectionSuccessString: "Connected Successfully", connectionUnauthorizedString: "Connection not authorized"), new UserService());

Example SSL server:

    byte[] certificate = File.ReadAllBytes("yourCert.pfx");
    string certificatePassword = "yourCertificatePassword";

    ITcpNETServerAuth<long> server = new ITcpNETServerAuth<long>(new ParamsTcpServerAuth(8989, "\r\n", connectionSuccessString: "Connected Successfully", connectionUnauthorizedString: "Connection not authorized"), new UserService(), certificate, certificatePassword);

Example SSL server with ParamsTcpServerAuthBytes:

    byte[]  eol = System.Encoding.UTF8.GetBytes("\r\n");

    byte[] certificate = File.ReadAllBytes("yourCert.pfx");
    string certificatePassword = "yourCertificatePassword";

    ITcpNETServerAuth<long> server = new ITcpNETServerAuth<long>(new ParamsTcpServerAuthBytes(8989, eol, connectionSuccessString: "Connected Successfully", connectionUnauthorizedString: "Connection not authorized"), new UserService(), certificate, certificatePassword);

Parameters

IUserService<T>

This is an interface contained in PHS.Networking.Server. The constructor for TcpNETServerAuth<T> requires an IUserService<T>, and this interface will need to be implemented into a concrete class.

A default implementation is not included with Tcp.NET.Server. You will need to implement this interface and add logic here.

An example implementation using Entity Framework is shown below:

    public class UserServiceTcp : IUserService<long>
    {
        protected readonly ApplicationDbContext _ctx;

        public UserServiceTcp(ApplicationDbContext ctx)
        {
            _ctx = ctx;
        }

        public async Task<long> GetIdAsync(string token, CancellationToken cancellationToken = default)
        {
            // Obfuscate the token in the database
            token = Convert.ToBase64String(Encoding.UTF8.GetBytes(token));
            var user = await _ctx.Users.FirstOrDefaultAsync(s => s.OAuthToken == token);
            return user != null ? user.Id : (default);
        }

        public Task<bool> IsValidTokenAsync(string token, CancellationToken cancellationToken = default)
        {
            // Obfuscate the token in the database
            token = Convert.ToBase64String(Encoding.UTF8.GetBytes(token));
            return await _ctx.Users.Any(s => s.OAuthToken == token);
        }
    }

Because you are responsible for creating the logic in GetIdAsync(string token) and IsValidTokenAsync(string token), the data could reside in many stores including (but not limited to) in-memory, database, identity system, or auth systems.

All connection objects will contain an UserId which represents the unique identifier for the authenticated user and are exposed from the included events.

Events

4 events are exposed on the ITcpNETServerAuth<T> interface: MessageEvent, ConnectionEvent, ErrorEvent, and ServerEvent. These event signatures are below:

Signatures:

Examples:

    server.MessageEvent += OMessageEvent;
    server.ConnectionEvent += OnConnectionEvent;
    server.ErrorEvent += OnErrorEvent;
    server.ServerEvent += OnServerEvent;

SSL

To enable SSL, use the provided SSL server constructor and specify your SSL certificate as a byte array and your certificate's private key as a string.

Signatures:

The SSL Certificate MUST match the domain where the Tcp Server is hosted or clients will not able to connect to the Tcp Server.

Example:

    byte[] certificate = File.ReadAllBytes("cert.pfx");
    string certificatePassword = "yourCertificatePassword";

    ITcpNETServerAuth<long> server = new TcpNETServerAuth<long>(new ParamsTcpServerAuth(8989, "\r\n", connectionSuccessString: "Connected Successfully"), new UserManager(), certificate, certificatePassword);

In order to allow successful SSL connections, you must have a valid, non-expired SSL certificate. There are many sources for SSL certificates and some of them are open-source (Let's Encrypt).

Note: A self-signed certificate or one from a non-trusted CA is not considered a valid SSL certificate.

Start the Server

To start the server, call the StartAsync() method to instruct the server to begin listening for messages.

Signature:

Example:

    await server.StartAsync();

Send a Message

To send messages to connections, 6 methods are exposed:

IdentityTcpServer<T> represents a connected client to the server. These are exposed in ConnectionEvent, can be retrieved from Connections inside of TcpNETServerAuth, or by extending TcpNETServerAuth and using the this._connectionManager object.

An example to send a message to a specific connection could be:

    IdentityTcpServer<T> connection = server.Connections.FirstOrDefault(x => x.ConnectionId = "desiredConnectionId");

    if (connection != null) {
        await server.SendToConnectionAsync("YourDataPayload", connection);
    }

Ping

TcpNETServerAuth will send a ping message to every client at a specified interval defined by PingIntervalSec (defaults to 120 sec, in ParamsTcpServerAuth) to verify which connections are still alive. If a client fails to detect the PingCharacters or PingBytes and/or respond with the PongCharacters or PongBytes, during the the next ping cycle, the connection will be severed and disposed. However, if you are using TcpNETClient, the ping / pong messages are digested and handled and will not be emit by MessageEvent. This means you do not need to worry about ping and pong messages if you are using TcpNETClient.

If you would like to disable the ping / pong feature, set the PingIntervalSec defined in ParamsTcpServerAuth to 0.

If you are creating your own Tcp connection and PingIntervalSec is greater than 0, you should incorporate logic to listen for PingCharacters or PingBytes. If received, immediately respond with a message containing PongCharacters or PingBytes followed by the EndOfLineCharacters or EndOfLineBytes. This could look similar to the following:

Sent by Server:

    ping\r\n

Response by Client:

    pong\r\n

Disconnect a Connection

To disconnect a connection from the server, invoke the function DisconnectConnectionAsync(IdentityTcpServer<T> connection).

Signature:

Example:

    await DisconnectConnectionAsync(connection);

IdentityTcpServer<T> represents a connected client to the server. These are exposed in ConnectionEvent, can be retrieved from Connections inside of TcpNETServerAuth, or by extending TcpNETServerAuth and using the this._connectionManager object.

Stop the Server

To stop the server, call the StopAsync() method.

Signature:

Example:

    await server.StopAsync();

Disposal

After stopping the server, if you are not going to start the server again, call the Dispose() method to free all allocated memory and resources.

Signatures:

Example:

    server.Dispose();

Additional Information

Tcp.NET was created by Rob Engel - LiveOrDevTrying - and is maintained by Pixel Horror Studios. Tcp.NET is currently implemented in (but not limited to) the following projects: The Monitaur, Allie.Chat, and Gem Wars (currently in development). It is used in the following packages: WebsocketsSimple, NTier.NET, and The Monitaur.

Pixel Horror Studios Logo