Search code examples
c#multithreadingasynchronousevent-handlingfreeswitch

Freeswitch Event Socket Library


I'm trying to use the Event Socket Library (ESL) (archive) from FreeSWITCH but I guess this question is more about the technique and "how to" than anything else.

The code below is what my question is about and can be found here.

static void OutboundModeSync(Object stateInfo)
{
    //...
    while (true)
    {
        Socket sckClient = tcpListener.AcceptSocket();

        //Initializes a new instance of ESLconnection, and connects to the host $host on the port $port, and supplies $password to freeswitch
        ESLconnection eslConnection = new ESLconnection(sckClient.Handle.ToInt32());

        Console.WriteLine("Execute(\"answer\")");
        eslConnection.Execute("answer", String.Empty, String.Empty);
        Console.WriteLine("Execute(\"playback\")");
        eslConnection.Execute("playback", "music/8000/suite-espanola-op-47-leyenda.wav", String.Empty);
        Console.WriteLine("Execute(\"hangup\")");
        eslConnection.Execute("hangup", String.Empty, String.Empty);
    }
    //...
}

static void OutboundModeAsync(Object stateInfo)
{
    //...
    while (true)
    {
        tcpListener.BeginAcceptSocket((asyncCallback) =>
        {
            TcpListener tcpListened = (TcpListener)asyncCallback.AsyncState;

            Socket sckClient = tcpListened.EndAcceptSocket(asyncCallback);

            //Initializes a new instance of ESLconnection, and connects to the host $host on the port $port, and supplies $password to freeswitch
            ESLconnection eslConnection = new ESLconnection(sckClient.Handle.ToInt32());

            ESLevent eslEvent = eslConnection.GetInfo();
            //...
            while (eslConnection.Connected() == ESL_SUCCESS)
            {
                eslEvent = eslConnection.RecvEvent();
                Console.WriteLine(eslEvent.Serialize(String.Empty));
            }

            sckClient.Close();
        }, tcpListener);

        Thread.Sleep(50);
    }
}

(I'm using FreeSWITCH ESL in outbound mode). As far as I understand, both the "Sync" and "ASync" method are in an infinite loop waiting (blocking) for an event, reacting whenever some event is received from FreeSWITCH.

However, I would like to create a Windows service that tracks all calls FreeSWITCH throws at me. I want another application to call my service, and tell it to send "Foo" to connection "XYZ". So I'd keep track of a dictionary of connections where the key is the connection-(GU)ID. However, if that connection is waiting (blocking) for an event, I don't see how I could use it to "butt-in" and send my message, regardless if an event is received from FreeSWITCH to "free up" the blocking thread.

As an aside: by "butting-in" I mean that, if for some reason I, for example, decide I want to hangup a connection (some timer fired for example) I want to be able to send a hangup, regardless wether the connection is waiting for an event to be sent from FreeSWITCH (which could take a long time if nothing happens).

I could use recvEventTimed to create a "polling mechanism" but this approach has three problems:

  1. polling feels dirty
  2. whenever I want to "butt-in", I want my message sent ASAP, not 50ms (or 1ms) later
  3. recvEventTimed seems to ignore anything below ~500ms going so it'll poll like crazy. And 500ms is too much latency for me.

Aside from these (I could file a bug report), polling doesn't feel right.

I would like to "accept a connection", tell the ESL to wait (blocking) for an event and then raise an event so I could use an event-based approach. I expect to handle lots of connections so I would prefer low latencies, no (or little) polling and a clean approach to handle all these connections, while they last, giving me free "two way, unpolled, event-based" communication.

I have considered RX (Reactive extensions) and implementing my own "ESL" with .Net Async sockets (as opposed to the FreeSWITCH provided binary) and other approaches. But I can't seem to find a way to neatly implement my wishes/requirements without lots of boilerplate or/and nasty threading stuff etc. I'm pretty sure I should be able to use async/await but get lost on how to do that exactly. Any help would be very much appreciated.

Simple description:

* Connection comes in
* Accept connection + get GUID from connection
* Put connection in some collection for later reference
* Bind delegate to connection (`EventReceived` event)
* Use collection to get connection and send a message (preferrably async ("fire and forget"), but blocking COULD be acceptable as long as it only blocks that specific connection) to "butt-in" whenever I want to

The collection will be hosted in a Windows Service which I can contact to for I/O to my FreeSWITCH connections / list available connections etc.

So, I imagine something, like, in "pseudo code":

MyService {
    stopped = false;

    void OnStart {
        while (!stopped) {
          var someconn = esl.waitforconnection();
          var id = someconn.getCallId();
          someconn.EventReceived += MyDelegate(...);
          mydict.add(id, someconn);
        }
    }

    void OnStop {
      stopped = true;
    }

    void ButtIn(string callid, string msg) {
        mydict[callid].send(msg);
    }

    void MyDelegate(EventData evtdata) {
        //Do something with evtdata if desired
    }
}

How would I go about implementing this given the ESL provided by FreeSwitch is not event-based and has only a blocking "wait for event" or a polling "wait X ms for event"?

tl;dr: Don't want infinite-while-loops / polling but more of an event-based approach, preferrably using async/await, clean + simple code to use once the 'underlying "ESL connection"' is wrapped/abstracted in my own class(es). Need help/advice in best approach. No threading/sockets-guru.


Solution

  • NEventSocket and ReactiveExtensions might help you.

    private static async Task CallTracking()
        {
            var client = await InboundSocket.Connect("10.10.10.36", 8021, "ClueCon");
    
            string uuid = null;
    
            client.Events.Where(x => x.EventName == EventName.ChannelAnswer)
                  .Subscribe(x =>
                      {
                          uuid = x.UUID;
                          Console.WriteLine("Channel Answer Event {0}", x.UUID);
                      });
    
            client.Events.Where(x => x.EventName == EventName.ChannelHangup)
                  .Subscribe(x =>
                      {
                          uuid = null;
                          Console.WriteLine("Channel Hangup Event {0}", x.UUID);
                      });
    
            Console.WriteLine("Press enter to hang up the current call");
            Console.ReadLine();
    
            if (uuid != null)
            {
                Console.WriteLine("Hanging up {0}", uuid);
                await client.Play(uuid, "ivr/8000/ivr-call_rejected.wav");
                await client.Hangup(uuid, HangupCause.CallRejected);
            }
    
            client.Exit();
        }