Netly
v3.1.0
积极的开发发生在“dev”分支上。对于稳定版本,请参阅“主”分支。
Netly 版本 4 即将发布,帮助验证与 netly 交互的新方式。查看更多
您在 Netly 上的明星照亮了我们的旅程,并产生了真正的影响! |
第 4 版开发路线图
全面实施 | 字节 3 • TCP客户端• TCP服务器• UDP客户端• UDP服务器• HTTP客户端• HTTP服务器• HTTP WebSocket • RUDP客户端• RUDP服务器 |
---|---|
正在进行的工作 | 文档v4 #63(新文档网站) HTTP Body(Enctype 检测器和解析器)#67(Body 解析器作为中间件) |
待定功能 | 添加 Byter v4 *正在开发中 添加 RUDP 测试 添加 HTTP 测试 添加Websocket测试 |
获取有关这个名为 Netly 的项目的基本信息
概述 | Netly 是一个强大的 C# 套接字库,旨在简化网络通信。它为多种协议提供全面支持,包括 HTTP、TCP、SSL/TLS、UDP、Reliable UDP (RUDP) 和 WebSocket。这种多功能性使 Netly 成为开发各种应用程序的绝佳选择,从多人游戏和聊天系统到实时数据交换。 |
---|---|
网站 | 存储库: github.com/alec1o/netly 文档: netly.docs.kezero.com |
赞助 | |
支持者 | 为什么要为 Netly 做出贡献?
|
官方出版商
努盖特 | Unity资源商店 |
---|---|
在 Nuget 上安装 | 在资源商店安装 |
显着变化
v1.xx | v2.xx | v3.xx | v4.xx |
---|---|---|---|
遗产 | 遗产 | 稳定的 | 发展 |
TCP支持 | 支持消息帧的 TCP | 支持 TLS/SSL 的 TCP | HTTP 客户端和服务器支持 |
UDP支持 | TCP 和 UDP 性能提升 | UDP 有连接(超时响应) | 可靠的 UDP (RUDP) 客户端和服务器支持 |
新的消息帧协议和性能提升 | WebSocket 客户端和服务器支持 | ||
升级至字节2.0 | 升级至字节3.0 | ||
Docsify 作为文档框架 | Docusaurus 和 DocFxMarkdownGen 的文档改进 | ||
语法和内部改进 | |||
XML 注释改进 |
有关集成的技术说明
已测试平台列表 |
|
---|---|
依赖关系 | 拜特 |
建造 |
# 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/ |
特征 |
|
代码亮点
传输控制协议 | 客户 using 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 ) ; 服务器 using 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协议 | 客户 using 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 ) ; 服务器 using 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协议 | 客户 using 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 ( ) ; 服务器 using 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 | 客户 using 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 ) ; 服务器 using 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 | 客户 using 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 ) ; 服务器 using 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官方信息
原始 using Byter ;
警告Primitive 可以序列化/反序列化复杂数据,例如(T[]、List、Class、Struct、Enum)。
例子
扩大 using Byter ;
|
集成交互示例代码
标准 | 安慰 using 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 ) ;
}
}
}
} |
---|---|
亚麻发动机 | 脚本 using 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 ) ;
}
}
}
} |
统一引擎 | 单一行为 using 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 ) ;
}
}
}
} |
警告: | 初始化事件处理程序一次,而不是循环初始化。在初始化方法(如“Awake()”或“Start()”)中使用“..On.”方法设置处理程序。避免在更新循环中重复设置这些以保持性能。 明智地处理协议操作。使用“..To.”方法,例如“..To.Open()”、“..To.Data()”和“..To.Close()”,并仔细管理。确保仅在连接尚未打开时打开连接,并仅在确认连接处于活动状态时才发送数据。避免在紧密循环中调用这些方法。 // 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]
} |