
Networking Principles for DIY and Interactive Systems
Exploring networks and protocols for creative practice

Exploring networks and protocols for creative practice
In my experience as someone who mostly started off working with Digital Content Creation tools like 3dsMax, Substance Painter and game engines like Unreal, Unity. I wanted to branch out and learn more about networking. During the XR Jockey prototype I struggled to link communication between unity and ableton. Eventually I was able to demonstrate my vision after much trial and error. After much more hands on experience troubleshooting multiplayer games, web services, databases, and linux servers. I present a broad compilation that goes way beyond my troubles in the XR Jockey Project and discuss what has been useful in bridging the gaps between creative software development and the practical plumbing required to wrangle your own distributed network of machines. This knowledge has been useful for modifying Websites, REST APIs, Real-time Telemetry, OSC/MIDI Orchestration, Multiplayer Backends, Observability Dashboards, Camera Feeds, and much more. Interactive Systems can be anything you want if you do it yourself.
A Host is any device (e.g. a smart device, laptop, or sensor) connected to a network that can send or receive data and is identified by an IP address. It can take on different roles at any given time.
What Role is your device taking in a conversation?
Is it a Listener, waiting for incoming connections?
Is it an Initiator, reaching out to known endpoints?
Is it a Coordinator, helping other machines discover, route, and synchronize?
Most networked applications can be understood through two foundational models: Client–Server and Peer-to-Peer. Each makes different tradeoffs around control, latency, reliability, and complexity.
In a client-server model we have hosts that are a Server and hosts that are a Client.
Servers listen for incoming connections on known addresses and ports.
Clients initiate requests and wait for responses.
Client: Web browser (Chrome, Firefox, Safari)
Server: Web server (e.g. Nginx, Apache, Node.js)
Flow:
1. The browser (client) initiates an HTTP request to a server’s URL.
2. The server listens on a known port (80 / 443).
3. The server responds with HTML, CSS, JavaScript, and assets.
4. The client renders the page locally.Client: Frontend app, mobile app, or script
Server: API service + database
Flow:
1. The client sends an HTTP request (GET / POST / PUT / DELETE).
2. The server validates the request (auth, permissions, schema).
3. The server performs logic (read/write database, compute response).
4. The server returns structured data (usually JSON).Client: Player’s game instance (Unity / Unreal)
Server: Dedicated game server (authoritative)
Flow:
1. Clients send inputs (movement, actions).
2. The server simulates the authoritative world state.
3. The server resolves conflicts (collisions, scoring, physics).
4. The server broadcasts state updates to all clients.
5. Clients render the result locally.In a peer-to-peer model each device can act as both a client and a server. Both devices communicate directly instead of communicating through a middleman.
Communication happens directly between peers with minimal central coordination.
Client / Server: Every peer plays both roles
Supporting servers: Signalling, STUN / TURN (discovery + fallback)
Flow:
1. Peers discover each other via a signaling service.
2. Connection details are exchanged.
3. Media or data flows directly between peers.
4. Servers drop out once the connection is established.
Whether it's client-server or peer-to-peer the same basic plumbing moves your messages from one host to another. Understanding how IP addresses, ports, and transport protocols carry your signals should be explored.
Networks and protocols are just ways of moving data efficiently and providing the infrastructure and language required for others to do so as well. Whether your intentions are to support or provoke, machines don't care about what you say but how you say it. With just a few pieces of information and a few humble steps, you can demystify these systems and begin your journey into creative connectivity.
Introductory computer networking courses will often take a bottom-up approach by first discussing the mechanisms that move signals through space. Alternatively, the top-down approach begins where user interaction with applications actually occurs. Arguably the most impactful networking technology has been the TCP/IP model used for internet communication.
TCP/IP STACK
---------------------------
APPLICATION LAYER ← The story you want to tell
---------------------------
TRANSPORT LAYER ← The envelope keeping it safe
---------------------------
INTERNET LAYER ← The post office
---------------------------
NETWORK ACCESS LAYER ← The streets and bridges
---------------------------An Internet Protocol Version 4 (IPv4) Address can be any number between 0-4,294,967,295 roughly speaking (i.e 32 bits). We just choose to represent these numbers more conveniently for readability. So for IPv4 we group them into 4 octets separated by .
So the representations end up ranging from ~0.0.0.0-255.255.255.255 with some special cases.
The following IPv4 Addresses are reserved for special purposes and used for
The wildcard address 0.0.0.0 - “When setting up a web service on this machine/listen on all available network interfaces"
The localhost address 127.0.0.1 - "When communicating with another web service on this machine"
The broadcast address 255.255.255.255 - "When communicating with every machine on this network"
The following CIDR blocks of IPs are reserved for private networks and are not routable on the public internet. These address ranges are commonly used inside home, office, and data center networks.
10.0.0.0/8 = 10.0.0.0 - 10.255.255.255
192.168.0.0/16 = 192.168.0.0 - 192.168.255.255
172.16.0.0/12 = 172.16.0.0 - 172.31.255.255
Classless Inter-Domain Routing (CIDR) notation defines a network’s starting address, size, and range.
IPv4 Address block (CIDR block) Example
CIDR block 192.168.0.0/24
The 192.168.0.0 represents the network address
The /24 represents the subnet mask (prefix length) and translates to 11111111 11111111 11111111 00000000 or 255.255.255.0
The network occupies IPs ranging from [192.168.0.0 - 192.168.0.255]
The broadcast address is typically the max value, in this case 192.168.0.255
Devices connected to this network will be assigned an address between 192.168.0.1 - 192.168.0.254CIDR is a generalization of classful routing, which is no longer practiced.
Before CIDR, IPv4 networks were divided into 3 fixed-size classes.
Class A: Large, /8 with ~16 million Hosts
Class B: Medium /16 with ~65 thousand Hosts
Class C: Small /24 with ~254 Hosts
With the adoption of CIDR, networks could be logically divided into smaller sub-networks.
A Network can be segmented into several subnets to manage congestion, security, and organization. When 2 Machines are on separate subnets they cannot communicate directly and must go through the Default Gateway.
To understand why adjusting the prefix length (i.e subnet mask) changes the amount of hosts involves familiarity with the binary number system and bitwise math.
The Network Portion of 192.168.0.0/24 is 192.168.0.X while the Host portion is the final digit.
We have 24 bits reserved for the Network Address and 8 bits reserved for the Host Address.
Max of 8-bit binary number 11111111 = 1 + 2 + 4 + 8 + 16 + 32 + 64 + 128 = 255
We have 254 possible host addresses for the 192.168.0.0/24 subnet because .0 and .255 are reserved for the network address, and broadcast address.
We can split this Network into 2 Subnets, 192.168.0.0/25 and 192.168.0.128/25
We have 25 bits reserved for the Network Address and 7 bits reserved for the Host Address.
Max of 7-bit 1111111 = 1 + 2 + 4 + 8 + 16 + 32 + 64 = 127
We have 126 possible host addresses for the 192.168.0.0/25 subnet because .0 and .127 are reserved for the network address, and broadcast address.
We have 126 possible host addresses for the 192.168.0.128/25 subnet because .128 and .255 are reserved for the network address, and broadcast address.
| Subnet | CIDR Range | Usable IP Range | Broadcast |
| Subnet A | 192.168.0.0/25 | .1 to .126 | .127 |
| Subnet B | 192.168.0.128/25 | .129 to .254 | .255 || Subnet | CIDR Range | Usable IP Range | Broadcast |
| Subnet 1 | 192.168.0.0/26 | .1 to .62 | .63 |
| Subnet 2 | 192.168.0.64/26 | .65 to .126 | .127 |
| Subnet 3 | 192.168.0.128/26 | .129 to .190 | .191 |
| Subnet 4 | 192.168.0.192/26 | .193 to .254 | .255 || Subnet | CIDR Range | Usable IP Range | Broadcast |
| Subnet 1 | 192.168.0.0/27 | .1 to .30 | .31 |
| Subnet 2 | 192.168.0.32/27 | .33 to .62 | .63 |
| Subnet 3 | 192.168.0.64/27 | .65 to .94 | .95 |
| Subnet 4 | 192.168.0.96/27 | .97 to .126 | .127 |
| Subnet 5 | 192.168.0.128/27 | .129 to .158 | .159 |
| Subnet 6 | 192.168.0.160/27 | .161 to .190 | .191 |
| Subnet 7 | 192.168.0.192/27 | .193 to .222 | .223 |
| Subnet 8 | 192.168.0.224/27 | .225 to .254 | .255 |As time went on the ~4.3 billion possible addresses became scarce and another abstraction was needed to support many more devices than that by Network Address Translation (NAT). NAT is a mechanism that often resolves your public internet traffic to local devices.
Internet Service Providers (ISP) typically provide a 4-in-1 box that acts as a modem, router, switch, and wireless access point. Each of these can be separate devices and not all of them are required depending on whats being built.
The simplified roles of each are as follows:
Your ISP will also lease your 4-in-1 box with a public IPv4 address, but because IPv4 addresses are running out, they often use Carrier-Grade NAT (CGNAT), and thus creates a Double NAT scenario. This means your connection may actually share a public IP with other customers, with the ISP handling the CGNAT behind the scenes - separate from the NAT occurring on you're Local Area Network (LAN).
To the world outside your LAN, all traffic traveling out will share this public IPv4 address.
To the world inside your LAN, the router uses Network Address Translation (NAT) to forward incoming traffic to the correct device using its private IP address.
When connecting a new device to your network it will typically be assigned a Private IP from the CIDR Block 192.168.0.0/24
The router will lease a semi-permanent Private IPv4 Address by Dynamic Host Configuration Protocol (DHCP), "a network management protocol that automatically assigns IP addresses, subnet masks, default gateways, and DNS servers to devices, eliminating manual configuration".
When a device wants to communicate with another device, it includes its own IP address as the source so the receiver knows where to send a response. But what if the destination IP address is outside your LAN? Any traffic for unknown or non-local IP addresses is sent to the default gateway, which is usually your router’s IP address. The router maintains a routing table and continuously updates it, learning the best paths to forward your messages toward their destinations.
The IP Address of your default gateway for example might be 192.168.0.1 You can access and configure more settings by navigating to http://192.168.0.1 in you're web browser. The Provided ISP's 4-in-1 will be limited compared to using a dedicated router for this.
IP addresses are logical and flexible. They can change depending on the network you connect to and are used to route traffic across networks, helping data find its way from one network to another. At some point this logical address needs to resolve to a physical address. The MAC Address is a permanent and unique hardware identifier assigned by a manufacturer that is bound to a device.
The Address Resolution Protocol (ARP) acts as the bridge between these two systems by broadcasting a question on the local network asking, "Who has this specific IP address? Another device needs to communicate with it”.
Traffic can potentially flow from network to network until it reaches its destination device, but how does it know which app or service to go to?
A Port Number can be between 0 – 65535 and is also used when configuring communication between devices.
Well-Known Ports (0–1023) are reserved for standard services and protocols.
80 – HTTP: standard web traffic, used by browsers or web-based controllers
53 – DNS: translating names like example.com into IP addresses
Registered Ports (1024–49151) are assigned for user or vendor applications.
3306 – MySQL: database communication
1883 – MQTT: sending messages in real time between devices or apps
Ephemeral Ports (49152–65535) are assigned temporarily by the OS for outgoing connections.
Your OS may use port 49153 to request data from another device fulfilling request at their IP Address on port 80.Most apps and services are accessed by addressing a specific IP address, domain, port, and protocol.
URL schemes commonly resolve to default ports.
http://127.0.0.1 = http://127.0.0.1:80
ws://127.0.0.1 = ws://127.0.0.1:80
https://127.0.0.1 = https://127.0.0.1:443
wss://127.0.0.1 = wss://127.0.0.1:443
mqtt://127.0.0.1 = mqtt://127.0.0.1:1883
rtsp://127.0.0.1 = rtsp://127.0.0.1:554When the port numbers are not the default for that protocol, you must specify which one to use.
http://127.0.0.1:8080
rtsp://127.0.0.1:1554Each application or Service has different communication requirements. Maybe they need to prioritize speed and low latency or guarantee reliability and correctness. These tradeoffs are handled by transport-layer protocols like Transmission Control Protocol (TCP) and User Datagram Protocol (UDP).
A port number is always paired with a transport-layer protocol when setting up a service or communicating with a service.
192.168.0.10:443/TCP and 192.168.0.10:443/UDP are two different endpointsPort Forwarding is often mistaken as a mechanism of the Firewall but they serve different purposes.
When you're running apps or services behind NAT and must make these available to the outside world, then we must specify what internal machine should receive traffic on a specific port number. This way the router knows where to forward incoming requests.
Even for your ISPs provided router (4-in-1 Box) you can navigate to the Default Gateway IP (e.g http://192.168.0.1) and set this up in your Routers Admin Dashboard. You will also need to look into how to make exceptions inside the Operating Systems firewall of your device.
Your ISP may also block certain ports beyond your control.
Signaling, STUN, and TURN servers play coordinating roles in facilitating peer-to-peer (P2P) communication. They support popular web applications like anonymous real-time chat platforms (e.g. Monkey, Omegle), and live-streamed interactive apps (Pixel Streaming).
Signaling is responsible for the initial coordination between peers, exchanging connection metadata such as session descriptions and network candidates using a client–server protocol (e.g. HTTP or WebSockets). A signaling server often coordinates with STUN and TURN servers to help peers connect across NATs and firewalls.
STUN (Session Traversal Utilities for NAT) helps peers discover their public-facing IP address and port so they can attempt a direct P2P connection. Public STUN servers are freely available online (e.g stun.l.google.com:19302).
TURN (Traversal Using Relays around NAT) acts as a fallback relay server when direct peer-to-peer communication fails. TURN servers can be expensive to operate because all media traffic is relayed through the server, which increases bandwidth and resource usage.
CoTurn is an open-source solution that allows you to self-host your own STUN and TURN servers, giving you full control over peer-to-peer connection handling, authentication, and bandwidth usage.
Instead of memorizing numerical addresses of our favorite websites and services, the Domain Name Service (DNS) is used to resolve human-friendly names into their actual IP Addresses. Therefore the application can depend on a stable name while its physical servers or IP Address change.
You pay yearly to have domain registrars like Porkbun, Namecheap, or Cloudflare secure usage for you and must renew to keep ownership. Top-Level Domains (TLDs) are the last part of the domain like (.com, .ai, .games, .xyz). TLDs don’t change how anything works, but they absolutely change how your project is perceived and how much you pay per year.
While many .com domains are a minimum $20/year after the first year. By contrast, the set of 1.111B class domains are .xyz domains made up of 1.111 billion possible 6-digit, 7-digit, 8-digit, and 9-digit numeric combinations, between ‘000000.xyz’ through ‘999999999.xyz only cost $1/year.
However its cheap price has attracted malicious actors like botnets and spammers so other network firewalls may prefer not to deal with traffic originating from .xyz TLDs.
DNS setup for your domain requires logging into your domain registrar's dashboard.
DNS Records take time to propagate across the internet before your domain becomes fully accessible. This delay can range from a few minutes to a few hours. A website like https://www.whatsmydns.net/ will test DNS servers worldwide. There are thousands of DNS servers, and by default your system usually uses those assigned by your ISP, device, or web browser.
Common Public DNS Servers
Google = 8.8.8.8
Cloudflare = 1.1.1.1
Quad9 = 9.9.9.9A Reverse Proxy is a server (e.g Nginx Proxy Manager) that sits in front of your other servers so that traffic can pass through a single point of entry and have their requests forwarded to the appropriate destination. They are commonly used to enhance security by consolidating port access and provide another layer of obfuscation through encryption by providing a mechanism to easily enable Transport Layer Security (TLS) for HTTPS.
A Host taking the role of a server providing multiple services on numerous ports (e.g 3000/tcp, 8080/tcp, 8888/tcp) may require a client to request data from multiple endpoints (e.g PUBLIC_IP:3000, PUBLIC_IP:8080, and PUBLIC_IP:8888).
Instead the Host can run Nginx Proxy Manager (NPM) at PUBLIC_IP:443 and use a domain like 123456789.xyz with an DNS Address Record pointing 123456789.xyz -> PUBLIC_IP. I recommend NPM because it provides a simple WebUI to easily support HTTPS and Websockets.
Reverse proxies commonly route traffic using:
HTTP is an insecure protocol where data is transported in plain text.
HTTPS is HTTP Secure where data is transported with TLS Encryption Protection.
Although HTTPS adds extra complexity it becomes very necessary if your services interface with public connections. To enable HTTPS, a server presents a TLS certificate issued by a trusted Certificate Authority (CA). Let’s Encrypt is a free, automated CA commonly used to issue certificates for web services.
A single certificate can be issued for multiple subdomains using a wildcard (*).
The certificate for *.123456789.xyz covers
Browsers require HTTPS Certificates for applications trying to access device capabilities like geolocation, microphone, camera, WebXR, etc.
By default traffic is only encrypted using TLS outside the Reverse Proxy. As it forwards requests to the destination_ip:port inside the network its coordinating with, the services it communicates with are often still just running as HTTP (insecure, plain text).
While Global DNS relies on external servers to resolve domains to IP Addresses, we don't always need broader internet connectivity. mDNS is a protocol that is also commonly used without knowing it. This is how our smart-devices are able to detect each other and advertise services simply because they exist on the same LAN.
The Hostname is a human-readable label assigned to your device during setup and can be changed. When discovering devices on your network you will often identify them by their hostname (e.g YardCam, Office-PC). Recall by default, most LANs use DHCP to lease out semi-permanent IPs for your newly connected devices. Similarly to how domains work by providing a stable name to use, mDNS does this with zero configuration. While ARP asks "Who has this IP?", mDNS asks "Who has this Name?"
By appending .local to a hostname you can use mDNS to find the device.
You can use rtsp://user:[email protected]:554/cam/realmonitor?channel=1&subtype=1 to access your camera stream.
You can use http://Office-PC.local to access a web server.
When purchasing a domain for the first year you will also be given discounted server rates that can often be more costly and overpowered then what you actually need. You can also just rent a Virtual Private Server (VPS) which will provide you with a Public IP Address and an Operating System to accomplish your goals. Because Linux operating systems run efficiently as software, this often translates to lower hosting costs. Learning how to navigate and manage a system remotely through the terminal becomes a valuable skill when working this way.
You can also go down the route of self-hosting but comes with more risk and complications. Recall that the devices inside your LAN will often be behind 2 NATs, and your DNS Record requires your domain to have a Static IP Address. A website like https://whatismyipaddress.com/ will show your Public IP Address.
If it is the same as the Wide-Area-Network (WAN) IP displayed in your router gateway (http://192.168.0.1) then you are not behind a CGNAT. Recall that your ISP will lease you a semi-permanent IPv4 Address. They charge you more $$$ for a Permanent IPv4 Address. Instead, you can setup Dynamic DNS (DDNS) to automatically update your Domains DNS Record when it detects a new Public IPv4 Address has been issued. For example, if you paid $5 to own the domain name 123456789.xyz for 5 years then you can configure DDNS in your router to monitor your WAN IP and propagate changes to your A Record to all DNS servers worldwide. Now you have a stable name to communicate with your DIY Interactive System anywhere in the world.
If it's not the same as the WAN IP displayed in your router gateway (http://192.168.0.1) then you are behind a CGNAT and have to rely on tunneling or relay methods like a Virtual Private Network (VPN) using software like Tailscale, Wireguard, or OpenVPN. But if your ISP and router supports IPv6, your device may have a globally routable address, allowing it to be reached directly from the internet without relying on NAT or tunneling.
Internet Protocol Version 4 (IPv4) is still very common but the Next-Generation Internet Protocol known as IPv6 is increasingly being used.
IPv6 Addresses are simply a number between 0 and 340,282,366,920,938,463,463,374,607,431,768,211,455 (i.e 128 bits).
Again We just choose to represent these numbers more conveniently for readability by using 8 groups of 4 hexadecimal digits, separated by colons (:).
A few more shortcuts for condensing IPv6 Addresses are also used.
IPv6 Address block (CIDR block) Example
CIDR block 2001:db8:abcd:0012::/64
The 2001:db8:abcd:0012:: represents the network address
The /64 represents the prefix length, meaning the first 64 bits identify the network
The remaining 64 bits are used for device addresses
The network occupies IPs ranging from [2001:db8:abcd:0012:0000:0000:0000:0000 - 2001:db8:abcd:0012:ffff:ffff:ffff:ffff]IPv6 also provides Unique Local Addresses (ULA) similar to IPv4 Private Addresses (e.g 192.168.0.0/16)
The full ULA range is fc00::/7, but in practice only fd00::/8 is used. The IPv6 CIDR Block fd00::/8 provides IP Addresses from fd00:0000:0000:0000:0000:0000:0000:0000 to fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff which is more than you will probably ever need.
The following IPv6 Addresses are reserved for special purposes and used for
The wildcard address :: - “When setting up a web service on this machine”
The localhost address ::1 - "When communicating with another web service on this machine"
Another barrier that can interfere with communication is the Firewall. Firewalls exist on the Default Gateway or Router. They are by default configured to be very protective and block network traffic based on IP addresses, port numbers, and protocols. You definitely want this behavior but you may need to make exceptions based on your goals.
To access your router’s firewall, your ISP-provided 4-in-1 box may have limited functionality. You can navigate to the default gateway IP (e.g http://192.168.0.1) in a web browser to explore its web dashboard and see what options are available.
In addition to local firewalls, firewalls can also exist upstream, such as those managed by an ISP or a network administrator. These firewalls filter traffic before it even reaches your network.
Firewalls can also exist on the operating system.
Windows will by default block all traffic in and out
Windows Defender Firewall > Allow an app through Windows Firewall
Linux will by default for most distributions allow all traffic in and out unless configured otherwise.
Installing the Uncomplicated Firewall (ufw) package will quickly setup a firewall and allow you to Block/Allow traffic based on certain rules.
---------------------------
NETWORK ACCESS LAYER ← The streets and bridges
---------------------------Most internet speeds are now approaching 100 Mbps minimum for many users, but the actual speed you experience depends on your networking equipment. Devices like Network Interface Cards (NICs), routers, switches, modems, access points, and even Ethernet cables are each rated for a maximum speed
Your data transfer speed will always be limited to the slowest component in the path.
They will frequently fall into the following categories:
| Category | Max Data Rate | Max Distance
| Cat 5 | 100 Mbps | 100m |
| Cat 5e | 1 Gbps | 100m |
| Cat 6 | 1 Gbps (10 Gbps*) | 100m (55m*) |
| Cat 6a | 10 Gbps | 100m || Generation | Technical Name | Common Frequency | Expected Data Rate | Typical Range
| WiFi 4 | 802.11n | 2.4 GHz | 20–50 Mbps | ~45m |
| WiFi 5 | 802.11ac | 5 GHz | 200–500 Mbps | ~15m |
| WiFi 6 | 802.11ax | 2.4 / 5 GHz | 600–900 Mbps | ~15m–45m |
| WiFi 6E/7 | 802.11be | 6 GHz | 1–2 Gbps | ~9m |The TCP/IP Networking Model provides all the infrastructure needed to communicate from one host to another host. Everything underneath usually just works but it helps to know what to look for and where to look when it doesn't. We have to be able to touch, see, and listen to what’s actually happening. That begins by checking what’s reachable, what’s responding, and what’s moving. This often occurs on a logical plane rather than physically checking if a wire is disconnected. Your lens and hands are a set of network utilities that allow you to verify addresses, messages, and delivery.
Application Layer: Is my data formatted correctly?
Transport Layer: Is the right Port Number being used?
Security: Is the Network or OS Firewall blocking the Port Number or Protocol (e.g udp/tcp)
Internet Layer: Is the IP Address actually the one I need?
Network Access Layer: Is the Host on a different Subnet?I'll be using Linux Terminal Commands to demonstrate this for simplicity, but these tools can be found for your OS.
Navigate to https://www.speedtest.net/
Install and run iperf3 on machine as a server
iperf3 -sInstall and run iperf3 on another machine as a client
iperf3 -c SERVER_IPsend requests to web servers and receive responses, acting as a "command-line web browser" for data.
curl -X POST http://localhost:5000/webhook \
-H "Content-Type: application/json" \
-d '{"type":"intensity","value":0.8}'see your device’s assigned address, network assignment, and default gateway.
ip acheck if a host is reachable
ping simulationshock.com -c 4see the path your packets take
traceroute simulationshock.comcheck if a specific port is open
nc -vz simulationshock.com 443discover which devices are on your network
nmap -sn 192.168.1.0/24capture and inspect the actual traffic
tcpdump -i <network interface>check what ports are "listening" on your machine.
netstat -tulpnquery a DNS Record for a domain at a specific nameserver
dig @8.8.8.8 simulationshock.com AWhat you actually end up designing with are higher-level, more human-friendly protocols that shape and send messages on your behalf. Machines don't care what you say but how you say it. A common schema is agreed upon so that both sender and receiver can speak the same language. Serialization is the process of translating your data into a format that is ready to be transmitted. Deserialization occurs once the message reaches its destination and is translated back to its original form.
JSON is more common in modern web applications, APIs, and real-time communication systems.
Messages exchanged between clients and servers are frequently structured in the Javascript Object Notation (JSON) format - a simple text format for representing structured data.
Key points:
{ "nested": "value" }[1, 2, 3]{
"sensor_id": "living_room_01", // string
"data": { // object
"temperature": 22.5, // number
"humidity": 45, // number
"unit": "celsius", // string
"status": null // null
},
"active": true, // boolean
"tags": ["living", "climate", "sensor"], // array of strings
"alerts": [ // array of objects
{ "type": "temperature", "level": "high" },
{ "type": "humidity", "level": "low" }
]
}Protocol Buffers (Protobuf) is a binary serialization format used to exchange structured data efficiently between clients and servers. Data is defined in a .proto schema, which specifies field names, types, and their numeric tags.
Key points:
syntax = "proto3";
message SensorData {
float temperature = 1;
int32 humidity = 2;
string unit = 3;
google.protobuf.StringValue status = 4; // optional null equivalent
}
message Alert {
string type = 1;
string level = 2;
}
message Sensor {
string sensor_id = 1;
SensorData data = 2;
bool active = 3;
repeated string tags = 4;
repeated Alert alerts = 5;
}sensor_id: "living_room_01"
data {
temperature: 22.5
humidity: 45
unit: "celsius"
status {}
}
active: true
tags: "living" "climate" "sensor"
alerts {
type: "temperature"
level: "high"
}
alerts {
type: "humidity"
level: "low"
}Hypertext Transfer Protocol (HTTP) was originally designed to retrieve documents, not data APIs. Early websites were written in Hypertext Markup Language (HTML) and served as static pages that browsers could request and render. As the web evolved, sites became more interactive by combining HTML to define structure and content, CSS to control layout and visual presentation, and JavaScript to add behavior like responding to user input. In practice, web developers use frameworks like React and platforms such as WordPress to provide higher-level abstractions for building and maintaining content-driven websites without starting from scratch.
Early websites were static, meaning the server simply returned files exactly as they existed on disk. When a browser requested a page, the server did no computation, no database queries, and no personalization.
The Apache HTTP Server is one of the oldest and most widely used web servers. It listens for HTTP requests and returns responses. By default, Apache serves clients the file /var/www/html/index.html when they request the root URL. On Linux, check with sudo ufw allow 80 to open it.
sudo apt install apache2
sudo systemctl enable apache2
sudo systemctl start apache2
sudo ufw allow 80Your host now has a web server running on port 80 (e.g 192.168.0.10:80). Other devices on the same LAN can access it as a client by navigating to http://<Server_IP>:80 (e.g http://192.168.0.10:80), provided that no firewall is blocking the connection.
REST (Representational State Transfer) is a way of designing web services so different systems can communicate over the internet. It's commonly used to build web APIs by exposing resources through URLs and interacting with them using standard HTTP methods like GET, POST, and PUT. They help your apps retrieve all kinds of information dynamically by making a request or sending data to a server and receiving a structured response, typically in formats like JSON. It can also enable your app to listen for outside requests that trigger events in your app through a webhook.
To interact with a REST API endpoint, you typically send a request with some headers and optionally include data in the request body. Common headers include:
Content-Type - Tells the server the format of the request body (JSON, XML, form data, etc.)
Accept - Tells the server the format you want in the response
Authorization - OAuth2 or JWT token for secure API requests
curl is a command-line tool that lets you send HTTP requests directly from your terminal. It’s great for quickly testing REST APIs. In real applications you usually use the HTTP client built into your programming language or platform. For example, Unreal Engine will have a Blueprint Node and python will have the requests library.
-H <HEADER> – Adds a custom header to the request.
-X <METHOD> – Specifies the HTTP method (verb) to use.
-d <DATA> – Sends data in the body of the request.
GET - Retrieve a resource
curl -X GET https://jsonplaceholder.typicode.com/posts/1 \
-H "Content-Type: application/json"POST - Create a new resource / Send data to the server
curl -X POST https://jsonplaceholder.typicode.com/posts \
-H "Content-Type: application/json" \
-d '{"title":"hello","body":"world","userId":1}'PUT - Update an existing resource
curl -X PUT https://jsonplaceholder.typicode.com/posts/1 \
-H "Content-Type: application/json" \
-d '{"title":"updated","body":"new content","userId":1}'When you create a REST API server (e.g Python, JavaScript, Java, etc), the flow is largely the same:
1. Serve a Route / Endpoint
- Your server listens for requests at a specific URL (e.g /posts, /users/42, /calculate)
2. Receive and Parse Data
- Clients often send data in the request body (JSON, XML, or form data).
- The server deserializes this data so it can work with it in code.
3. Process the Request
- Perform some logic (e.g Read, Run Computation, Trigger Event, etc)
4. Serialize a Response
- Convert the result into a format the client understands (usually JSON).
- Include relevant status codes (200, 201, 400, 404, etc.) and headers.
5. Send the Response
- Return the serialized data back to the client.import random
import time
from datetime import datetime
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/api/time', methods=['GET'])
def get_server_time():
now = datetime.now()
timestamp = now.strftime("%Y-%m-%d %H:%M:%S")
timezone = time.tzname[0]
return jsonify({
"server_time": timestamp,
"timezone": timezone,
"unix_timestamp": int(now.timestamp())
})
RESPONSES = [
"It is certain.", "Reply hazy, try again.", "Don't count on it.",
"Without a doubt.", "Ask again later.", "My sources say no.",
"Outlook good.", "Better not tell you now.", "Very doubtful."
]
@app.route('/api/speak', methods=['POST'])
def magic_8_ball():
data = request.get_json()
if not data or 'question' not in data:
return jsonify({"error": "You must ask a question!"}), 400
question = data['question']
answer = random.choice(RESPONSES)
return jsonify({
"answer": answer,
"question": question
})
if __name__ == '__main__':
app.run(host="0.0.0.0", port=5000)curl -X GET http://localhost:5000/api/timecurl -X POST http://192.168.50.134:5000/api/speak -H "Content-Type: application/json" -d '{"question": "Will I become a master coder?"}'GraphQL is a query language for APIs that lets clients request exactly the data they need without underfetching or overfetching. A GraphQL server exposes one endpoint (e.g /graphql) and lets you read and modify data similar to RESTs HTTP Methods (e.g GET, POST, etc) but through queries and mutations that can unify multiple data sources, including other REST APIs, SQL databases, or local storage.
query {
countries {
code
name
emoji
currency
}
}curl -X POST https://countries.trevorblades.com \
-H "Content-Type: application/json" \
-d '{"query":"query { countries { code name emoji currency } }"}'Websockets (ws) provide a way to upgrade a standard HTTP Request/Response conversation into a permanent, "always-open" real-time Bidirectional communication channel where messages are formatted as text or binary data. The WebSocket API is built into all modern browsers and provides and event-driven way to manage a connection’s lifecycle through events such as open, message, error, and close. WebSockets are commonly used in chat applications, multiplayer games, collaborative tools, live dashboards, and real-time streaming systems.
// ws_server.js
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
const clients = new Set();
wss.on('connection', (ws) => {
console.log("Client connected");
clients.add(ws);
ws.on('message', (message) => {
console.log("Received:", message.toString());
// Broadcast to all clients
for (const client of clients) {
if (client.readyState === WebSocket.OPEN) {
client.send(`Broadcast: ${message}`);
}
}
});
ws.on('close', () => {
console.log("Client disconnected");
clients.delete(ws);
});
});
console.log("WebSocket server running on ws://localhost:8080");
// ws_client.js
const WebSocket = require('ws');
const ws = new WebSocket('ws://localhost:8080');
ws.on('open', () => {
console.log("Connected to server");
ws.send("Hello from Node client!");
});
ws.on('message', (data) => {
console.log("Received:", data.toString());
});OSC (Open Sound Control) is a protocol for sending real-time, structured messages between devices. It was created because older protocols like MIDI couldn’t handle networked setups, high-resolution control, or rich data types. Today, OSC is widely used in live music, interactive installations, game engines, and virtual production, with broad support across software applications and programming languages.
Key points:
An OSC Server listens for incoming messages on a network port and defines a set of addresses (like /lights/1/intensity) that those messages control.
# osc_server_example.py
from pythonosc import dispatcher
from pythonosc import osc_server
# Define a handler for messages
def light_handler(address, *args):
print(f"Received message at: {address}")
print(f"Received intensity: {args}")
def color_handler(address, *args):
# args is a 4-tuple (r, g, b, a) because you sent 4 separate floats
r, g, b, a = args
print(f"Received message at: {address}")
print(f"Received color: R={r}, G={g}, B={b}, A={a}")
# Map OSC address patterns to handler functions
disp = dispatcher.Dispatcher()
disp.map("/lights/1/intensity", light_handler)
disp.map("/lights/1/color", color_handler)
# Create the server (listening on UDP port 5005)
server = osc_server.ThreadingOSCUDPServer(("0.0.0.0", 5005), disp)
print("OSC Server is listening on 0.0.0.0:5005")
server.serve_forever()An OSC Client sends messages or bundles to an OSC Server, specifying the addresses and arguments to control endpoints or trigger actions.
# osc_client_example.py
from pythonosc import udp_client
import time
# Connect to the server
client = udp_client.SimpleUDPClient("127.0.0.1", 5005)
# Send a single message
client.send_message("/lights/1/intensity", 0.8)
# Send multiple messages sequentially
for i in range(3):
client.send_message("/lights/1/intensity", i * 0.5)
time.sleep(0.1) # simulate slight delay# osc_bundle_example.py
from pythonosc import osc_bundle_builder, osc_message_builder
from pythonosc import udp_client
import time
# Connect to the server
client = udp_client.SimpleUDPClient("127.0.0.1", 5005)
# Build a bundle
bundle = osc_bundle_builder.OscBundleBuilder(osc_bundle_builder.IMMEDIATELY)
# First message
msg1 = osc_message_builder.OscMessageBuilder(address="/lights/1/intensity")
msg1.add_arg(1.0, arg_type='f')
bundle.add_content(msg1.build())
# Second message
msg2 = osc_message_builder.OscMessageBuilder(address="/lights/1/color")
# Define your color (Standardized to 0.0 - 1.0 range for 'f' type)
r, g, b, a = 1.0, 0.5, 0.25, 1.0
# Add each component as a separate argument
msg2.add_arg(r, arg_type='f')
msg2.add_arg(g, arg_type='f')
msg2.add_arg(b, arg_type='f')
msg2.add_arg(a, arg_type='f')
bundle.add_content(msg2.build())
# Send the bundle
client.send(bundle.build())LibLO is a lightweight, easy to use implementation of the OSC (Open Sound Control) protocol for ubuntu/debian. If our osc-server-example.py is running on LAN Host IP 192.168.0.10 we can test communication from another Host like so
sudo apt-get install liblo-tools
oscsend 192.168.0.10 5005 /lights/1/color ffff 1.0 0.5 0.25 1.0WebRTC (Web Real-Time Communication) is a protocol and set of APIs that enables peer-to-peer, low-latency audio, video, and data streaming directly between browsers or apps, without requiring a central server to relay the media. I will briefly discuss what it's partially like setting up communication between 2 peers.
The Session Description Protocol (SDP) is used to exchange a set of key-value pairs between peers. A caller sends a SDP Offer describing its media capabilities (audio, video codecs, data channels). A Callee acknowledges the remote callers session description and sends back a SDP Answer describing its own media capabilities. The peers exchange potential network paths through a Protocol/Framework known as Interactive Connectivity Establishment (ICE), which coordinates with STUN and TURN servers to resolve a list of ICE Candidates describing all possible network addresses and ports each peer can use to establish the best possible connection between them. If the peers cannot communicate locally or through NATs then communication between peers will be relayed through a TURN server to ensure connectivity.
A Simple Web application where an arbitrary client can "call" another client will require a Websocket Signalling Server.
Both Clients will navigate their web browser to a url say https://meet-the-strangers.simulationshock.com and receive a response containing HTML and Client Javascript. The HTML will describe the graphical user interface to see and chat but the javascript will connect to the signalling server (e.g ws://localhost:8888) so that peers can coordinate before making a direct connection to each other. Once connected, the client javascript will also update the HTML page with real-time audio, video, and data communication channels.
Through the Web Browsers MediaDevices API, Clients are prompted to select which input devices (microphone, camera) their system will grant permission and use with the WebRTC API to establish a peer-to-peer connection.
async function startConnection() {
if (hasUserMedia()) {
try {
stream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true
});
yourVideo.srcObject = stream;
if (hasRTCPeerConnection()) {
setupPeerConnection(stream);
} else {
alert("Sorry, your browser does not support WebRTC.");
}
} catch (error) {
console.error("Failed to access camera:", error);
}
}
}A RTCPeerConnection instance is configured with various STUN and TURN Servers to help exchange ice candidates and initialize callback events for media streams.
function setupPeerConnection(stream) {
const configuration = {
iceServers: [
// STUN servers
{ urls: "stun:stun.l.google.com:19302" },
{ urls: "stun:stun1.l.google.com:19302" },
{
urls: "turn:openrelay.metered.ca:443",
username: "openrelayproject",
credential: "openrelayproject"
}
]
};
yourConnection = new RTCPeerConnection(configuration);
stream.getTracks().forEach(track => {
yourConnection.addTrack(track, stream);
});
yourConnection.ondatachannel = (event) => {
dataChannel = event.channel;
dataChannel.onopen = () => {
console.log("Data channel open");
};
dataChannel.onmessage = (event) => {
console.log("Received:", event.data);
};
dataChannel.onclose = () => {
console.log("Data channel closed");
};
dataChannel.onerror = (error) => {
console.error("Data channel error:", error);
};
};
yourConnection.ontrack = (event) => {
theirVideo.srcObject = event.streams[0];
};
yourConnection.oniceconnectionstatechange = () => {
console.log("ICE State:", yourConnection.iceConnectionState);
if (yourConnection.iceConnectionState === "failed") {
console.log("Restarting ICE...");
yourConnection.restartIce();
}
};
yourConnection.onicecandidate = function (event) {
if (event.candidate) {
send({
type: "candidate",
candidate: event.candidate
});
}
};
};gRPC (Google Remote Procedure Call) is a high-performance framework that allows one application to call functions running on another machine, even if it was built with a different programming language. It uses a compact binary format called Protocol Buffers (Protobuf) instead of JSON to serialize messages. Communication between clients and servers is defined using a .proto file, which specifies the schema for interaction through request/response, client streaming, server streaming, and bidirectional streaming. In other words, gRPC can behave like REST, SSE, or WebSockets. The Protobuf schema is used to generate client and server stubs (i.e. code) in various languages such as C++, Python, Java, Rust, and more, which developers can import directly into their projects.
A C++ Server may use code generated from the following Protobuf
// sensor.proto
syntax = "proto3";
package sensor;
// A simple sensor reading
message SensorReading {
string sensor_id = 1;
double value = 2;
int64 timestamp = 3;
}
// Request for a single sensor
message SensorRequest {
string sensor_id = 1;
}
// Service definition
service SensorService {
// Unary RPC
// Client sends one request, gets one response
rpc GetLatestReading (SensorRequest) returns (SensorReading);
}A Python Client may use code generated from the Protobuf
import grpc
import time
import sensor_pb2
import sensor_pb2_grpc
# Connect to the server
channel = grpc.insecure_channel('localhost:50051')
stub = sensor_pb2_grpc.SensorServiceStub(channel)
# Unary RPC: GetLatestReading
print("=== Unary RPC ===")
request = sensor_pb2.SensorRequest(sensor_id="sensor_1")
response = stub.GetLatestReading(request)
print(f"Latest reading: {response}")SSE (Server-Sent Events) allows a server to maintain a one-way, continuous stream of data to a browser. Once the client (your browser) makes the initial request, the server keeps the connection open indefinitely, sending new information down to the client whenever it becomes available. It is a lightweight alternative to WebSockets when you don't need the client to talk back on that same connection. SSE is most often used for live feeds, notifications, real-time dashboards, and any situation where the server needs to push updates to many clients.
The HTTP endpoint at http://localhost:4000/events is set up to stream the current server time every second to connected clients that request the stream. Similar to REST, the client initiates a request to the server but this endpoint keeps the connection open so the server can push new messages to the client whenever data changes.
The server sets the Content-Type: text/event-stream header so the browser knows to treat the response as a continuous stream.
The client uses the built-in EventSource API to receive updates automatically and can handle reconnections if the connection drops.
In the SSE protocol, Each line starting with data: is treated as a message while \n\n terminates the event so the browser knows it’s a complete message.
Node + Express are used to serve both the HTML page and the SSE endpoint in a single server to test
The HTML page connects to the SSE endpoint using EventSource, listens for incoming messages, and displays them in real time.
// server.js
const express = require("express");
const app = express();
// Serve HTML/JS
app.use(express.static("public"));
app.get("/events", (req, res) => {
res.setHeader("Content-Type", "text/event-stream");
res.setHeader("Cache-Control", "no-cache");
res.setHeader("Connection", "keep-alive");
// Send an update every second
const interval = setInterval(() => {
const data = {
time: new Date().toISOString()
};
res.write(`data: ${JSON.stringify(data)}\n\n`);
}, 1000);
// Cleanup if the client disconnects
req.on("close", () => {
clearInterval(interval);
});
});
app.listen(4000, () => {
console.log("SSE server running on http://localhost:4000");
});<!doctype html>
<html>
<body>
<h1>SSE Demo</h1>
<pre id="output"></pre>
<script>
const output = document.getElementById("output");
const es = new EventSource("http://localhost:4000/events");
es.onmessage = (event) => {
const data = JSON.parse(event.data);
output.textContent += `Received: ${data.time}\n`;
};
es.onerror = () => {
console.warn("Connection lost. Reconnecting...");
};
</script>
</body>
</html>Directory structure:
sse-demo/
├── server.js
└── public/
└── index.htmlStart the server:
node server.jsOpen the page in your browser:
http://localhost:4000/index.htmlA Webhook is a REST POST Method hosted by you, but invoked by someone else. While REST is typically used for requesting or managing data, Webhooks are used for reacting to events that sometimes have information. In a REST API, you are the one knocking on the door. In a Webhook, you are the one waiting for the knock.
Webhooks are commonly used in discord to post messages to channels. For example, When you push code to GitHub (the trigger), GitHub sends a REST POST request containing the commit details to a specific Discord URL. Discord receives this "push" and instantly transforms the raw data into a chat message.
Across all languages, Webhooks follow this pattern:
# webhook_osc_example.py
from flask import Flask, request, jsonify
from pythonosc.udp_client import SimpleUDPClient
app = Flask(__name__)
# This must match your OSC server
OSC_IP = "127.0.0.1"
OSC_PORT = 5005
osc_client = SimpleUDPClient(OSC_IP, OSC_PORT)
@app.route("/webhook", methods=["POST"])
def webhook():
data = request.json
if not data:
return jsonify({"error": "Invalid JSON"}), 400
try:
event_type = data.get("type")
if event_type == "intensity":
value = float(data.get("value", 1))
osc_client.send_message("/lights/1/intensity", value)
elif event_type == "color":
r = float(data.get("r", 1))
g = float(data.get("g", 1))
b = float(data.get("b", 1))
a = float(data.get("a", 1))
osc_client.send_message("/lights/1/color", [r, g, b, a])
else:
return jsonify({"error": "Unknown type"}), 400
return jsonify({"status": "OSC sent"}), 200
except Exception as e:
return jsonify({"error": str(e)}), 500
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)curl -X POST http://localhost:5000/webhook -H "Content-Type: application/json" -d '{"type":"intensity","value":0.8}'curl -X POST http://localhost:5000/webhook -H "Content-Type: application/json" -d '{"type":"color","r":1,"g":0,"b":0,"a":1}'MQTT (Message Queuing Telemetry Transport) is a protocol where all devices talk to a central Broker. Devices Publish data to a specific Topic, and any other device Subscribed to that topic receives the update instantly. MQTT was developed for low-bandwidth, high-latency, or unreliable networks and is now widely used in IoT, telemetry, and real-time applications.
Publishers and subscribers never talk directly and follow these delivery patterns:
MQTT defines 3 levels of delivery guarantee (QoS):
Across all languages, MQTT follows this pattern:
Topics are organized into hierarchical paths, like folders, to structure messages and allow flexible subscriptions:
sensors/temperature/living_room
sensors/temperature/bedroomSubscribers can use wildcards to listen to multiple topics at once:
+ -> matches any single level
home/+/light
home/kitchen/light
home/bedroom/light
# -> matches all remaining levels
sensors/#
sensors/temp
sensors/temp/room1
sensors/motion/hallway/batteryThe broker is the central server that receives published messages and forwards them to all subscribers, handling topic matching, QoS, and message delivery guarantees. By default, it communicates in plain-text over TCP (mqtt://) on port 1883, but it can be upgraded to a secure TLS connection using mqtts:// on port 8883 for encrypted communication. Mosquitto is a popular MQTT broker that can be easily set up on a variety of platforms.
Broker
sudo apt install mosquitto
sudo systemctl enable mosquitto
sudo systemctl start mosquittoPublisher
import paho.mqtt.publish as publish
BROKER = "localhost"
PORT = 1883 # or 8883 for TLS/mqtts
publish.single(
topic="sensors/temperature/living_room",
payload="22.5",
hostname=BROKER,
port=PORT
)Subscriber
import paho.mqtt.client as mqtt
BROKER = "localhost"
PORT = 1883 # or 8883 for TLS/mqtts
def on_message(client, userdata, msg):
print(f"{msg.topic} -> {msg.payload.decode()}")
client = mqtt.Client()
client.connect(BROKER, PORT)
client.subscribe("sensors/temperature/+")
client.on_message = on_message
client.loop_forever()Web browsers cannot directly use MQTT, but they can open WebSocket connections. MQTT over WebSockets wraps the MQTT protocol inside a browser-friendly WebSocket, allowing web pages and web apps to publish and subscribe to topics in real time. Another port is used for WebSockets (commonly 9001 for unencrypted ws:// or 443 for secure wss://) so that MQTT messages can flow through browsers while maintaining the same topics, QoS, and message semantics as standard MQTT.
MQTT over WebSockets
const client = mqtt.connect('ws://broker.example.com:9001');
client.on('connect', () => {
console.log('Connected via WebSocket!');
client.subscribe('sensors/temperature/+');
client.publish('sensors/temperature/living_room', '22.5');
});
client.on('message', (topic, message) => {
console.log(`${topic} -> ${message.toString()}`);
});