X-Developer Cross-platform development in C++, Flight Simulation

1Dec/11Off

IPv6 … isn’t!

Yesterday we shipped the 1.3 update for the CRJ. It included the much-awaited remote CDU functionality, which is basically a web server running in the plugin, serving the CDU screen in HTML format via Ajax.
The webserver in the plugin is a very basic HTTP server built with boost.asio, an incredibly powerful cross-platform asynchronous network library.

Thinking progressive as I always do, I of course built the webserver with IPv6 support, and configured it to always start in dual-stack mode, that means it serves the CDU both via IPv4 and IPv6.
I thought I could relax on that basis, and not touch this code again until the IPv6 address space is full.

I was so wrong!
Reports from customers kept coming in, that they couldn't start the CRJ any more. A very helpful customer showed me a gdb stacktrace of what was wrong. Guess what: it couldn't open a listening socket on 0::0, the "any" IP address in IPv6.
No, this guy was not using Windows 98. He was using the latest and greatest Ubuntu Linux. SRSLY, WTF?! It is 2011 and Ubuntu 11.10 has IPv6 support disabled by default?
Next customer with this problem was using Windows XP. Still the most popular MS OS these days. Guess what, no IPv6 enabled by default!

After instructing those guys how to enable IPv6 on their machines, I coded a patch for the server, to fallback to IPv4 only when dual-stack is unavailable. This is released as the 1.3.1 hotfix now.

I wonder how this planet is going to make the transition to IPv6 any time soon, when about 25% of my customers just can't use it without browsing through cryptic knowledgebase articles...

22Oct/10Off

Blocking I/O and select()

Imported from X-Plane.org and filed under "plain C hacks".

Hi folks,

a recent entry in the Plugin Developers Forum here on the .org made me remember the last year when I was facing the very same problem.

So why does X-Plane lock up completely when you try to read data from the serial port or a network socket? Basically you call a function "get me data from there!" and the function dutifully looks at the specified place (e.g. the serial port) for fresh bytes. If there is nothing to fetch it just sits there and waits... as long as it takes until data arrives.
Now it is crucial to understand that all function calls you do in a plugin happen synchronously. Nothing can be done as long as you are waiting for the read function to return.
Moreover, not only your plugin has to wait, but since the plugin processing is called from X-Plane again in a synchronous way, the whole simulation stops while the socket function waits for data.
This behavior is what we call "blocking I/O".

So what do you do about it?
There are two ways to handle it: You can check if there is really data available BEFORE you call the read function. This way you can guarantee your plugin can immediately continue with its work, either with fresh data or without.
The other way is turn the blocking IO effectively into non-blocking IO by putting the blocking function in a separate thread the runs concurrent to X-Plane (and your plugin).

The first way is pretty straightforward:
You have to enclose your processing in another if-clause that checks if the IO is ready

1
2
3
4
5
if (ready_read(your_file_descriptor) == 1) 
{
    blocking_read(your_file_descriptor);
    decode_input_and_do_whatever();
}

if there is nothing to fetch, the blocking read function will never be called. So how do you implement "ready_read(file)" ?

There is a function defined by the POSIX standard that is called select(). Select takes a few arguments, among them the file_descriptor and a struct that represents a timeout. What select() does is  either return immediately, if there is data at the file_descriptor, or wait for a maximum timeout that you specify.

Select() is part of the Socket-System and available on all UNIX-platforms, that means on Linux and MacOS. But you can also use it under Windos, because Microsoft in there rare moments of reason had the good idea to implement something compatible to UNIX sockets, and that is called winsock2.
So all you need for a working select() is the correct header files for your operating system, and on windos a library called "ws2_32.lib" that you have to link statically.

Here is how you code it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int ready_read(int descriptor)
{
    int res;
    fd_set sready;
    struct timeval nowait;
 
    FD_ZERO(&sready);
    FD_SET((unsigned int)descriptor,&sready);
    nowait.tv_sec = 0;    // specify how many seconds you would like to wait for timeout
    nowait.tv_usec = 0;   // how many microseconds? If both is zero, select will return immediately
 
    res = select(descriptor+1,&sready,NULL,NULL,&nowait);
    if( FD_ISSET(descriptor,&sready) )
        return 1;
    else
        return 0;
}

To compile this, you need the headers

1
2
3
4
5
#include <unistd.h>
#ifdef WIN32
#include <winsock2.h>
#else
#include <sys/socket.h>

And in your makefile, you specify that if you are on Windos, to define the WIN32 symbol and link against ws2_32

So that was the easy way to do it.

The more sophisticated way is to "outsource" the blocking operation to another thread and define a callback that is called asynchronously whenever data arrives.
If you are familiar with the "reactor pattern" and know how to utilize pthreads that shouldn't be too much of a challenge. But wait! You cannot always write your data to X-Plane, it is crucial that you obey to the regulations for plugins - because you may well crash X-Plane when you write to a dataref when the sim is not in a consistent state. So it is absolutely necessary to read the technote on multithreading in plugins.

Happy coding to all plugin-devs out there and always remember to reset your wild pointers!
Phil