This repository is a class library that implements the WebSocketProtocol on the server side only. It it very performance focused and is able to handle around 8000 clients with ± 50 MB RAM and ± 1% CPU on a quad core machine in production.
Follow these steps to start using this library
- If you don't already have a dotnet project, start by creating a new one with
dotnet new consolewhich will create a new blank Console application. - Download and add a reference ot the NuGet package by running `dotnet add reference <path/to/nuget/file>. This step will become simpler once the classlib is live on nuget.org
- Now you will have to 'use' the
NarcityMedia.Enjentnamespace which holds theWebSocketServerclass, among others. - Next, create, configure and start a
WebSocketServer. Here's an example of a typical use of theWebSocketServerclass.
using System;
using System.Net;
using NarcityMedia.Enjent;
namespace UseSocketServer
{
class Program
{
static void Main(string[] args)
{
// Instantiate a WebSocketServer - it won't be running at this point
WebSocketServer Wss = new WebSocketServer();
// Add event handlers
Wss.OnConnect += HandleConnect;
Wss.OnMessage += HandleMessage;
Wss.OnDisconnect += HandleDisconnect;
// Start the server
try
{
Wss.Start(new IPEndPoint(IPAddress.Loopback, 13003));
}
catch (WebSocketServerException e)
{
Console.WriteLine("An error occured when starting the WebSocket server - " + e.Message);
}
}
public static void HandleConnect(object sender, WebSocketServerEventArgs args)
{
// Execute logic on socket connection
Console.WriteLine("Got a new socket ID: " + args.Cli.Id);
}
public static void HandleMessage(object sender, WebSocketServerEventArgs args)
{
// Execute logic on socket message
Console.WriteLine(String.Format("User {0} says : {1}", args.Cli.Id, args.DataFrame.Plaintext));
}
public static void HandleDisconnect(object sender, WebSocketServerEventArgs args)
{
// Execute logic on socket disconnect
Console.WriteLine(String.Format("User {0} disconnected", args.Cli.Id));
}
}
}Your output should look something like this
That's it! You're good to go!
At this point, we do not consider this classlib to be 'production ready', a lot of testing and improvements remain to be done.
However, if you were to deploy this to the public, you need to use the wss uri scheme (which is a TLS layer over the WebSocket protocol) to connect to the server from a cient. Most browsers will disallow connecting via plain ws to a server if you're not connecting to localhost and as a responsible developer, you should not allow application data to be transfer unciphered anyway.
In order to enable clients to connect via wss, you will have to deploy your WebSocket server behind a server that will act as a reverse proxy to handle ciphered traffic such as NGINX or Apache. This classlib is not intended to be deployed directly to the Internet.
This is an example of how you could configure an NGINX server to handle ciphered traffic and reverse proxy requests coming from the WebSocket client to your WebSocket server.
upstream dotnet_websocket_proxy {
server 127.0.0.1:13003;
keepalive 64;
}
server {
server_name websocket.your_domain.com;
root /some/public/directory; # Could be /var/www/yoursite
listen 443 ssl;
# If you use Certbot to generate HTTPS certs, it will likely add the following part automatically
ssl_certificate /etc/letsencrypt/live/websocket.your_domain.com/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/websocket.your_domain.com/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
# End of Certbot section
location / {
add_header "Access-Control-Allow-Origin" "https://your_domain.com";
add_header "Access-Control-Allow-Methods" "GET, POST, OPTIONS, HEAD";
add_header "Access-Control-Allow-Headers" "Authorization, Origin, X-Requested-With, Content-Type, Accept, Sec-WebSocket-Version, Sec-We
proxy_set_header "Access-Control-Allow-Origin" "https://your_domain.com";
proxy_set_header "Access-Control-Allow-Methods" "GET, POST, OPTIONS, HEAD";
proxy_set_header "Access-Control-Allow-Headers" "Authorization, Origin, X-Requested-With, Content-Type, Accept, Sec-WebSocket-Version,
# If you receive real ip from Cloudflare, this will map it to "X-Real-Ip" header
proxy_set_header X-Real-Ip $http_cf_connecting_ip;
proxy_set_header Host $http_host;
proxy_set_header Host $host;
proxy_buffers 8 32k;
proxy_buffer_size 64k;
proxy_pass http://dotnet_websocket_proxy;
}
}
If you are interested in exploring the 'low-level' workings of the WebSocket protocol itself, there really is no better way than to start by reading the WebSocket specification (RFC 6455).
Here's an oversimplified overview of the workings of this classlib:
- When the
WebSocketServerclass is instantiated, it creates a newSocketobject. This socket will be responsible for listening for incoming HTTP requests. - When the
WebSocketServer.Start()method is called, theWebSocketServerclass tries to bind it's HTTP listenerSocketon the specifiedEndpointand listens for incoming connections using a dedicated Thread as to not block the execution of your code that called theWebSocketServer.Start()method. - When an HTTP request is accepted by the
WebSocketServer, it uses aThreadPoolthread to negotiates the WebSocket connection as per the RFC 6455 specifications and returns a101 UpgradeHTTP status code to the client if all went well. This way, it can go back to listening for HTTP requests immediately. - In the case where the negotiation was successful, the
WebSocketServerobject will invoke itsOnConnectevent, providing your code with aWebSocketClientobject that represents the newly acquired connection.
Note: If you wish to use a custom object instead of the defaultWebSocketClient, you can use the genericWebSocketServer<TWebSocketClient>class, whereTWebSocketClientis a type that is derived fromWebSocketClient. The genericWebSocketServer<TWebSocketClient>class requires that you provide aClientInitializationStrategy, which is a callback that is invoked right before theOnConnectevent and that is used to initialize an instance of your customTWebSocketClienttype. - When the
WebSocketServerreceives a WebSocketFramefrom a client, theOnMessageevent event is invoked if the received frame was a 'data frame', that is, a frame that is not a 'control frame'. Reffer to the WebSocket specification (RFC 6455) specification for the definition of a 'data frame' and a 'control frame'. - The
WebSocketServerinvokes theOnDisconnectevent when a client closes the connection to the server.
Mozilla Public License Version 2.0
TL;DR : You can use the library to make money as long as it remains open source. The typical use case involves no additional work.
Both individuals and businesses are allowed to use Enjent. You are allowed to host a copy of Enjent, modify it, redistribute it, create something different with it.
One important thing to note : you must disclose the source code, copyright, and must document any modification done. A copy of the license must be available. However, this does not mean you need to credit Narcity Media on your website or anything of the sort. Simply make the source code available and highlight the modifications if any.
That being said, you can still use Enjent for almost any purposes.
Like most open source licenses, Narcity Media is not liable if anything happens with your server, data, etc. Enjent does not come with a warranty.
The previous information is not legal advice, but should give you a good idea.
Mozilla Public License Version 2.0 is a simple, permissive license with conditions easy to respect. There have a great FAQ here.
© Narcity Media, 2019
