Netly version 4 is now available! Experience the new way of interacting with Netly. See more
| ⭐ Your star on Netly brightens our journey and makes a real impact! |
powered by ALEC1O
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 | KeZero sponsor Netly with hosting infrastructure and website domain. Netly receives JetBrains' IDE for free, Thanks to their generous sponsorship. |
| 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 | Latest |
| 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 4.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 | |
| 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. BUT, LOCAL MIDDLEWARE HAVE PRIORITY THAN GLOBAL MIDDLEWARE */ // 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(); } });// Register parse middleware server.Middleware.Add((request, response, next) => { if (request.Enctype == HTTP.Enctype.Json) { request.Body.RegisterParse(true, (Type type) => { // e.g. using dotnet >= 6 @System.Text.Json return JsonSerializer.Deserialize(request.Body.Text, type); }); } ... next(); }); // Usage of body parser. server.Map.Post("/register", (request, response) => { var data = request.Body.Parse<MyLoginInput>(); ... }); |
| 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 client.To.Open(new Uri("ws://127.0.0.1:8080/echo")); // close websocket client connection client.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] } |
KeZero sponsor Netly with hosting infrastructure and website domain.
Netly receives JetBrains' IDE for free, Thanks to their generous sponsorship. 