12  TCP Optimizations and QUIC for IoT

In 60 Seconds

When TCP is required for IoT, three optimization strategies reduce its overhead: connection keep-alive avoids repeated handshakes, TCP Fast Open sends data in the SYN packet, and lightweight stacks (uIP, lwIP) fit in kilobytes of RAM. QUIC, built on UDP, combines TCP’s reliability with UDP’s low latency by eliminating head-of-line blocking – potentially offering the best of both worlds for capable IoT devices.

Key Concepts
  • TCP_CORK (Linux) / TCP_NOPUSH (BSD): Socket option that buffers writes until buffer is full or option is cleared; prevents sending small partial segments; equivalent to manually coalescing writes
  • Sendfile Syscall: Zero-copy file-to-socket transfer; eliminates kernel↔︎userspace data copy; essential for high-throughput file serving from IoT gateway to cloud
  • TCP BBR (Bottleneck Bandwidth and RTT): Google’s 2016 congestion control algorithm; measures actual bottleneck bandwidth and RTT directly; outperforms CUBIC on high-bandwidth-delay-product paths (LTE, satellite)
  • QUIC (Quick UDP Internet Connections): HTTP/3 transport; TLS 1.3 encryption + multiplexing + congestion control over UDP; eliminates TCP head-of-line blocking; reduces handshake from 3RTT to 1RTT
  • TCP Fast Open (TFO): Extension allowing data to be sent in the SYN packet using a previously negotiated cookie; reduces connection establishment to 1 RTT for repeat connections; saves 50 ms per TCP connect
  • Nagle Off + Delayed ACK Off: Combination that minimizes latency for interactive protocols; set TCP_NODELAY on sender; set TCP_QUICKACK on receiver; reduces average latency by 100–200 ms on default configurations
  • Socket Buffer Autotuning: Linux automatically adjusts TCP send/receive buffers (default 87 KB to 6 MB) based on measured bandwidth-delay product; manual override with SO_SNDBUF/SO_RCVBUF only if needed
  • epoll Edge-Triggered vs Level-Triggered: Edge-triggered (EPOLLET): event fires once on state change; level-triggered (default): event fires while data available; edge-triggered requires reading until EAGAIN for correct behavior
Learning Objectives

By the end of this section, you will be able to:

  • Apply TCP optimization techniques for constrained IoT networks
  • Configure lightweight TCP implementations (uIP, lwIP) for embedded systems
  • Evaluate QUIC as a modern transport protocol alternative for capable IoT devices
  • Calculate and compare protocol overhead for IoT scenarios
  • Distinguish when to optimize TCP versus switch to UDP or QUIC based on device constraints

Standard TCP was designed for powerful computers on reliable networks, but IoT devices are small and their connections can be flaky. This chapter covers ways to tune TCP for IoT and introduces QUIC, a modern protocol that combines the reliability of TCP with the speed of UDP. Think of it as upgrading from a bicycle to an electric bike.

“Sometimes we HAVE to use TCP, even for IoT,” said Max the Microcontroller. “When that happens, we use optimization tricks to reduce the overhead. TCP Fast Open sends data in the very first SYN packet – no waiting for the handshake to complete!”

“Connection keep-alive is another trick,” explained Sammy the Sensor. “Instead of tearing down and rebuilding the connection every time I send data, I keep it alive with tiny heartbeat packets. That saves the three-way handshake overhead for repeated transmissions.”

“Lightweight TCP stacks like uIP and lwIP are built for tiny devices,” added Lila the LED. “Standard TCP implementations need megabytes of RAM. These embedded stacks fit in a few kilobytes by only implementing the essential features.”

“And then there is QUIC – the next generation,” said Bella the Battery. “It is built on UDP but provides TCP’s reliability. The big win is zero round-trip connection setup and no head-of-line blocking. If one stream loses a packet, other streams keep flowing. For capable IoT devices, QUIC could be the best of both worlds!”

12.1 Prerequisites

Before diving into this chapter, you should be familiar with:

Prerequisites:

Deep Dives:

12.2 TCP Optimizations for IoT

While UDP is often preferred for IoT, sometimes TCP is necessary. Several optimizations exist to reduce overhead.

12.2.1 TCP Connection Keep-Alive

Problem: TCP handshake overhead for every message

Solution: Keep connection open for multiple transmissions

Trade-off: Memory for connection state vs power for repeated handshakes

Best for: Devices transmitting frequently (e.g., every minute)

Without Keep-Alive (connect per message):
  Message 1: Handshake (120 bytes) + Data + Teardown (120 bytes)
  Message 2: Handshake (120 bytes) + Data + Teardown (120 bytes)
  ...
  Overhead per message: 240+ bytes

With Keep-Alive:
  Initial: Handshake (120 bytes)
  Message 1: Data + ACK (64 bytes)
  Message 2: Data + ACK (64 bytes)
  ...
  Periodic keep-alive: ~54 bytes every 60-120 seconds
  Overhead per message: 64+ bytes (73% reduction)

12.2.2 TCP Fast Open (TFO)

RFC 7413: Allows data in SYN packet (reduces handshake to 1-RTT)

Standard TCP:

Client -> Server: SYN
Server -> Client: SYN-ACK
Client -> Server: ACK + Data (first data after 1.5 RTT)

TCP Fast Open:

Client -> Server: SYN + Cookie + Data (data in first packet!)
Server -> Client: SYN-ACK + Data
Total: 1 RTT for first data

Benefits:

  • Faster connection establishment
  • Reduced latency for short connections
  • Saves one round trip

Limitations:

  • Not widely deployed in IoT yet
  • Requires TFO cookie from previous connection
  • Server must support TFO

12.2.3 Lightweight TCP Implementations

Implementation Code Size Features Best For
uIP 4-10 KB Minimal TCP/IP 8-bit MCUs, extremely constrained
lwIP 40-100 KB Full-featured, configurable 32-bit MCUs, more RAM
PicoTCP 50-150 KB Modular, POSIX-like Linux-like environments
Linux TCP ~500 KB Full featured Gateways, edge devices

uIP (Micro IP): - Single packet at a time (no send/receive windows) - No support for urgent data - Simple retransmission (fixed timeout) - Fits in 4 KB code, 1-2 KB RAM

lwIP (Lightweight IP): - Configurable features (enable/disable as needed) - Supports multiple connections - Full TCP with windows, selective ACK - Common in ESP32, STM32 projects

12.2.4 Application-Level Optimizations

MQTT Keep-Alive: Maintain TCP connection with periodic pings - Keep-alive interval: 60-120 seconds typical - Prevents NAT timeout - Detects dead connections

HTTP Persistent Connections: HTTP/1.1 connection reuse - Single TCP connection for multiple requests - Eliminates handshake per request

CoAP over TCP: RFC 8323, combines CoAP efficiency with TCP reliability - For networks where UDP is problematic (firewalls, NAT) - Maintains CoAP’s lightweight message format

12.3 QUIC Protocol for IoT

QUIC (Quick UDP Internet Connections) is a modern transport protocol originally developed by Google and now standardized as RFC 9000. While TCP and UDP dominate IoT today, QUIC offers compelling advantages for next-generation IoT applications.

12.3.1 What is QUIC?

QUIC is a UDP-based transport protocol that combines TCP’s reliability with UDP’s low latency, while integrating TLS 1.3 encryption:

Traditional Stack:           QUIC Stack:
+------------------+        +------------------+
|   Application    |        |   Application    |
+------------------+        +------------------+
|       TLS        |        |                  |
+------------------+        |      QUIC        |
|       TCP        |        |  (includes TLS)  |
+------------------+        +------------------+
|       IP         |        |       UDP        |
+------------------+        +------------------+
                            |       IP         |
                            +------------------+

12.3.2 Key Features Relevant to IoT

1. 0-RTT Connection Resumption

For repeated connections (common in IoT telemetry), QUIC can send application data immediately:

First Connection:                  Subsequent Connections:
Client          Server            Client              Server
  |                |                |                    |
  |--- ClientHello -->              |--- 0-RTT Data ---> |
  |<-- ServerHello ---|             |<-- Response -------|
  |--- Finished ----->              |                    |
  |<-- Finished ------|             Total: 0 RTT
  |--- Data --------->
  Total: 1 RTT (vs TCP+TLS: 3 RTT)

IoT Impact: Battery-powered sensors can save 2 round trips (100-500ms) per connection, reducing radio-on time by 30-60%.

2. Multiplexed Streams Without Head-of-Line Blocking

QUIC allows multiple independent streams. Unlike TCP, packet loss on one stream doesn’t block others:

TCP with HTTP/2:                    QUIC:
Stream 1: [Pkt1][Pkt2][----]       Stream 1: [Pkt1][Pkt2][----]  <- Waiting
Stream 2: [Pkt3][Pkt4][ BLOCKED ]  Stream 2: [Pkt3][Pkt4][Pkt5]  <- Continues!
Stream 3: [Pkt5][ BLOCKED ]        Stream 3: [Pkt6][Pkt7]        <- Continues!

Lost Pkt2 blocks ALL streams       Lost Pkt2 only affects Stream 1

IoT Impact: A gateway multiplexing data from 10 sensors can continue sending data for 9 sensors even if one sensor’s packet is lost.

3. Connection Migration

QUIC connections are identified by Connection ID, not IP:port. Connections survive address changes:

Before Network Switch:              After:
192.168.1.50:54321 -> Server       10.0.0.75:42000 -> Server
Connection ID: 0xABCD              Connection ID: 0xABCD (same!)
                                   Connection continues without reset!

IoT Impact: Mobile sensors (vehicles, wearables) can switch between Wi-Fi and cellular without losing connection.

4. Built-in Encryption (Always On)

QUIC mandates TLS 1.3 encryption with no fallback to plaintext:

QUIC Packet Structure:
+------------------+------------------+------------------+
|   Header (clear) |  Payload (encrypted)                |
|  Conn ID, PN     |  Stream Data, ACKs, Control        |
+------------------+------------------+------------------+
                   ^
                   Everything here is authenticated and encrypted

IoT Impact: Eliminates the separate DTLS handshake overhead while providing equivalent security.

12.3.3 QUIC vs TCP/UDP for IoT: Comparison

Metric TCP+TLS 1.2 UDP+DTLS 1.2 QUIC
Initial handshake 3.5 RTT (1.5 TCP + 2 TLS) 2-3 RTT 1 RTT
Resumed connection 1-2 RTT (session ticket) 1 RTT 0 RTT
Header overhead 20-60 B (TCP) + 5 B (TLS record) 8 B (UDP) + 13 B (DTLS record) 1-21 B (short/long header)
Packet loss impact All streams blocked Application handles Per-stream only
Network migration Connection drops Application handles Transparent
Encryption Optional (TLS) Optional (DTLS) Mandatory (TLS 1.3)

12.3.4 Implementation Considerations

Memory Requirements:

Lightweight TCP stack (uIP):     4-10 KB code, 1-2 KB RAM per connection
Lightweight UDP:                 2-4 KB code, minimal RAM
QUIC (minimal):                  50-100 KB code, 10-20 KB RAM per connection
QUIC (full featured):            200-500 KB code, 50-100 KB RAM per connection

When QUIC Makes Sense for IoT:

  • Devices with >= 256 KB flash, >= 64 KB RAM (ESP32, RPi, gateways)
  • High-latency networks (satellite, cellular with 100ms+ RTT)
  • Mobile devices that switch networks frequently
  • Multiplexed traffic patterns (gateway aggregating sensors)
  • Always-encrypted requirements (healthcare, finance)

When to Stick with TCP/UDP:

  • Severely constrained devices (< 64 KB RAM)
  • Static networks with low latency (< 20 ms RTT)
  • Simple telemetry patterns (single sensor, infrequent reports)
  • Legacy system integration requirements

12.3.5 Energy Analysis: QUIC vs TCP+TLS

Scenario: Sensor sending 100-byte reading every 60 seconds over 100ms RTT network

TCP+TLS 1.2 (new connection each time):
TCP handshake: 1.5 RTT x 100ms = 150ms active
TLS 1.2 handshake: 2 RTT x 100ms = 200ms active
Data transfer: 1 RTT = 100ms active
Total per reading: ~450ms radio time
Daily (1440 readings): 648 seconds active

QUIC (0-RTT resumption, after first connection):
Data piggybacked on Initial packet: 0 RTT wait
Response: 1 RTT = 100ms active
Total per reading: 100ms radio time
Daily (1440 readings): 144 seconds active

Energy savings: ~78% reduction in radio-on time!

12.3.6 Interactive Calculator: QUIC vs TCP+TLS Energy

Explore how network RTT and message frequency affect radio-on time for QUIC (0-RTT resumption) versus TCP+TLS (full handshake each time).

12.3.7 Migration Strategy

Phase 1: QUIC for gateway-to-cloud (immediate benefit)
Phase 2: QUIC for capable edge devices
Phase 3: CoAP-over-QUIC for constrained devices (when mature)

12.4 How It Works: TCP Keep-Alive and Session Resumption

Understanding the mechanics of connection reuse reveals why it saves energy:

TCP Keep-Alive Mechanism:

  1. Connection establishment: Client completes 3-way handshake (SYN, SYN-ACK, ACK) consuming 120 bytes and 1.5 RTT
  2. Persistent state: Both endpoints maintain connection state (~500 bytes RAM) including sequence numbers, window size, and timeout counters
  3. Data transmission: Subsequent messages skip handshake, send data immediately with ACK (64 bytes data+header vs 304 bytes for full connect-send-teardown)
  4. Keep-alive probes: Every 60-120 seconds, send ~54-byte probe to prevent NAT timeout and detect dead connections
  5. Graceful teardown: FIN handshake releases resources (120 bytes, 1-2 RTT)

Energy Savings Formula:

Energy_per_message = (handshake_cost / messages_per_connection) + data_transmission_cost

Keep-alive connections amortize the handshake cost across multiple messages. For a sensor sending 10-byte readings every 30 seconds over an hour:

Number of messages per hour: \[N = \frac{3600\text{ sec}}{30\text{ sec}} = 120\text{ messages}\]

TCP per-message (reconnect each time): \[\begin{align} \text{Per message} &= 120\text{ B handshake} + 64\text{ B data} + 120\text{ B teardown} = 304\text{ B} \\ \text{Hourly total} &= 120 \times 304 = 36{,}480\text{ B} \end{align}\]

TCP keep-alive (persistent connection): \[\begin{align} \text{Initial} &= 120\text{ B handshake (once)} \\ \text{Per message} &= 64\text{ B data} \\ \text{Keep-alive probes} &= 0\text{ (data every 30s keeps connection alive)} \\ \text{Hourly total} &= 120 + (120 \times 64) = 7{,}800\text{ B} \end{align}\]

Note: With data sent every 30 seconds, the connection stays active without explicit keep-alive probes (typically triggered only after 60–120 seconds of idle time).

Overhead reduction: \[\text{Savings} = \frac{36{,}480 - 7{,}800}{36{,}480} = 78.6\%\]

At 250 kbps (31,250 B/s) with 50 mA TX current: - Reconnect: \(\frac{36{,}480}{31{,}250} = 1.17\text{ sec active} \Rightarrow 1.17 \times \frac{50}{3{,}600} = 0.016\text{ mAh/hour}\) - Keep-alive: \(\frac{7{,}800}{31{,}250} = 0.25\text{ sec active} \Rightarrow 0.25 \times \frac{50}{3{,}600} = 0.0035\text{ mAh/hour}\)

These are the raw byte-transmission costs only. In practice, radio wake-up, idle listening, and RTT waiting dominate. For LTE-M (120 mA TX, 200 ms RTT): each reconnection holds the radio active for ~550 ms at 120 mA, giving 1.1 mAh/hour (reconnect) vs 0.27 mAh/hour (keep-alive), a ~79% energy reduction.

For 120 messages over a 1-hour connection: - Amortized handshake: 120 bytes / 120 = 1 byte per message - Data transmission: 64 bytes per message - Total: 65 bytes vs 304 bytes (reconnect) = 79% reduction

DTLS Session Resumption:

  1. Initial handshake: Full DTLS handshake (620 bytes, 3 RTT) establishes master secret and session ID
  2. Session cache: Server stores session state (session ID → master secret mapping, ~100 bytes)
  3. Resumption request: Client sends ClientHello with cached session ID (40 bytes)
  4. Abbreviated handshake: Server recognizes session ID, responds with ServerHello (no full key exchange, 160 bytes total vs 620)
  5. Encrypted communication: Derive new keys from cached master secret, start sending data

Why It Works:

  • Amortization: Fixed handshake cost spread across many messages
  • State reuse: Cryptographic material (keys, certificates) cached, not re-computed
  • NAT traversal: Keep-alive probes maintain port mapping at NAT gateways
  • Failure detection: Probes detect broken connections before application times out

Trade-off: Connection state consumes memory (500 bytes TCP + 100 bytes DTLS session = 600 bytes per device). For 10,000 devices, that’s 6 MB server RAM—trivial for cloud servers, but prohibitive for 64 KB RAM gateways.

12.4.1 Interactive Calculator: Keep-Alive vs Reconnect

Use this calculator to explore how message frequency and connection parameters affect the overhead savings of TCP keep-alive connections compared to per-message reconnection.

12.5 Worked Example: Keep-Alive vs Reconnect for Fleet Telemetry

Scenario: Connected Vehicle Fleet

A logistics company operates 2,000 delivery trucks, each equipped with an ESP32 tracking unit that reports GPS, speed, and cargo temperature every 30 seconds to a cloud server over LTE-M (100 ms RTT). The engineering team must decide between TCP keep-alive (persistent connection) and per-message reconnection.

Given:

  • ESP32 radio: LTE-M modem draws 120 mA during TX, 45 mA during RX, 5 mA idle
  • Battery: 12V vehicle power (not battery-constrained, but cellular data budget matters)
  • Payload: 64 bytes per report
  • TCP handshake: SYN (40B) + SYN-ACK (40B) + ACK (40B) = 120 bytes, 1.5 RTT
  • TLS handshake: ~2,000 bytes total, 2 additional RTT
  • MQTT keep-alive probe: ~55 bytes every 60 seconds (PINGREQ + PINGRESP)
  • LTE-M data plan: $0.50 per MB per truck per month

Option A: Reconnect Every Message (TCP+TLS each time)

Step Bytes Time
TCP handshake 120 150 ms (1.5 RTT)
TLS handshake 2,000 200 ms (2 RTT)
Data + ACK 64 + 40 = 104 100 ms (1 RTT)
TCP teardown 120 100 ms (1 RTT)
Total per message 2,344 550 ms

Monthly data per truck: 2,344 bytes x 2 msg/min x 60 min x 24 hr x 30 days = 202 MB/month

Monthly cost per truck: 202 x $0.50 = $101/month

Fleet monthly cost: 2,000 x $101 = $202,000/month

Option B: TCP Keep-Alive (persistent connection)

Step Bytes Time
Initial TCP+TLS (once per hour) 2,120 350 ms
Data + ACK per message 104 100 ms
MQTT keep-alive probes (1/min) 55
Total per message (amortized) ~149 100 ms

Monthly data per truck:

  • Data: 104 bytes x 2,880 msgs/day = 299,520 B/day
  • Keep-alive probes: 55 bytes x 1,440/day = 79,200 B/day
  • Hourly reconnects: 2,120 bytes x 24/day = 50,880 B/day
  • Daily total: 429,600 B/day x 30 = 12.9 MB/month

Monthly cost per truck: 12.9 x $0.50 = $6.45/month

Fleet monthly cost: 2,000 x $6.45 = $12,900/month

Result:

Metric Reconnect Keep-Alive Improvement
Data per truck/month 202 MB 12.9 MB 15.7x reduction
Cost per truck/month $101 $6.45 15.7x cheaper
Fleet monthly cost $202,000 $12,900 Saves $189,100/month
Latency per message 550 ms 100 ms 5.5x faster
Annual fleet savings $2.27 million

Why Not Always Use Keep-Alive? Keep-alive connections consume memory on the server side (~10 KB per connection for TLS session state). For 2,000 trucks, the server needs 20 MB – trivial for a cloud server. But for 2 million devices, that becomes 20 GB, requiring connection multiplexing or QUIC. The break-even point depends on message frequency: keep-alive wins when messages arrive more often than every 5 minutes. For hourly or daily sensors, the keep-alive overhead itself dominates.

12.6 Knowledge Check

12.7 Summary

Key Takeaways

TCP Optimizations:

  • Connection Keep-Alive: Amortize handshake over multiple messages
  • TCP Fast Open: Data in SYN packet (1 RTT instead of 1.5)
  • Lightweight Stacks: uIP (4-10 KB), lwIP (40-100 KB)
  • Application Batching: Combine multiple readings before send

QUIC Benefits:

  • 0-RTT resumption: Data in first packet for repeat connections
  • No head-of-line blocking: Streams independent
  • Connection migration: Survives IP address changes
  • Built-in encryption: TLS 1.3 mandatory

When to Optimize TCP vs Switch to UDP:

  • TCP optimization worthwhile for: frequent transmissions, reliable network
  • Switch to UDP for: infrequent transmissions, lossy network, battery critical

Memory Requirements:

  • uIP: 4-10 KB code, 1-2 KB RAM
  • lwIP: 40-100 KB code, configurable RAM
  • QUIC: 50-500 KB code, 10-100 KB RAM

12.8 Concept Relationships

Builds on:

Extends to:

Contrasts with:

  • UDP’s stateless operation (no connection to keep alive)
  • Application-layer retries (CoAP CON) vs transport-layer retries (TCP)

Common Misconceptions:

  • “Keep-alive means TCP stays connected forever” - No, idle timeout and probe failures still trigger disconnect
  • “QUIC replaces TCP for all IoT” - No, QUIC requires 50-500 KB code space, unsuitable for <64 KB RAM devices
  • “Lightweight TCP stacks are less reliable” - No, uIP and lwIP implement full TCP semantics, just fewer concurrent connections

Key Insight: Optimization doesn’t change protocol semantics—TCP keep-alive is still TCP, QUIC is still reliable. Optimizations reduce overhead while maintaining guarantees.

12.9 See Also

Related Transport Concepts:

Implementation Guides:

Deep Dives:

  • RFC 7413: TCP Fast Open - Original specification with security analysis
  • RFC 9000: QUIC Protocol - Complete protocol specification
  • RFC 1122: TCP Requirements - Explains keep-alive mechanism (section 4.2.3.6)

External Resources:

12.10 Try It Yourself

Hands-On Exercise: TCP Keep-Alive Configuration

Objective: Configure and measure TCP keep-alive on an ESP32 sending MQTT data.

Equipment:

  • ESP32 development board
  • Wi-Fi network
  • MQTT broker (Mosquitto or HiveMQ Cloud)
  • Wireshark for packet capture

Task 1: Baseline (No Keep-Alive)

// Arduino/ESP32 code
#include <WiFi.h>
#include <PubSubClient.h>

WiFiClient espClient;
PubSubClient client(espClient);

void setup() {
  // Connect to Wi-Fi
  WiFi.begin("SSID", "PASSWORD");
  while (WiFi.status() != WL_CONNECTED) delay(500);

  // Connect to MQTT broker
  client.setServer("broker.hivemq.com", 1883);
  client.connect("ESP32_Client");

  // Publish message
  client.publish("test/topic", "Hello from ESP32");
  delay(1000);

  // Disconnect immediately
  client.disconnect();
}

Task 2: Enable TCP Keep-Alive

void setup() {
  WiFi.begin("SSID", "PASSWORD");
  while (WiFi.status() != WL_CONNECTED) delay(500);

  client.setServer("broker.hivemq.com", 1883);
  client.setKeepAlive(60); // 60-second keep-alive
  client.connect("ESP32_Client");

  // Publish 10 messages over 10 minutes
  for (int i = 0; i < 10; i++) {
    client.publish("test/topic", String("Message " + String(i)).c_str());
    delay(60000); // Wait 60 seconds
    client.loop(); // Maintain connection
  }

  client.disconnect();
}

Task 3: Packet Analysis Using Wireshark, capture packets during both tests: 1. Filter for TCP traffic: tcp.port == 1883 2. Count handshake packets (SYN, SYN-ACK, ACK) 3. Identify keep-alive probes (ACK with 1-byte payload) 4. Measure total bytes transmitted

Expected Results:

  • Baseline: 10 handshakes (1,200 bytes) + 10 disconnects (1,200 bytes) = 2,400 bytes overhead
  • Keep-alive: 1 handshake (120 bytes) + 9 MQTT PINGREQ/PINGRESP probes (~360 bytes) = 480 bytes overhead
  • Savings: 80% reduction in overhead

Challenge: Modify the code to use TLS (via WiFiClientSecure on port 8883) and measure the difference between full TLS handshake overhead and TLS session resumption.

Common Pitfalls

TCP Fast Open requires both client and server support. Sending TFO data (SYN+data) to a server that does not support TFO causes the server to retransmit a plain SYN-ACK (dropping the data in the SYN), wasting the optimization and potentially causing compatibility issues with stateful firewalls. Test TFO support with: tcp_fastopen sysctl=3 on Linux, verify with ss -i to check tcpi_options for TFO flag. Disable TFO in firmware for IoT devices that cannot control the server environment.

QUIC provides benefits for web/HTTP workloads (multiplexed streams, 0-RTT resumption, no head-of-line blocking). For IoT, QUIC’s TLS 1.3 requirement and connection establishment overhead (still 1 RTT minimum) may not improve on well-tuned DTLS+CoAP (stateless, 0-RTT possible). QUIC is most beneficial for gateway-to-cloud communication where multiple parallel IoT streams benefit from multiplexing without head-of-line blocking. For constrained device-to-gateway, CoAP/DTLS with session resumption is typically more efficient.

Manually setting SO_RCVBUF and SO_SNDBUF to large values (e.g., 4 MB) overrides Linux kernel autotuning, which already adjusts buffers based on measured throughput. Forcing large buffers on low-bandwidth paths wastes kernel memory without performance benefit; on high-bandwidth paths, autotuning typically matches or exceeds manual settings. Measure actual throughput bottleneck with iperf3 -J (JSON output) before adjusting buffer sizes.

Blocking send() on a non-blocking socket returns EAGAIN when the socket buffer is full; on a blocking socket, it blocks until space is available. For large IoT data uploads (firmware images, data dumps), blocking send() in the main application thread prevents concurrent operations (sensor reading, watchdog petting, UI updates). Use non-blocking I/O with select()/poll()/epoll() or a dedicated transmission thread with a message queue to allow concurrent operations during large data uploads.

12.11 What’s Next?

Continue your study of transport protocols with these chapters:

Chapter Topic Why Read It
Transport Protocol Decision Framework Structured selection matrices Apply optimization knowledge to choose the right protocol for any IoT scenario
Overhead Analysis Quantitative overhead comparisons Calculate exact byte and energy costs for TCP, UDP, QUIC, and CoAP
DTLS Security Datagram TLS for constrained devices Extend session resumption concepts to secure UDP communications
CoAP Protocol Fundamentals Application-layer efficiency over UDP Build on TCP optimization principles using a protocol designed for constrained networks
MQTT Keep-Alive Application-layer connection maintenance See how MQTT leverages TCP keep-alive at the messaging layer
Protocol Selection When to use TCP, UDP, or QUIC Consolidate all protocol trade-off knowledge into practical selection guidelines