NDS WiFi Programming with devkitARM — Part 3
Posted in NintendoDS, sockets on February 18th, 2010 by Chaos EngineerFinally, after half a year... part 3.
I don't want to go any further without laying out some terminology to use. This project will consist of two parts, an application running on a PC, which I will refer to as the "Interface Host", and an application running on a NintendoDS, which I will refer to as the "Interface Client".
The host application as you may know will be rendering a cube using SDL and OpenGL. It will have a socket listener that will allow the NintendoDS to connect and negotiate terms by which it will send input data to the PC. I wanted to make the project simple but at the same time built with a framework that can be easily extended to do even more interesting things like pipe audio from the DS to the PC or something of the like. In order to do this, we'll define a simple protocol.
The protocol is quite simple. The Host machine will have a TCP listener that will allow Clients to connect and register. Once registered, a Client opens it's own TCP listener that will allow the Host to send commands to it. These listeners are used for signaling, or sending commands back and forth. I will use the terms signal and command interchangeably. After establishing signal listeners on both ends, there is a channel by which communication between the applications is simple. They can use this channel to negotiate terms for a transfer of DS touchpad data, for example.
By having an array of agreed-upon values that correspond to specific signals, and expected data to follow those signals, the applications have the foundation of a protocol. There may be more to the protocol as well, such as an expected order of operations. Again with a focus on simplicity, let us outline a protocol:

Nds Interface Protocol
The idea here is that the machines first negotiate with one another, eventually establishing a method to transfer data from the client to the host. All negotiation occurs over TCP, but the actual data transfer will occur over UDP on an agreed-upon port. I won't go into details of TCP vs UDP. There are articles on the net written by people far more qualified than me if you are curious. The basic reasoning is that we need the negotiation to be reliable, but we can tolerate a missing data transfer. Although in theory a series of UDP packets sent over a network can arrive at the destination in any order, I have never seen this happen in practice. Perhaps because all my practice has been over a LAN... maybe things over a WAN would be different. Regardless... UDP works great in practice and provides the benefits of being simpler and carrying less overhead. UDP is generally used for realtime streaming applications where a lost transfer doesn't break the overall functionality of the system. This is exactly the situation for our input data.
The host machine opens a listener on a port the client knows to look for. The client connects to the host machine on this port, and sends the Register signal. The host responds on the same socket with the RegisterAck signal. This acknowledgement signal is followed by a 32-bit integer telling the client what port to open as it's own signal listener. The host then knows how to communicate with it's newly registered "node". After constructing it's own signal listener, the machines can have any number of conversations initiated by either end. Each conversation will typically occur over the same socket connection, and consist of a signal followed an acknowledgement signal w/ a data payload. A simple beowulf cluster could be implemented using this design. Typically deployed over a non-homogenous array of machines, a beowulf host might send a signal to query each node's capability and the node might respond with the time it required to crunch some numbers using it's potentially unique hardware specifications.
The first applications I wrote that implemented this signaling design used multiple threads. The main thread was the signal listener, and when it got incoming connections, it would accept them and then pass the connected socket to a newly spawned thread that would handle the signal from the client. At the time, I thought this was an elite implementation, and without a doubt the best way to accomplish things. I then got myself into situations where I wanted to construct a signal listener either in a project I didn't want to implement threads or on a platform that doesn't support them. Enter the select() system call.
Multiplexing in a single thread, now THAT is elite. Using select(), you can easily monitor a listening socket for new connections while still handling signals from new sockets and even sending data every frame. The innermost loop of our Interface Client application on the DS will do exactly this. It will be managing the signal listener, handling any new signals and sending touchpad coordinates over the established data socket.
Here is what this synchronous code would look like inside an application's main loop, here being part of the Interface Host's main loop:
while(!g_bDone) // Pseudo main loop { // Check listener for new signals. This is done in a time-sensitive manner //using select to return immediately if there is no data/connections present. tvTimeout.tv_sec = 0; tvTimeout.tv_usec = 0; FD_ZERO(&fdsRead); FD_SET(sktHost, &fdsRead); retval = select(FD_SETSIZE, &fdsRead, NULL, NULL, &tvTimeout); if (retval == -1) { cerr << __FILE__ << '@' << __LINE__ << " : select() error waiting on accept." << endl; } else if (retval) { cout << "Incoming connection..." << endl; if((sktClient = accept(sktHost, (struct sockaddr*)&adrInterfaceClient, (socklen_t*)&iClientSize)) < 0) { cerr << __FILE__ << '@' << __LINE__ << " : Failed to accept client connection." << endl; } else { strcpy(g_szConnectedClientName, inet_ntoa(adrInterfaceClient.sin_addr)); cout << "Client connected: " << g_szConnectedClientName << endl; InterfaceClientSignalHandler(sktClient); } } // Do whatever else here... render objects, process input, etc. }
The next article -- part 4 -- will detail the NintendoDS implementation using the dswifi library, and I'll probably write a 5th article just to wrap it all up.