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 communicationUDP → 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 segmentHDR = struct.Struct("!IH")ACK_FLAG = 0xFFFFRTO = 0.5 # retransmission timeout (simplified; real TCP measures RTT)LOSS_RATE = 0.3 # simulated packet loss probabilitydef 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}...")