Creating a subscription message and ping/pong from a custom protocol

33 views
Skip to first unread message

Niklas

unread,
Jul 1, 2015, 6:32:35 AM7/1/15
to xsocke...@googlegroups.com
Hi,

We are writing a custom protocol for Android devices, and I have a few questions regarding subscription.
  1. Let's say my client wants to subscribe to a specified topic. I get to onIncomingFrame, get the topic from the payload, and then don't know what to do with it. So, should I still use a Message instance for subscription messages? If so, what should go into the first parameter of Message(string obj, string topic, controller, true);
  2. How should we handle ping/pong messages? Is there any way we can specify the byte signature of these messages ourselves so it doesn't break our protocol? What does Ping(byte[] data) and Pong(byte[] data) do in the XSockesProtocol base class?
Thanks in advance
/Niklas

Ulf Björklund

unread,
Jul 1, 2015, 8:02:58 AM7/1/15
to xsocke...@googlegroups.com
Hi Niklas

So.. I wrote a simple custom protocol to show you the concept... I built it on v5, but it should be pretty much the same on v4. If not let me know and I will help you.
If you are in development I do recommend you to use v5 over v4!

I assume that you are familiar with the main concepts of XSockets, so I will not go into details...

Sample Controller
/// <summary>
/// Just for showing pub/sub over the custom protocol...
/// Your controller will probably have custom methods with strongly typed parameters.
/// </summary>
[XSocketMetadata("sample")]
public class SampleController : XSocketController
{
    public override void OnMessage(IMessage message)
    {
        this.PublishToAll(message);
    }
}


Sample Protocol
public class SampleProtocol : XSocketProtocol
{
    public SampleProtocol()
    {
        this.ProtocolProxy = new SampleProtocolProxy();
    }
    /// <summary>
    /// You can return false here since we implement a custom heartbeat for this sample...
    /// </summary>
    /// <returns></returns>
    public override bool CanDoHeartbeat()
    {
        return false;
    }

    /// <summary>
    /// The message passed back on connection...        
    /// </summary>
    public override string HostResponse
    {
        get { return "Welcome to SampleProtocol"; }
    }

    /// <summary>
    /// Just adding crlf to get a new row since this protocol is for Putty or similar
    /// </summary>
    /// <returns></returns>
    public override byte[] GetHostResponse()
    {
        return Encoding.UTF8.GetBytes(string.Format("{0}\r\n", HostResponse));
    }

    public override bool Match(IList<byte> handshake)
    {
        //Check if the handshake matches the expected protocol... Do something more clever then this ;)
        return Encoding.UTF8.GetString(handshake.ToArray()).StartsWith("SampleProtocol");
    }

    /// <summary>
    /// Handle logic for reading frames... In this case I only look for \r\n... you probably have a more complex scenario
    /// </summary>
    /// <param name="segment"></param>
    /// <param name="readState"></param>
    /// <param name="processFrame"></param>
    public override void ReceiveData(ArraySegment<byte> segment, IReadState readState, Action<FrameType, IEnumerable<byte>> processFrame)
    {
        lock (_locker)
        {
            for (int i = 0; i < segment.Count; i++)
            {
                readState.Data.Add(segment.Array[i]);
                //if the frame is completed we will find \r\n at the end
                if (readState.Data.Count >= 2 && readState.Data[readState.Data.Count - 1] == 10 &&
                    readState.Data[readState.Data.Count - 2] == 13)
                {
                    processFrame(FrameType.Text, readState.Data.Take(readState.Data.Count - 2));
                    readState.Clear();
                }
            }
        }
    }

    /// <summary>
    /// Convert the incoming frame to a IMessage...        
    /// </summary>
    /// <param name="payload"></param>
    /// <param name="messageType"></param>
    /// <returns></returns>
    public override IMessage OnIncomingFrame(IEnumerable<byte> payload, MessageType messageType)
    {
        return this.ProtocolProxy.In(payload, messageType);
    }

    /// <summary>
    /// Convert the outgoing IMessage to what ever your custom protocol and the client expects...
    /// </summary>
    /// <param name="message"></param>
    /// <returns></returns>
    public override byte[] OnOutgoingFrame(IMessage message)
    {
        return this.ProtocolProxy.Out(message);
    }

    public override IXSocketProtocol NewInstance()
    {
        return new SampleProtocol();
    }
}



Sample ProtocolProxy
public class SampleProtocolProxy : IProtocolProxy
{
    private IXSocketJsonSerializer JsonSerializer { get; set; }

    public SampleProtocolProxy()
    {
        JsonSerializer = Composable.GetExport<IXSocketJsonSerializer>();
    }

    /// <summary>
    /// I expect the stupid sample message to look like "controller|topic|data"
    /// You do not have to have controller and topic in here.. you might wanna send messages to a predefined controller for this protocol...
    /// I have no clue ;)
    /// </summary>
    /// <param name="payload"></param>
    /// <param name="messageType"></param>
    /// <returns></returns>
    public IMessage In(IEnumerable<byte> payload, MessageType messageType)
    {
        var data = Encoding.UTF8.GetString(payload.ToArray());
        if (data.Length == 0) return null;
        var d = data.Split('|');
        switch (d[1])
        {
            //Special case for subscribe...
            case "subscribe":
                return new Message(new XSubscription { Topic = d[2] }, Constants.Events.PubSub.Subscribe, d[0], JsonSerializer);
            //Special case for unsubscribe...
            case "unsubscribe":
                return new Message(new XSubscription { Topic = d[2] }, Constants.Events.PubSub.Unsubscribe, d[0], JsonSerializer);
            default:
                return new Message(d[2], d[1], d[0], JsonSerializer);

        }
    }

    /// <summary>
    /// Just convert back to the format "controller|topic|data"
    /// </summary>
    /// <param name="message"></param>
    /// <returns></returns>
    public byte[] Out(IMessage message)
    {
        return Encoding.UTF8.GetBytes(string.Format("{0}|{1}|{2}\r\n", message.Controller, message.Topic, message.Data));
    }
}


Then just open up two instances of Putty... and connect your server (probably localhost and port 4502)
Then you can type "SampleProtocol" to connect....
When connected you can use pub/sub by typing

sample|subscribe|foo

in one client...

The from the other you can publish a message on the topic foo by typing

sample|foo|hello over my custom protocol

I did a quick test... See image below.





Note: v4 might not have the ProtocolProxy feature, if so... Just move the code in the protocol proxy to the protocol where the proxy is called... 

Regards
Uffe

Ulf Björklund

unread,
Jul 1, 2015, 8:04:52 AM7/1/15
to xsocke...@googlegroups.com
About ping/pong...

I would add a condition in the "ProtocolProxy" and if a ping comes in just send a pong back... You can do this in a custom way that you see fit.


Niklas

unread,
Jul 1, 2015, 10:16:08 AM7/1/15
to xsocke...@googlegroups.com
Thanks for quick reply!
Is the ping handled like any other IMessage, passing through onOutgoingFrame, with the topic set to ping?
If so, I could easily translate it to whatever bytes I need to not break my protocol and everything will be fine.
Reply all
Reply to author
Forward
0 new messages