Why I Started Using This Tool

Every UDP post before this one sent the same thing: a plain string, "hello udp", over and over. That's fine for proving a socket works, but it's not how anything real communicates. Real systems send shapes — a command, plus whatever data goes with it — and they don't always live on the same machine, let alone the same operating system. I had two problems nagging me at once: first, I wanted a message with actual structure instead of a bag of bytes I had to parse by hand; second, I wanted to prove that structure survives a trip from my Windows PC, running completely different socket code, into the BeagleBone Black running Linux. Both turned out to be smaller problems than I expected.

What It Does

In plain English: instead of sending a raw string, you define a struct that describes exactly what a message contains, and you send the struct's raw memory directly over the socket. The receiver reads those same bytes back into an identically-shaped struct, and it all just lines up — no parsing, no splitting on delimiters.

typedef struct command_msg {
    char command[32];
    char data[32];
} command_msg_t;
  • Fixed-size arrays, never pointers — why it matters — a pointer is a memory address that only means something inside your own process. Send one over a socket and the receiving machine gets a meaningless number. Every field has to be self-contained bytes, which is exactly what char[32] gives you.
  • sizeof(command_msg_t) as the exact wire size — how you use it — because both fields are fixed, the struct is always the same number of bytes (64, here), so that one constant is what you hand to both sendto() and recvfrom(). No guessing at buffer sizes.
  • A one-shot trigger instead of a loop — time saved chasing "why didn't it start" — the Windows side sends exactly once and exits; the BeagleBone blocks on recvfrom() until that packet shows up, then runs its own independent loop from there. The network's job ends after one packet.

The walkthrough

The periodic version — same struct, sent on a one-second heartbeat with sendto()/sleep() — looks like this on the sending side:

command_msg_t msg;
int counter = 0;

while (1) {
    memset(&msg, 0, sizeof(msg));
    strncpy(msg.command, "PING", sizeof(msg.command) - 1);
    snprintf(msg.data, sizeof(msg.data), "count=%d", counter++);

    sendto(sock_fd, &msg, sizeof(msg), 0,
           (struct sockaddr *)&dest_addr, sizeof(dest_addr));
    sleep(1);
}

That memset() before filling the struct isn't decoration — it clears out whatever was left in memory from the previous loop iteration, so nothing stale leaks into the padded tail of command or data. On the receiving end, the trick is just reading raw bytes straight into the struct type instead of a char buffer[]:

command_msg_t msg;
recvfrom(sock_fd, &msg, sizeof(msg), 0,
         (struct sockaddr *)&from_addr, &from_len);

The kernel doesn't know or care what a struct is — it just drops bytes into that memory address. As long as both sides agree on the layout, it comes back out as a valid struct.

Then I pushed it further: instead of Linux talking to Linux, I sent from an actual Windows PC using Winsock2. The headers and a few function names change — WSAStartup()/WSACleanup() to initialize and tear down the networking subsystem, SOCKET instead of a plain int, closesocket() instead of close() — but the socket logic itself, SO_BROADCAST, sockaddr_in, sendto(), is identical to the Linux code:

WSADATA wsa;
WSAStartup(MAKEWORD(2, 2), &wsa);
SOCKET sock = socket(AF_INET, SOCK_DGRAM, 0);

And this time, the Windows program sends exactly one packet and quits — no loop, no Sleep():

command_msg_t msg;
memset(&msg, 0, sizeof(msg));
strncpy(msg.command, "START", sizeof(msg.command) - 1);
strncpy(msg.data, DEFAULT_NAME, sizeof(msg.data) - 1);

sendto(sock, (char *)&msg, sizeof(msg), 0,
       (struct sockaddr *)&dest_addr, sizeof(dest_addr));

On the BeagleBone side, recvfrom() blocks — the program does nothing at all until that one packet lands. Once it does, the network's part is over, and a completely separate loop takes over, using whatever was in the struct:

ssize_t received = recvfrom(sock_fd, &msg, sizeof(msg), 0,
                             (struct sockaddr *)&from_addr, &from_len);

int counter = 0;
while (1) {
    printf("[%d] running with command=\"%s\" data=\"%s\"\n",
           counter++, msg.command, msg.data);
    sleep(1);
}

One packet, from a different CPU architecture and a different compiler entirely, and the board just keeps going on its own afterward.

My Honest Pros & Cons

✅ What I Love

  • Structs scale where strings don't — adding a third field to a message is one line in the typedef, not a new delimiter-parsing scheme on the receiving end.
  • It's genuinely cross-platform for free — because command_msg_t is nothing but char arrays, there's no padding and no endianness mismatch between a Windows x86-64 PC and the BBB's ARM Linux target. I didn't write a single line of conversion code and it just worked.
  • The trigger pattern matches how real remote control actually works — you don't keep a connection open forever; you send one command and let the device run independently from there. Watching the Windows program exit immediately while the board kept counting on its own made that click instantly.

❌ What Could Be Better

  • The all-char struct is a trap waiting to happen — the moment you add an int or float field to a message like this, the compiler can insert padding, and mixed architectures can disagree on byte order. None of that applies today, but it's the first thing that breaks if you extend this design carelessly.
  • UDP is still UDP — the entire "trigger" pattern hinges on that one packet actually arriving. There's no retry, no acknowledgment; if it's dropped, the BeagleBone just sits at recvfrom() forever with no indication anything went wrong.
  • I trimmed error checking on the Windows sender to keep the code on screen smallsocket(), setsockopt(), and sendto() can all fail, and a real program needs to check every one of those return values the same way the Linux side always has.

Pricing: Is It Worth It?

No dollar cost either direction — Winsock2 ships with every Windows install via Ws2_32.lib, and the POSIX sockets API is already part of the BeagleBone's Debian image. The only real cost is a little extra boilerplate on Windows: linking Ws2_32.lib and calling WSAStartup()/WSACleanup(), which Linux never needed. That's maybe five extra lines total.

My take: for four extra lines of Windows-specific setup, getting a real cross-platform, cross-architecture message working — with a design that also scales past plain strings — is a very good trade.

Final Verdict

If you've already got UDP send/receive working on a single Linux box, this is the natural next step in two directions at once: give your message actual shape with a struct, and prove that shape survives a trip from a completely different machine. Beginners should focus on two things — fixed-size arrays only, never pointers, in anything you send over a socket, and recvfrom() blocking is a feature, not a bug, when you want a program to wait for a trigger instead of polling. If you already understand structs and sockets separately, the only genuinely new material here is Winsock's four points of difference from POSIX — everything else is code you've already written.