At 1/30/14 11:01 PM, Diki wrote:
I'll probably be posting updates on that as it progresses, but right now I'm still learning the inner-workings of the boost library, so I don't have anything to post (other than what currently basically amounts to an echo server).
Time for an update (and another wall of text).
I've been doing a lot of reading and tinkering, and I've gotten to the point that I'm comfortable working with Boost's Asio library. I used this example as a basic template for my code, and after tweaking the formatting to my preferences, and inserting the necessary functionality for communicating with Flash Player via sockets (e.g. responding to the cross-domain policy request) I now have this working code. It's still a proof of concept which is why I have it all in one file instead of separated into header and source files. The implementation is the same as the example found on Boost's website that I linked:
int main() {
try {
boost::asio::io_service io_service;
Server server(io_service);
io_service.run();
} catch (std::exception& e) {
std::cerr << e.what() << std::endl;
}
return 0;
}
And here's a breakdown of how it works:
After the server object is initialised it calls the start_accept() function, which allocates memory for a new client. When a client connection is called the handle_accept() function is called because of this line:
__acceptor.async_accept(pclient->socket(),
boost::bind(&Server::handle_accept, this,
pclient,
boost::asio::placeholders::error
)
);
Without going into too much detail, that is simply waiting for a connection on the socket object that is returned by pclient->socket(), and calls the callback, which is the second argument. The boost::bind function is kind of like a function pointer. The first argument to boost::bind() is the function that will be called, the second argument is a pointer to the object that holds the function, and the last two arguments are what will be passed to Server::handle_accept(). The Server::handle_accept() function simply initialises the client object, and tells it to start listening for incoming data, and then tells the server to once again listen for incoming connections.
The client listens for incoming data by this function being called:
inline void socket_read(size_t nbytes) {
boost::asio::async_read(__socket, boost::asio::buffer(__data,nbytes),
boost::bind(&Client::handle_read, shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred
)
);
}
It works pretty much the same way that async_accept() does. There are really only two notable differences. The first is that the second argument is boost::asio::buffer(__data,nbytes), which returns a buffer object that will store its data inside the __data variable (which is a char array) and expects the data to be of size nbytes. The second difference is the shared_from_this() function. It is used to return a pointer to the current object. The this pointer could technically be used there, but since the client object was allocated on the heap and stored in a shared_ptr object, that function must be used so that the Boost library knows that a reference to the pointer still exists, otherwise it could be popped off the heap. The shared_from_this() function exists because the Client class extends boost::enable_shared_from_this.
Once the socket receives data that is of size equal to or greater than nbytes it will call the Client::handle_read function that simply determines what type of data has been sent (i.e. either a header or a message). Because of the nature of Boost.Asio, and socket programming in C/C++ in general, you need to know how much data is going to be received before you receive it. I have it setup such that ever header message is 22 bytes and is in the format "NNP/1.0 LENGTH=125". It is unlikely that the header will ever actually be 22 bytes in size, so if it's not I simply pad the data until it is. The length determines the size of the message that will follow the header. NNP/1.0 stands for "NoName Protocol version 1.0", which is just a working title until I can think of something proper to put there.
So if I wanted to send "hello world" to the server I would first send:
NNP/1.0 LENGTH=11\0\0\0\0\0
And then immediately afterwards send:
hello world
Since the server received the header it knew that a message that is 11 bytes in size will follow. After receiving that message the server then listens for another piece of data that is 22 bytes in size, which will be another header.
One might wonder why I specifically chose to have a header size that is 22 bytes. The reason is that when Flash Player connects to a server over a socket that is hosted on a different domain than what Flash Player is running on it sends a policy request before anything else. The policy request is just the text "<policy-file-request/>", which is 22 bytes. After receiving a response to that request it disconnects the socket that that data was sent on, and opens a new one. Since I have no way of having Flash Player send a header before sending that policy request I opted to use 22 bytes as my header size for two reasons. Firstly 22 bytes is more than enough to store the necessary header information that I require. Secondly it's much simpler than trying to treat the policy request as some sort of special message. I could use the async_read_until() function, but it has the caveat of possibly receiving extra data after the specified delimiter, which would need to be parsed out. It made much more sense to just treat the policy request as a regular message and simply put an if statement in the handle_read() function:
void handle_read_message(size_t nbytes) {
if (memcmp(__data, POLICY_REQUEST, POLICYREQUEST_LEN) == 0) {
socket_write(POLICY, POLICY_LEN);
}
socket_read(PROTOHEAD_BUFFER_LEN);
}
I intend to write some code to optimise that function call such that it won't call memcmp() unless it's necessary to do so (i.e. only if it's possible for the data that has been received to be a policy request).
And that's pretty much it.
I haven't fully recreated the proof of concept that I wrote with my crappy Python server since the protocol I'm using for this C++ server is significantly different, but now that I have all the connecting/disconnecting and sending/receiving functionality coded in that won't really be that hard.
I hope someone actually reads this post because it took me like 40 minutes to write. ;(