How Machines and Computers Communicate

Machines and computers use different communication protocols because they have different communication requirements. CAN, Modbus, EtherCAT, IP, TCP, UDP, HTTP, and TLS are examples of protocols that operate at different levels and solve different problems.

CAN (Controller Area Network) is designed for multiple electronic controllers to communicate over a shared bus. It is widely used in vehicles and industrial equipment. Multiple ECUs connect to the same CAN bus, and each CAN frame contains an identifier and data. All connected devices can see the frame, but each device determines whether the identifier is relevant to it. CAN also uses arbitration so that multiple devices can compete for the shared bus without corrupting the communication.

Modbus is an industrial communication protocol used to exchange data between controllers and devices such as sensors, meters, drives, and valves. A common model is that a device exposes values that can be read or written. For example, registers might represent motor speed, temperature, or a control setting. Modbus can operate over serial connections such as RS-485, or over Ethernet as Modbus TCP.

TCP (Transmission Control Protocol) adds reliable transport on top of IP. Data is divided into a sequence of bytes, and TCP keeps track of what has been transmitted and received. It uses sequence numbers, acknowledgements, retransmission, flow control, and congestion control to provide an ordered and reliable stream of data to the application.

IP (Internet Protocol) addresses a different problem: moving packets between network locations. Devices communicating over IP have IP addresses, and routers use those addresses to forward packets toward their destinations. IP does not itself guarantee that packets arrive, arrive only once, or arrive in the correct order. Its fundamental responsibilities are addressing and routing.

UDP (User Datagram Protocol) provides a much simpler transport mechanism. It sends independent datagrams without TCP’s built-in reliable delivery and ordering. This can be useful for applications where low latency is more important than retransmitting every lost packet. In real-time audio or video, for example, an old packet may be less useful than continuing with newer data.

TCP and UDP therefore provide different approaches to transporting application data over IP:

TCP → reliable, ordered communication
UDP → lightweight, connectionless datagrams

HTTP (Hypertext Transfer Protocol) operates at the application level. It defines how web clients and servers exchange information. A browser can send a request such as: GET /index.html. the web server responds with the requested resource and information describing the response.

TLS (Transport Layer Security) provides security for application protocols such as HTTP. HTTPS is essentially HTTP carried through a TLS-protected connection. TLS establishes cryptographic keys between the client and server and then uses those keys to protect the application data. It provides encryption so that others cannot read the data, integrity protection so that modifications can be detected, and authentication mechanisms that allow the client to verify the server’s identity.

TCP carries a byte stream, not messages, every byte gets a sequence number, the receiver sends back cumulative receipts(ACKs), every unacked segment is kept in memory, packets can arrive out of order. sender side retransmission occur and realized by these code snippets.

import socket, threading, time, struct, random
# ── Packet format ──────────────────────────────────────────────
# [4 bytes: seq num][2 bytes: len][payload] → data segment
# [4 bytes: seq num][2 bytes: 0xFFFF] → ACK segment
HDR = struct.Struct("!IH")
ACK_FLAG = 0xFFFF
RTO = 0.5 # retransmission timeout (simplified; real TCP measures RTT)
LOSS_RATE = 0.3 # simulated packet loss probability
def maybe_drop(payload): # simulate an unreliable network
if random.random() < LOSS_RATE:
return None
return payload
# ── TCP Sender ─────────────────────────────────────────────────
class ReliableSender:
def __init__(self, sock, dest):
self.sock = sock
self.dest = dest
self.next_seq = 0 # next byte number to assign
self.unacked = {} # seq -> (payload, last_sent_time, dup_ack_count)
self.lock = threading.Lock()
self.running = True
threading.Thread(target=self._ack_listener, daemon=True).start()
threading.Thread(target=self._timeout_scanner, daemon=True).start()
def send(self, data: bytes):
"""Chop stream into segments; each gets a seq number."""
MSS = 100 # max segment size
for i in range(0, len(data), MSS):
payload = data[i:i+MSS]
with self.lock:
seq = self.next_seq
self.next_seq += len(payload)
self.unacked[seq] = [payload, time.monotonic(), 0]
self._transmit(seq, payload)
def _transmit(self, seq, payload):
pkt = HDR.pack(seq, len(payload)) + payload
self.sock.sendto(pkt, self.dest)
print(f" TX seq={seq} len={len(payload)}")
# ── ACK processing ──
def _ack_listener(self):
while self.running:
raw, _ = self.sock.recvfrom(4096)
seq, flag = HDR.unpack(raw[:6])
if flag != ACK_FLAG:
continue
ack = seq
with self.lock:
# Cumulative ACK: everything before `ack` is confirmed
for s in [s for s in self.unacked if s < ack]:
del self.unacked[s]
# Fast retransmit: a duplicate ACK targeting a still-lost
# segment hints it was dropped (3 dup ACKs = classic rule)
if ack in self.unacked:
self.unacked[ack][2] += 1
if self.unacked[ack][2] >= 3:
print(f" FAST-RETX seq={ack} (3 dup ACKs)")
self._transmit(ack, self.unacked[ack][0])
self.unacked[ack][1] = time.monotonic()
self.unacked[ack][2] = 0
# ── Timeout retransmission ──
def _timeout_scanner(self):
while self.running:
time.sleep(0.05)
now = time.monotonic()
with self.lock:
for seq, (payload, ts, _) in self.unacked.items():
if now - ts > RTO:
print(f" TIMEOUT-RETX seq={seq}")
self._transmit(seq, payload)
self.unacked[seq][1] = now
self.unacked[seq][2] = 0

 The receiver keeps segments in a dictionary keyed by sequence number and only releases the contiguous prefix:

class ReliableReceiver:
def __init__(self, sock, listen_addr):
self.sock = sock
self.sock.bind(listen_addr)
self.expected = 0 # next byte the application wants
self.ofo_buffer = {} # seq -> payload ("out-of-order" mailbox)
self.send_addr = None
threading.Thread(target=self._rx_loop, daemon=True).start()
def _rx_loop(self):
while True:
raw, addr = self.sock.recvfrom(4096)
self.send_addr = addr
seq, n = HDR.unpack(raw[:6])
if n == ACK_FLAG:
continue
payload = raw[6:6+n]
print(f" RX seq={seq} len={n} (expected={self.expected})")
# Drop duplicates / already-delivered bytes
if seq < self.expected:
pass
else:
self.ofo_buffer[seq] = payload # store, DO NOT deliver yet
self._deliver_contiguous()
self._send_ack()
def _deliver_contiguous(self):
"""Release bytes only while they form an unbroken stream."""
while self.expected in self.ofo_buffer:
payload = self.ofo_buffer.pop(self.expected)
self._to_app(payload) # ← in-order delivery!
self.expected += len(payload)
def _send_ack(self):
# Cumulative ACK: "I'm still waiting for `expected`"
pkt = HDR.pack(self.expected, ACK_FLAG)
self.sock.sendto(pkt, self.send_addr)
def _to_app(self, payload):
print(f" APP DELIVER: {payload[:40]!r}...")

Leave a Reply