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 console
which 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.Enjent
namespace which holds theWebSocketServer
class, among others. - Next, create, configure and start a
WebSocketServer
. Here's an example of a typical use of theWebSocketServer
class.
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
WebSocketServer
class is instantiated, it creates a newSocket
object. This socket will be responsible for listening for incoming HTTP requests. - When the
WebSocketServer.Start()
method is called, theWebSocketServer
class tries to bind it's HTTP listenerSocket
on the specifiedEndpoint
and 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 aThreadPool
thread to negotiates the WebSocket connection as per the RFC 6455 specifications and returns a101 Upgrade
HTTP 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
WebSocketServer
object will invoke itsOnConnect
event, providing your code with aWebSocketClient
object 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, whereTWebSocketClient
is a type that is derived fromWebSocketClient
. The genericWebSocketServer<TWebSocketClient>
class requires that you provide aClientInitializationStrategy
, which is a callback that is invoked right before theOnConnect
event and that is used to initialize an instance of your customTWebSocketClient
type. - When the
WebSocketServer
receives a WebSocketFrame
from a client, theOnMessage
event 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
WebSocketServer
invokes theOnDisconnect
event 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