Sunday, September 7, 2008

Socket Programming with Flex and Apache Mina (Part 2)

I was responding to narup's comment on my previous post, Socket Programming with Flex and Apache Mina, and my response got so long that I decided it was worth a follow-up post. The previous post focused mainly on the parts involved in socket layer communications and some common pitfalls. This post will focus more on decisions when defining a protocol to use in socket layer communications. narup asked two things:
  1. Whether I used binary or XML sockets, and
  2. Some general clues on the whole architecture
The most important thing is to define your protocol. If you choose a binary protocol, some important decisions to make are,
  • Whether to have a header and, if so, what it will contain
  • Whether a command will have a fixed or variable length and, if variable, how to know the length
  • Whether to have the header indicate the type of command and, if so, how many types you might implement, which will help determine how many bytes you allocate for the command.
I'm a strong believer that it's impossible to anticipate every case you'll ever need, so I like to leave myself room to expand without having to change the protocol. This inclines me toward variable length commands.

Command Length
Reading a fixed length command is easy. If your protocol is 8 bytes of data, you read 8 bytes. Variable length commands, by their nature, are of an unpredictable length. Ergo, you can't just read bytes blindly. Each command must somehow transmit with it an indicator for the size of the command. Some options are terminating with a null byte, a particular sequence of bytes (like an EOF character), or indicating the length before the command.

Terminating with a null byte can be problematic if for some reason you have a null byte in your command. I don't want to rule out that possibility, so I rule out that strategy. I also don't like reading ahead without any idea of how far I'm going, so I'm just not a big fan of terminating with a sequence. This can be especially tricky when your command doesn't show up in its unique entirety, but rather comes in pieces or along with another command. Knowing how far to read by prefacing the command with byte or four indicating the length, however, is clean and simple.

I really like variable length commands because its easily expandable if you decide to 10x the size of the data on which you're operating, but it doesn't require every message to be that 10x size, especially if your data is 1/100th of that when you start out.

When you have the length prefacing the command, you might as well call it a header. I like headers. They separate operations from data. I like sending a command with a header that indicates what to do and the length of the data upon which to act. Thus, the command body contains only the data. Simple. But now we're back to the length issue again... is our header variable or fixed? I actually like fixed header lengths, because otherwise you end up with the same prefacing or termination issues I just discussed for the body. Since a header is more defined (specific to the protocol than the data) and less likely to undergo volatile changes as your development progresses (unless you change your protocol, in which case this is all out the window), it's easy to define a fixed length header with ample room for growth.

For example, if in all the planning I can only define 10 command types, I'm still likely to allocate a whole byte to specifying the command despite the fact that I could fit my 10 known types into 4 bits with 6 as yet unknown types to spare. The fact of the matter is that 8 bits vs. 4 bits is a miniscule difference but if months later I needed 17 types, I certainly won't want to have to rewrite my encoders/decoders. Same goes for the number of bytes to allocate toward indicating the length of the command; maybe you only need 2 bytes for now, but why not use a full integer just in case. Those 2 additional bytes aren't going to break the bank.

The Right Socket for Your Protocol
This is a bit of a square-peg, round-hole point. It's not usually that definite, but once you've defined your protocol, the decision about how to carry out the transmissions will mainly depend on how much you value human-readability vs. markup overhead, as well as what you're most comfortable with.

I used binary, since the server would be relaying thousands of commands a second and I wanted to be as lightweight as possible. That said, reading and writing byte arrays to process your command is a recipe for obscure bugs you'll spend hours tracking down. By writing protocol encoders/decoders on both the mina and flex sides, and logic testing those independently of the system, I was able to handle the commands in an OO fashion within my programs, but still transmit the command in a super lightweight fashion.

In my case, since I had confirmed the correctness of the encoder/decoder logic, I really didn't care at all about the human readability that XML would have provided. Plus, the overhead of the markup was undesirable both in terms of network transfer and parsing time.

Additional Note
I should probably put a disclaimer that I also prefer sending and receiving JSON over XHR instead of XML, as well as properties files instead of XML config files, haha. I did use XML sockets when writing a Mina-based server to act solely as a policy file server, though, and I could think of cases where it makes sense.


narup said...

hey Ryan,

thanks for the post. it did help.

narup said...

hi Ryan, one question again :)
about the enocder/decoder in client side how are you reading the object from the socket, i can get the string as byte arrays, what if i want the complete oject, or i have to construct it myself on the client side based on received bytes.

Ryan said...

Hey narup,

I like to have a method in my Flex object representation of the protocol command called encode, such as encode():ByteArray, and a static method in the protocol command class called decode that will return an object representation of the particular command, such as decode(bytes:ByteArray):ProtocolCommand. In a language that supports overloading methods, like Java, I'd just overload the constructor to create one that accepts a Java ByteBuffer, but you can't do that in actionscript, so the static method works fine for decode.

So, when sending a command to the server, my client calls encode on the ProtocolCommand object I've created, which will return the appropriate ByteArray to write to the socket; and when receiving a command, my client calls the static decode method (using the ByteArray read from the socket as an argument), which will return a nice OO command representation.

So, yes, to a certain extent you have to interpret the bytes somewhere but, if done correctly, you'll only have to do it twice: once for outgoing, and once for incoming. I've seen some object representation standards which would let you skip ever dealing with the command as a byte[] and let you read/write objects, but I've never investigated them further, so I'm not sure on those.

Does that answer your questions?

拇指 said...

Hi Ryan,

Thank you so much for sharing!

I have a question:
I saw statements like "over 4000 concurrent connections ...". What does it really mean? What is the actual limitation on the max concurrent connections?

Thanks in advance


Ryan said...

Honestly, Donald, I'm not sure. I haven't reached it. The mina list/forums will probably know better.

Flex programming said...

Hey Ryan

thanx for a nice tip and some good piece of information

very useful blog - thanx again!


Thiago said...

Great post ryan. Can u give more details about the design of ProtocolCommand class and how you store the information?. Im developing a multplayer game flex/java using apache mina, and i define a class that have a header with OPCode, Lenght of body, and the Body (that is a ByteBuffer ), so in handler i identify the operation and extract the body information. Are you using the same aproach?

Ryan said...

Hi Thiago,

That's the exact approach I took and it worked very well for me. Best of luck on your game!

Thiago said...

Ryan, can you show a block of code that make the message exchange between flex and java using this approach?

aaa kitty20101122 said...

adidas nmd r1
nike huarache
yeezy shoes
michael kors purses
lebron james shoes
kyrie shoes
jordan shoes
air yeezy
longchamp bags

aaa kitty20101122 said...

michael kors outlet store
true religion jeans
air jordan
michael kors outlet
mlb jerseys
fitflops clearance
adidas superstar shoes
tory burch shoes
tory burch shoes
michael kors handbags

Love Kpop said...

It seems I'm on the right track, I hope I can do well. The result was something I did and was doing to implement it.

Pervez Joarder said...

Outstanding the very best content material to the current site! We have each undergo. This is often an information which for any good as well as searching. Anybody may click the cash advance spring-valley Fantastic information beneath carry on.