Netly version 4 will be released soon, help validating the new way of interacting with netly. See more
⭐ Your star on Netly brightens our journey and makes a real impact! |
powered by ALEC1O
🚀 Fully Implemented | Byter 3 • TCP Client • TCP Server • UDP Client • UDP Server • HTTP Client • HTTP Server • HTTP WebSocket • RUDP Client • RUDP Server |
---|---|
🔧 Work in Progress |
Documentation v4 #63 (New docs website) HTTP Body (Enctype Detector and Parser) #67 (Body Parser as Middleware) |
🔜 Pending Features | Adding Byter v4 *In development Adding RUDP tests Adding HTTP tests Adding Websocket tests |
Get basic information about this project called Netly
Overview |
Netly is a robust C# socket library designed to streamline network communication. It offers comprehensive support for multiple protocols, including HTTP, TCP, SSL/TLS, UDP, Reliable UDP (RUDP), and WebSocket. This versatility makes Netly an excellent choice for developing a wide range of applications, from multiplayer games and chat systems to real-time data exchanges. |
---|---|
Website |
Repository: github.com/alec1o/netly Documentation: netly.docs.kezero.com |
Sponsor |
|
Supporter |
|
Official publisher
Nuget | Unity Asset Store |
---|---|
Install on Nuget | Install on Asset Store |
Notable changes
v1.x.x | v2.x.x | v3.x.x | v4.x.x |
---|---|---|---|
Legacy | Legacy | Stable | Development |
TCP Support | TCP with Message Framing support | TCP with TLS/SSL support | HTTP client and server support |
UDP Support | TCP and UDP performance increase | UDP with connection (timeout response) | Reliable UDP (RUDP) client and server support |
New Message Framing protocol and performance increase | WebSocket client and server support | ||
Upgrade to Byter 2.0 | Upgrade to Byter 3.0 | ||
Docsify as documentation framework | Documentation improvement by Docusaurus and DocFxMarkdownGen | ||
Syntax and internal improvement | |||
XML comments improvement |
Technical descriptions about integrations
List of tested platforms |
|
---|---|
Dependencies |
Byter |
Build |
# 1. clone project
$ git clone "https://github.com/alec1o/Netly" netly
# 2. build project
$ dotnet build "netly/" -c Release -o "netly/bin/"
# NOTE:
# Netly.dll require Byter.dll because is Netly dependency
# Netly.dll and Byter.dll have on build folder <netly-path>/bin/ |
Features |
|
Code highlights
TCP |
📄 Clientusing Netly;
TCP.Client client = new TCP.Client(framing: true); client.On.Open(() =>
{
printf("connection opened");
});
client.On.Close(() =>
{
printf("connetion closed");
});
client.On.Error((exception) =>
{
printf("connection erro on open");
});
client.On.Data((bytes) =>
{
printf("connection receive a raw data");
});
client.On.Event((name, data) =>
{
printf("connection receive a event");
});
client.On.Modify((socket) =>
{
printf("called before try open connection.");
});
client.On.Encryption((certificate, chain, errors) =>
{
// Only if client.IsEncrypted is enabled
printf("validate ssl/tls certificate");
// return true if certificate is valid
return true;
}); // open connection if closed
client.To.Open(new Host("127.0.0.1", 8080));
// close connection if opened
client.To.Close();
// send raw data if connected
client.To.Data(new byte[2] { 128, 255 });
client.To.Data("hello world", NE.Encoding.UTF8);
// send event if connected
client.To.Event("name", new byte[2] { 128, 255 });
client.To.Event("name", "hello world", NE.Encoding.UTF8);
// enable encryption (must call before client.To.Open)
client.To.Encryption(true); 📄 Serverusing Netly;
TCP.Server server = new TCP.Server(framing: true); server.On.Open(() =>
{
printf("connection opened");
});
server.On.Close(() =>
{
printf("connection closed");
});
server.On.Error((exception) =>
{
printf("connection error on open");
});
server.On.Accept((client) =>
{
client.On.Modify((socket) =>
{
printf("modify client socket e.g Enable NoDelay");
});
client.On.Open(() =>
{
printf("client connected");
});
client.On.Data((bytes) =>
{
printf("client receive a raw data");
});
client.On.Event((name, bytes) =>
{
printf("client receive a event");
});
client.On.Close(() =>
{
printf("client disconnected");
});
});
server.On.Modify((socket) =>
{
printf("called before try open connection.");
}); // open connection
server.To.Open(new Host("1.1.1.1", 1111));
// close connection
server.To.Close();
// enable encryption support (must called before server.To.Open)
server.To.Encryption(enable: true, @mypfx, @mypfxpassword, SslProtocols.Tls12);
// broadcast raw data for all connected client
server.To.DataBroadcast("text buffer");
server.To.DataBroadcast(new byte[] { 1, 2, 3 });
// broadcast event (netly event) for all connected client
server.To.EventBroadcast("event name", "text buffer");
server.To.EventBroadcast("event name", new byte[] { 1, 2, 3 }); |
---|---|
UDP |
📄 Clientusing Netly;
UDP.Client client = new UDP.Client(); client.On.Open(() =>
{
printf("connection opened");
});
client.On.Close(() =>
{
printf("connection closed");
});
client.On.Error((exception) =>
{
printf("connection error on open");
});
client.On.Data((bytes) =>
{
printf("connection received a raw data");
});
client.On.Event((name, eventBytes) =>
{
printf("connection received a event");
});
client.On.Modify((socket) =>
{
printf("called before try open connection.");
}); // open connection if closed
client.To.Open(new Host("127.0.0.1", 8080));
// close connection if opened
client.To.Close();
// send raw data if connected
client.To.Data(new byte[2] { 128, 255 });
client.To.Data("hello world", NE.Encoding.UTF8);
// send event if connected
client.To.Event("name", new byte[2] { 128, 255 });
client.To.Event("name", "hello world", NE.Encoding.UTF8); 📄 Serverusing Netly;
UDP.Server server = new UDP.Server(); server.On.Open(() =>
{
printf("connection opened");
});
server.On.Close(() =>
{
printf("connection closed");
});
server.On.Error((exception) =>
{
printf("connection error on open");
});
server.On.Accept((client) =>
{
client.On.Open(() =>
{
printf("client connected");
});
client.On.Close(() =>
{
// Only if use connection is enabled.
printf("client disconnected");
});
client.On.Data((bytes) =>
{
printf("client received a raw data");
});
client.On.Event((name, bytes) =>
{
printf("client received a event");
});
}); // open connection
server.To.Open(new Host("127.0.0.1", 8080));
// close connection
server.To.Close();
// broadcast raw data for all connected client
server.To.DataBroadcast("text buffer");
server.To.DataBroadcast(new byte[] { 1, 2, 3 });
// broadcast event (netly event) for all connected client
server.To.EventBroadcast("event name", "text buffer");
server.To.EventBroadcast("event name", new byte[] { 1, 2, 3 });
|
HTTP |
📄 Clientusing Netly;
HTTP.Client client = new HTTP.Client();
// add http header for request
client.Headers.Add("Content-Type", "json");
client.Headers.Add("Token", "ImGui.h");
// add http url queries e.g: https://www.alec1o.com/?page=about&version=4
client.Queries.Add("page", "about");
client.Queries.Add("version", "4");
// set request timeout (ms) default 15s (15000ms), 0 or negative value means infinite timeout.
client.Timeout = 6000; // 6s
// is opened: while is requesting
bool isFetching = client.IsOpened; HttpClient http = null;
// called before try connect to server
// modify the HttpClient object
client.On.Modify((HttpClient instance) =>
{
http = instance;
});
// connection is opened and fetch server.
client.On.Open((response) =>
{
// you can use "http" instance on this scope (isn't null)
if (http.<foo> == <bar>) { ... }
});
// erro on fetch, it can be timeout or whatever error
// but if you receives error it mean the operation is called or done
client.On.Error((Exception exception) =>
{
Ny.Logger.PushError(exception);
});
// connection is closed with fetch server.
client.On.Close(() =>
{
if (http.<bar> == <foo>) { ... }
}); // used to fetch a server
client.To.Open("method e.g GET", "url", "body, allow null");
// used for cancel opened request
client.To.Close(); 📄 Serverusing Netly;
HTTP.Server server = new HTTP.Server();
// return true if server is serve http context
bool isServe = server.IsOpened; server.On.Open(() =>
{
// http server opened
});
server.On.Close(() =>
{
// http server closed
});
server.On.Error((exception) =>
{
// http server open error
});
server.On.Modify((httpListener) =>
{
// HttpListener instance, called before try open connection.
});
// Open http server connection
server.To.Open(new Uri("http://127.0.0.1:8080/"));
// Close http server connection
server.To.Close(); // Map path
server.Map.Get("/", async (req, res) => {
// Handle async: GET
})
server.Map.Post("/user", (req, res) => {
// Handle sync: POST
});
// map using dynamic URL
server.Map.Delete("/post/{userId}/group/{groupId}", async (req, res)) =>
{
string userId = req.Param["userId"];
string groupId = req.Param["groupId"];
// Handle async: Delete from dynamic URL path
});
server.Map.WebSocket("/echo", (req, ws) =>
{
// Handle websocket connection from path
});
/*
You can map:
* Get # get request
* Post # post request
* Delete # delete request
* Put # put request
* Patch # patch request
* Trace # trace request
* Options # options request
* Head # head request, (only head)
* All # all http nethod request
* WebSocket # websocket request
*/
/*
Note: Middlewares is executed in added order
*/
// Global Middleware (*don't have workflow path)
server.Middleware.Add(async (req, res, next) => {
// verify request timer
Stopwatch watch = new Stopwatch(); // init timer
next(); // call another middleware.
watch.Stop(); // stop timer
res.Header.Add("Request-Timer", watch.ElapsedMilliseconds.ToString());
});
// Local middleware (have workflow path)
server.Middleware.Add("/admin", async (req, res, next) => {
if (MyApp.CheckAdminByHeader(req.Header))
{
res.Header.Add("Admin-Token", MyApp.RefreshAdminHeaderToken(req));
// call next middleware
next();
// now. all middleware is executed. (because this is two way middleware)
res.Header.Add("Request-Delay", (DateTime.UtcNow - timer)());
}
else
{
res.Header.Add("Content-Type", "application/json;charset=UTF-8");
await res.Send(404, "{ 'error': 'invalid request.' }");
// skip other middlewares:
// next();
}
}); |
RUDP |
📄 Clientusing Netly;
RUDP.Client client = new RUDP.Client(); client.On.Open(() =>
{
printf("connection opened");
});
client.On.Close(() =>
{
printf("connection closed");
});
client.On.Error((exception) =>
{
printf("connection error on open");
});
client.On.Data((bytes, type) =>
{
printf("connection received a raw data");
});
client.On.Event((name, bytes, type) =>
{
printf("connection received a event");
});
client.On.Modify((socket) =>
{
printf("called before try open connection.");
}); // open connection if closed
client.To.Open(new Host("127.0.0.1", 8080));
// close connection if opened
client.To.Close();
// send raw data if connected
client.To.Data(new byte[2] { 128, 255 }, RUDP.Unreliable);
client.To.Data("hello world", NE.Encoding.UTF8, RUDP.Reliable);
// send event if connected
client.To.Event("name", new byte[2] { 128, 255 }, RUDP.Unreliable);
client.To.Event("name", "hello world", NE.Encoding.UTF8, RUDP.Reliable); 📄 Serverusing Netly;
RUDP.Server server = new RUDP.Server(); server.On.Open(() =>
{
printf("connection opened");
});
server.On.Close(() =>
{
printf("connection closed");
});
server.On.Error((exception) =>
{
printf("connection error on open");
});
server.On.Accept((client) =>
{
client.On.Open(() =>
{
printf("client connected");
});
client.On.Close(() =>
{
// Only if use connection is enabled.
printf("client disconnected");
});
client.On.Data((bytes, type) =>
{
if (type == RUDP.Reliable) { ... }
else if (type == RUDP.Unreliable) { ... }
else { ... } /* type == RUDP.Sequenced */
printf("client received a raw data");
});
client.On.Event((name, type) =>
if (type == RUDP.Reliable) { ... }
else if (type == RUDP.Unreliable) { ... }
else { ... } /* type == RUDP.Sequenced */
printf("client received a event");
});
}); // open connection
server.To.Open(new Host("127.0.0.1", 8080));
// close connection
server.To.Close();
// broadcast raw data for all connected client
server.To.DataBroadcast("text buffer", RUDP.Unreliable);
server.To.DataBroadcast(new byte[] { 1, 2, 3 }, RUDP.Reliable);
server.To.DataBroadcast(new byte[] { 3, 2, 1 }, RUDP.Sequenced);
// broadcast event (netly event) for all connected client
server.To.EventBroadcast("event name", "text buffer", RUDP.Unreliable);
server.To.EventBroadcast("event name", new byte[] { 1, 2, 3 }, RUDP.Reliable);
server.To.EventBroadcast("event name", new byte[] { 3, 2, 1 }, RUDP.Sequenced); |
WebSocket |
📄 Clientusing Netly;
HTTP.WebSocket client = new HTTP.WebSocket(); client.On.Open(() =>
{
// websocket connection opened
});
client.On.Close(() =>
{
// websocket connection closed
});
client.On.Error((exception) =>
{
// error on open websocket connectin
});
client.On.Data((bytes, type) =>
{
if (type == HTTP.Binary) { ... }
else if (type == HTTP.Text) { ... }
else { /* NOTE: it's imposible */ }
// raw data received from server
});
client.On.Event((name, bytes, type) =>
{
if (type == HTTP.Binary) { ... }
else if (type == HTTP.Text) { ... }
else { /* NOTE: it's imposible */ }
// event received from server
});
client.On.Modify((wsSocket) =>
{
// modify websocket socket
}); // open websocket client connection
client.To.Open(new Uri("ws://127.0.0.1:8080/echo"));
// close websocket client connection
client.To.Close();
// send raw data for server
// text message
client.To.Data("my message", HTTP.Text);
// binnary message
client.To.Data(NE.GetBytes("my buffer"), HTTP.Binary);
// send event (netly event) for server
// text message
client.To.Event("event name", "my message", HTTP.Text);
// binnary message
client.To.Data("event name", NE.GetBytes("my buffer"), HTTP.Binary); 📄 Serverusing Netly;
using Netly.Interfaces;
HTTP.Server server = new HTTP.Server();
IHTTP.WebSocket[] Clients = server.WebSocketClients; server.Map.WebSocket("/chat/{token}", async (req, ws) =>
{
// Accept websocket from dynamic path
string token = req.Params["token"];
// validate websocket connection from params
if (Foo.Bar(token) == false)
{
ws.To.Close();
}
ws.On.Modify(...);
ws.On.Open(...);
ws.On.Close(...);
ws.On.Data(...);
ws.On.Event(...);
});
server.Map.Websocket("/echo", (req, ws) =>
{
// Handle websocket on /echo path
ws.On.Modify((wsSocket) =>
{
// modify server-side websocket ocket
});
ws.On.Open(() =>
{
// server-side websocket connection opened
});
ws.On.Close(() =>
{
// server-side websocket connection closed
});
ws.On.Data((bytes, type) =>
{
if (type == HTTP.Binary) { ... }
else if (type == HTTP.Text) { ... }
else { /* NOTE: it's imposible */ }
// server-side websocket received raw data
});
ws.On.Event((name, bytes, type) =>
{
if (type == HTTP.Binary) { ... }
else if (type == HTTP.Text) { ... }
else { /* NOTE: it's imposible */ }
// server-side websocket received event
});
}); server.On.Open(() =>
{
// http server opened
});
server.On.Close(() =>
{
// http server closed
});
server.On.Error((exception) =>
{
// http server open error
});
server.On.Modify((httpListener) =>
{
// HttpListener instance, called before try open connection.
});
// Open http server connection
server.To.Open(new Uri("http://127.0.0.1:8080/"));
// Close http server connection
server.To.Close(); // open websocket client connection
server.To.Open(new Uri("ws://127.0.0.1:8080/echo"));
// close websocket client connection
server.To.Close();
// broadcast raw data for all connected websocket socket
// text message
server.To.WebsocketDataBroadcast("my message", HTTP.Text);
// binnary message
server.To.WebsocketDataBroadcast(NE.GetBytes("my buffer"), HTTP.Binary);
// broadcast event (netly event) for all connected websocket socket
// text message
server.To.WebsocketEventBroadcast("event name", "my message", HTTP.Text);
// binnary message
server.To.WebsocketEventBroadcast("event name", NE.GetBytes("my buffer"), HTTP.Binary); |
Byter |
For more information and details see Byter's official information
📄 Primitiveusing Byter;
Primitive can serialize/deserialize complex data, e.g. (T[], List, Class, Struct, Enum).
📄 Extensionusing Byter;
|
Integration and interaction example codes
Standard |
📄 Consoleusing System;
using Netly;
public class Program
{
private static void Main(string[] args)
{
UDP.Client client = new UDP.Client();
client.On.Open(() =>
{
Console.WriteLine(<some-text-here>);
};
client.On.Close(() =>
{
Console.WriteLine(<some-text-here>);
};
client.On.Error((exception) =>
{
Console.WriteLine(<some-text-here>);
};
while(true)
{
if(!client.IsOpened)
{
client.To.Open(new Host("1.1.1.1", 1111));
}
else
{
Console.WriteLine("Message: ");
string message = Console.ReadLine();
client.To.Data(message ?? "No message.", NE.Encoding.UTF8);
}
}
}
} |
---|---|
Flax Engine |
📄 Scriptusing System;
using FlaxEngine;
using Netly;
public class Example : Script
{
public string message;
internal UDP.Client client;
public override void Awake()
{
client = new UDP.Client();
client.On.Open(() =>
{
Debug.Log(<some-text-here>);
};
client.On.Close(() =>
{
Debug.Log(<some-text-here>);
};
client.On.Error((exception) =>
{
Debug.Log(<some-text-here>);
};
}
public override void Start()
{
client.To.Open(new Host("1.1.1.1", 1111));
}
public override void Update()
{
if(!client.IsOpened)
{
client.To.Open(new Host("1.1.1.1", 1111));
}
else
{
if (Input.GetKeyDown(KeyCode.Space))
{
client.To.Data(message ?? "No message.", NE.Encoding.UTF8);
}
}
}
} |
Unity Engine |
📄 MonoBehaviourusing System;
using FlaxEngine;
using Netly;
public class Example : MonoBehaviour
{
public string message;
internal UDP.Client client;
private void Awake()
{
client = new UDP.Client();
client.On.Open(() =>
{
Debug.Log(<some-text-here>);
};
client.On.Close(() =>
{
Debug.Log(<some-text-here>);
};
client.On.Error((exception) =>
{
Debug.Log(<some-text-here>);
};
}
private void Start()
{
client.To.Open(new Host("1.1.1.1", 1111));
}
private void Update()
{
if(!client.IsOpened)
{
client.To.Open(new Host("1.1.1.1", 1111));
}
else
{
if (Input.GetKeyDown(KeyCode.Space))
{
client.To.Data(message ?? "No message.", NE.Encoding.UTF8);
}
}
}
} |
WARNING: |
Initialize event handlers once, not in loops. Set up handlers with `..On.` methods in initialization methods like `Awake()` or `Start()`. Avoid repeatedly setting these up in update loops to maintain performance.
Handle protocol actions wisely. Use `..To.` methods, such as `..To.Open()`, `..To.Data()`, and `..To.Close()`, with careful management. Ensure you only open a connection when it's not already open and send data only when the connection is confirmed as active. Avoid calling these methods in tight loops. // OK 100% Recommended
private void Start()
{
var client = ...;
client.On.Open(() => ...); // e.g generic handler
client.On.Open(() => ...); // e.g only to send "Hi"
client.On.Event((name, bytes, ?) => ...); // e.g generic event handler
client.On.Event((name, bytes, ?) => ...); // e.g only to handle A event
client.On.Event((name, bytes, ?) => ...); // e.g only to handle B event
client.To.Open(...);
} public void Update()
{
client.To.Open(...); // [OK? - May Not In Loop?]
client.To.Data(...); // [OK? - May Not In Loop?]
client.To.Event(...); // [OK? - May Not In Loop?]
client.To.Close(...); // [OK? - May Not In Loop?]
ws.On.Open(() => ...); // [BAD - Never In Loop]
ws.On.Close(() => ... ); // [BAD - Never In Loop]
ws.On.Data((bytes) => ... ); // [BAD - Never In Loop]
ws.On.Error((exception) => ... ); // [BAD - Never In Loop]
ws.On.Event((name, bytes) => ... ); // [BAD - Never In Loop]
} |