{"id":8146,"date":"2020-04-30T18:12:10","date_gmt":"2020-04-30T18:12:10","guid":{"rendered":"http:\/\/putridparrot.com\/blog\/?p=8146"},"modified":"2020-06-16T09:41:08","modified_gmt":"2020-06-16T09:41:08","slug":"websockets-with-fleck","status":"publish","type":"post","link":"https:\/\/putridparrot.com\/blog\/websockets-with-fleck\/","title":{"rendered":"Websockets with Fleck"},"content":{"rendered":"<p>I&#8217;m going to create a .NET core console application to demonstrate using Fleck. <\/p>\n<p>So create yourself a project and add the Fleck nuget package or simply add the Fleck nuget package to an existing project.<\/p>\n<p><strong>Creating a websocket server<\/strong><\/p>\n<p>To begin with, we simply create a websocket server, supplying the &#8220;location&#8221;, i.e. <\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\nvar websocketServer = new WebSocketServer(&quot;ws:\/\/0.0.0.0:8181&quot;);\r\n<\/pre>\n<p><em>Don&#8217;t forget the using Fleck; line at the start of your code<\/em><\/p>\n<p>In this example we&#8217;re using 0.0.0.0 (the non-routable meta-address) along with the port 8181 and ofcourse we prefix this with the <em>ws<\/em> protocol.<\/p>\n<p><strong>Interacting with clients<\/strong><\/p>\n<p>Next up we&#8217;ll want to start the server and intercept various events\/messages. Fleck uses a callback function\/delegate style, so we simply supply our functions for each of the connection methods that we wish to intercept, for example<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\nwebsocketServer.Start(connection =&gt;\r\n{\r\n  connection.OnOpen = () =&gt; \r\n    Console.WriteLine(&quot;OnOpen&quot;);\r\n  connection.OnClose = () =&gt; \r\n    Console.WriteLine(&quot;OnClose&quot;);\r\n  connection.OnMessage = message =&gt; \r\n    Console.WriteLine($&quot;OnMessage {message}&quot;);\r\n  connection.OnBinary = bytes =&gt; \r\n    Console.WriteLine($&quot;OnBinary {Encoding.UTF8.GetString(bytes)}&quot;);\r\n  connection.OnError = exception =&gt; \r\n    Console.WriteLine($&quot;OnError {exception.Message}&quot;);\r\n  connection.OnPing = bytes =&gt; \r\n    Console.WriteLine(&quot;OnPing&quot;);\r\n  connection.OnPong = bytes =&gt; \r\n    Console.WriteLine(&quot;OnPong&quot;);\r\n});\r\n<\/pre>\n<p><em>Note: if we&#8217;re handling different state for different connections to the same url, it&#8217;s our responsibility to create our own form &#8220;session state&#8221;.<\/em><\/p>\n<p>In this example, we&#8217;ve listed all the OnXXX actions that we can intercept.<\/p>\n<p>Obviously <em>OnOpen<\/em> occurs when a new client connects to the server (on ws:\/\/0.0.0.0:8181) and <em>OnClose<\/em> occurs if the client closes the connection.<\/p>\n<p><em>OnMessage<\/em> is called when string messages are sent over the websocket, whereas <em>OnBinary<\/em> is, ofcourse, the binary equivalent (in the example above we&#8217;re assuming the bytes represent a string, obviously change this if you&#8217;re sending raw byte data).<\/p>\n<p><em>OnError<\/em> is called with an Exception for instances where exceptions occur (as you&#8217;ll have surmised).<\/p>\n<p><em>OnPing<\/em> is used when being pinged and like wise <em>OnPong<\/em> is used when receiving a pong &#8211; ping and pong are used as ways to, in essence, check if the client or server are still running. If supported, a server might send a ping to the connected clients then mark the clients as stopped (and hence dispose of any connections) if the server does not receive a pong within a specified timeout period. Obviously one of the biggest problems for any server that is maintaining some form of state is at what point they can assume the client is no longer around. Obviously if the client closes the connection the server can handle this, but what about if they just disconnect &#8211; this is where ping and pong come into play.<\/p>\n<p>Obviously we also need to be able to send data to the client, hence we use the connection&#8217;s <em>Send<\/em> method. For example let&#8217;s change the OnMessage delegate to send an &#8220;Echo&#8221; of the message back to the client<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\nconnection.OnMessage = message =&gt;\r\n{\r\n  Console.WriteLine($&quot;OnMessage {message}&quot;);\r\n  connection.Send($&quot;Echo: {message}&quot;);\r\n};\r\n<\/pre>\n<p><strong>Writing a client to test our server<\/strong><\/p>\n<p>Let&#8217;s now create a simple console app to test our server code. This will use the System.Net.WebSockets ClientWebSocket class. <\/p>\n<p>We will need to actually specify a network address for the client, so we&#8217;ll use the loopback 127.0.0.1.<\/p>\n<p>Below are the contents of our client console application&#8217;s Main method<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\nvar websocketClient = new ClientWebSocket();\r\nvar cancellationToken = new CancellationTokenSource();\r\n\r\nvar connection = websocketClient.ConnectAsync(\r\n  new Uri(&quot;ws:\/\/127.0.0.1:8181&quot;), \r\n  cancellationToken.Token);\r\n\r\nconnection.ContinueWith(async tsk =&gt;\r\n{\r\n  \/\/ sends a string\/text message causes OnMessage to be called\r\n  await websocketClient.SendAsync(\r\n    new ArraySegment&lt;byte&gt;(Encoding.UTF8.GetBytes(&quot;Hello World&quot;)),\r\n    WebSocketMessageType.Text,\r\n    true,\r\n    cancellationToken.Token);\r\n\r\n  \/\/ receives a string\/text from the server\r\n  var buffer = new byte&#x5B;128];\r\n  await websocketClient.ReceiveAsync(\r\n    new ArraySegment&lt;byte&gt;(buffer), cancellationToken.Token);\r\n  Console.WriteLine(Encoding.UTF8.GetString(buffer));\r\n\r\n  \/\/ sends a string\/text message causes OnBinary to be called\r\n  await websocketClient.SendAsync(\r\n    new ArraySegment&lt;byte&gt;(Encoding.UTF8.GetBytes(&quot;Hello World&quot;)),\r\n    WebSocketMessageType.Binary,\r\n    true,\r\n    cancellationToken.Token);\r\n  });\r\n\r\n  Console.WriteLine(&quot;Press &lt;enter&gt; to exit&quot;);\r\n  Console.Read();\r\n\r\n  \/\/ close the connection nicely\r\n  websocketClient.CloseAsync(\r\n     WebSocketCloseStatus.NormalClosure, \r\n     String.Empty, \r\n     cancellationToken.Token);\r\n\r\n  \/\/ this will cause OnError on the server if not closed first\r\n  cancellationToken.Cancel();\r\n<\/pre>\n<p>Hopefully it&#8217;s fairly self explanatory what&#8217;s going on &#8211; we create a websocket client and a cancellation token (as the methods all required one). We connect to the server and when a connection is established we send and receive data (strings and then binary). Eventually we close the connection.<\/p>\n<p><strong>What and ping and pong?<\/strong><\/p>\n<p>At this time I don&#8217;t have any examples to implement these. <\/p>\n<p>In the case of the ClientWebSocket code, if you leave the client and server running you will periodically see OnPing being called.<\/p>\n<p><strong>I almost forgot&#8230;<\/strong><\/p>\n<p>We can also interact with the connection&#8217;s ConnectionInfo property which gives us access to headers, cookies and whether path was specified, i.e. the client url ws:\/\/127.0.0.1:8181\/somepath will result in ConnectionInfo.Path having the value \/somepath.<\/p>\n<p>Here&#8217;s an example of the server changes for OnOpen<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\nconnection.OnOpen = () =&gt;\r\n{\r\n  Console.WriteLine(&quot;OnOpen&quot;);\r\n  Console.WriteLine(connection.ConnectionInfo.Path);\r\n};\r\n<\/pre>\n<p>From what I can tell, each connection is assigned a GUID (found in ConnectionInfo.Id), so when handling multiple different clients with different state requirements we should be able to use this Id.<\/p>\n<p><strong>References<\/strong><\/p>\n<p><a href=\"https:\/\/github.com\/statianzo\/Fleck\" rel=\"noopener noreferrer\" target=\"_blank\">Fleck<\/a><br \/>\n<a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/WebSockets_API\/Writing_WebSocket_servers\" rel=\"noopener noreferrer\" target=\"_blank\">Writing WebSocket servers<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>I&#8217;m going to create a .NET core console application to demonstrate using Fleck. So create yourself a project and add the Fleck nuget package or simply add the Fleck nuget package to an existing project. Creating a websocket server To begin with, we simply create a websocket server, supplying the &#8220;location&#8221;, i.e. var websocketServer = [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[3,267],"tags":[],"class_list":["post-8146","post","type-post","status-publish","format-standard","hentry","category-c","category-websocket"],"jetpack_sharing_enabled":true,"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/posts\/8146","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/comments?post=8146"}],"version-history":[{"count":5,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/posts\/8146\/revisions"}],"predecessor-version":[{"id":8266,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/posts\/8146\/revisions\/8266"}],"wp:attachment":[{"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/media?parent=8146"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/categories?post=8146"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/tags?post=8146"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}