statianzo / Fleck

C# Websocket Implementation
MIT License
2.28k stars 583 forks source link

ASPNET Core MVC creating a websocket server #241

Closed harrystuart closed 6 years ago

harrystuart commented 6 years ago

I am trying to create a websocket server that communicates with an external API (the client). The API (Nexmo) requires the URI of the websocket and handles the websocket connection request. This is my code:

public class SocketController : Controller { public IActionResult Index() { var server = new WebSocketServer("ws://35.197.188.167:80/socket"); server.RestartAfterListenError = true; server.ListenerSocket.NoDelay = true; server.Start(socket => { socket.OnOpen = /*async*/ () => { }; socket.OnClose = () => { }; socket.OnMessage = message => { }; socket.OnBinary = binary => { }; }); return StatusCode(200); } }

I tell the API to make a websocket connection request to ws://ipaddress:80/socket (/socket is the MVC action controller that contains this code). This code is not working... Should I make an HTTP request to this MVC action (which would create the WebSocketServer) before I tell the API to try and connect to it? Any idea what could be wrong with the code?

Thanks

AdrianBathurst commented 6 years ago

See the "server" portion of this post for a simple server example... https://github.com/statianzo/Fleck/issues/215#issuecomment-367063732

Don't use a Controller to start up the socket server, better to add it to your "startup" method of the application so the socket server starts running when the application is first launched.

Also, don't use port 80, this will be in use by your webserver, so it will clash and won't work. Add your own free port, like 2001 in the example above. Your client socket will therefore connect using "ws://your-server-ip:2001/"

harrystuart commented 6 years ago

Hi Adrian,

Thanks for the reply. If I were to start the socket server when the application is first launched, how would I distinguish between different simultaneous requests to the server? The external API will make a new/unique websocket request approximately 100 times per minute. The server needs to be able to communicate respectively with each connection. The application will be hosted on a virtual machine 24/7 so I thought it would be best to open a new unique websocket each time the API makes a websocket request. If I open the socket when the application is launched, does that mean it will be open 24/7 also? Again, how do I differentiate between connections?

How do I decide which port to use?

Thanks, Harry

AdrianBathurst commented 6 years ago

The way you were trying to set it up, you were trying to launch a new instance of the websocket server for every request. You can only have one socket server, and one or more clients connect to it. So, add your websocket server logic to your startup method in your app. Whilst your application is alive, the socket server will continue to run in the background listening for client socket connections. Client connections will remain connected if the client maintains the connection to the server.

The port to use is ultimately determined by what free ports you have and are allowed to use on your IT infrastructure. To start with you could try using port 8080 or 8081.

Referencing the code sample below (which will work in a Console app)...

When a client makes a connection request (e.g. using var websocket = new WebSocket("ws://your-server-ip:8080/");), the line socket.OnOpen = () => will be triggered. The socket object is your unique socket connection to the client. In this sample, we maintain a collection of all client connections in the dictionary called allClientConnections.

If the client sends a message, socket.OnMessage = message => will be triggered, and the message parameter will contain the message. Again, the socket object is your unique socket connection to the client.

You can send a message to all connected clients by looping over your collection of client connections like this...

foreach (var clientConnection in allClientConnections)
{
    clientConnection.Value.Send("your message...");
}

Server Sample

using System;
using System.Collections.Concurrent;
using Fleck;

namespace Discovery
{
    class Program
    {
        private static ConcurrentDictionary<Guid, IWebSocketConnection> allClientConnections = new ConcurrentDictionary<Guid, IWebSocketConnection>();

        [STAThread]
        static void Main(string[] args)
        {
            Console.WriteLine("Socket Server");
            Console.WriteLine("====================");

            WebSocketServer server = new WebSocketServer("ws://0.0.0.0:8080");

            server.Start(socket =>
            {
                socket.OnOpen = () =>
                {
                    Console.WriteLine($"{socket.ConnectionInfo.Id} triggered OnOpen()");

                    // add the client connection to our collection of client connections
                    if (allClientConnections.TryAdd(socket.ConnectionInfo.Id, socket))
                    {
                        Console.WriteLine($"client {socket.ConnectionInfo.Id} added, we now have {allClientConnections.Count} client connections");

                        // lets say hello to client
                        socket.Send($"Hello client!");
                    }
                    else
                        Console.WriteLine($"{socket.ConnectionInfo.Id} add failed");
                };

                socket.OnMessage = message =>
                {
                    // a message sent from the client
                    Console.WriteLine($"client {socket.ConnectionInfo.Id} sent message: {message}");

                    // lets send a message back to the client
                    socket.Send("Thanks client, got your message!");
                };

                socket.OnClose = () =>
                {
                    Console.WriteLine($"client {socket.ConnectionInfo.Id} triggered OnClose()");

                    // remove the client connection from our collection of client connections
                    if (allClientConnections.TryRemove(socket.ConnectionInfo.Id, out IWebSocketConnection removedSocket))
                    {
                        Console.WriteLine($"client {removedSocket.ConnectionInfo.Id} removed, we now have {allClientConnections.Count} client connections");
                    }
                    else
                        Console.WriteLine($"{socket.ConnectionInfo.Id} remove failed");
                };

                socket.OnError = exception =>
                {
                    Console.WriteLine($"client {socket.ConnectionInfo.Id} triggered OnError(), error: {exception.Message}");
                };
            });

            // listen for text typed in the console app
            var input = Console.ReadLine();
            while (input != "exit")
            {
                // lets send the text typed into the console app to all client connections
                foreach (var clientConnection in allClientConnections)
                {
                    clientConnection.Value.Send(input);
                }

                input = Console.ReadLine();
            }
        }
    }
}
harrystuart commented 6 years ago

Thank you Adrian,

That clears things up nicely and with your advice and code, I have been able to create a websocket server that echoes the binary data it receives from the client, sending it back to the client.

Greatly Appreciated, Harry