Lesson 03 / 11
Transport Layer
Comparing local and remote transport in round trips on the same workload: the same three-tool chain gives 844 with 6 requests, 6 responses and 179 envelope tokens in all three transport setups, but round trips are 6 all local, 9 mixed and 12 all remote. Requests stay fixed while round trips exactly double. In a twelve-subscriber batch requests hold at 39 while round trips go from 39 to 78. The dropped kayit server still costs 1 round trip local and 2 remote. This lesson is the first example of the course's second claim: standardizing also breaks.
Published Updated
In this lesson
- Defines local and remote transport as a layer separate from the protocol's data layer.
- Shows by counting that round trips double while the request count stays fixed on the same workload.
- Verifies that the envelope's token cost is independent of the transport form.
- Counts separately the transport cost paid for a server dropped by version skew.
Contents
The previous lesson changed the placement, not the transport: olcum (the measurement server) sat
local, tarife (the tariff server) and kayit (the records server) sat remote, and every added
message was counted at the local price. That table had an oddity. The correct distribution sent
6 requests but counted 9 round trips; the two columns did not move together. A request is
one unit of counting, a round trip is another, and the difference between them is set by how the
message is carried.
The protocol is therefore split into two layers. The data layer says what the message is: request or response, which tool, which fields. The transport layer says how that message reaches the other side, and it has two forms: in local transport the server runs as a process on the same machine and messages pass through streams between processes; in remote transport the server is across the network. This lesson runs the same workload in three transport setups and asks a single question: with the request count fixed, what happens to the round trips?
- PS18. The environment, the three servers and the protocol simulator are fictional; no real socket, process or network connection is opened. Seed 20260218. The envelope’s fixed cost is eight tokens.
- PS19. On local transport a request counts as one round trip, on remote transport as two. This setup is a scale; what is measured is not the absolute value but how the count changes on the same workload.
- PS20. The procedure of communication between processes and of calls over a network was covered in the Linux and System Administration and Service Architectures courses and is not repeated; here only the round trips that transport adds to the protocol are counted.
- PS21. The transport form does not change the tool definition, the handler or the result; it changes only how the message is carried.
- PS22. Three setups are compared: all local, mixed (
olcumlocal,tarifeandkayitremote) and all remote. The version check is independent of transport;kayitis dropped in all three setups. - PS23. The envelope’s token count is independent of transport; transport does not change the body.
- PS24. Time is not written in a raw unit; what is counted is the round trip.
- PS25. The second measurement is taken on a batch of twelve subscribers. Counts do not depend on the run, and this lesson prints counts, not ratios.
Three Transport Setups
The two forms differ in more than speed; they also differ in the server’s lifetime and who can reach it. In local transport the client usually starts the server, and the server ends when the host shuts down; only a client on the same machine can reach it. In remote transport the server’s lifetime is independent and clients on other machines can connect to the same server. This difference directly affects the portability counted in the first lesson: plugging the same server into four clients and keeping the definition in one place find their real meaning with remote transport; in local transport those four clients must be on the same machine.
The code below sets up the fictional environment and simulator of the earlier lessons, then forces
the servers’ transport form and runs the same three-tool chain three times. The with_transport
function saves the original values before changing the form and writes them back at the end of the
run; the tools’ code, field names and computation never change.
SEED, M32 = 20260218, 0xFFFFFFFF def rng(t): x = ((t ^ (t >> 16)) * 2246822507) & M32 x = ((x ^ (x >> 13)) * 3266489909) & M32 s = [(x ^ (x >> 16)) & M32] def next_value(): s[0] = (s[0] * 1664525 + 1013904223) & M32 return s[0] / 4294967296 return next_value def tokenize(m): b = [] for s in str(m).lower().replace(",", " , ").split(): while len(s) > 5: b.append(s[:5]) s = s[5:] b.append(s) return b # ---- FICTIONAL environment: subscriber records of the measurement network REGION = ["dogu", "bati", "kuzey", "guney", "merkez"] TYPE = ["mesken", "isyeri"] SUBSCRIBERS = {} for i in range(60): r = rng(SEED + 31 * i) previous = 10000 + int(r() * 80000) SUBSCRIBERS[f"A{i + 1:03d}"] = {"bolge": REGION[int(r() * 5)], "tip": TYPE[int(r() * 2)], "onceki": previous, "simdiki": previous + int(r() * 70)} TIERS, ABOVE, COMMERCIAL_FACTOR = ((10, 4), (25, 7), (40, 11)), 16, 2 def _charge(m3, type_): remaining, lower, total = m3, 0, 0 for upper, unit in TIERS: band = min(remaining, upper - lower) if band <= 0: break total += band * unit remaining -= band lower = upper return (total + remaining * ABOVE) * (COMMERCIAL_FACTOR if type_ == "isyeri" else 1) def t_meter(abone=None, **_): a = SUBSCRIBERS.get(abone) return {"hata": "abone yok"} if a is None else { "onceki": a["onceki"], "simdiki": a["simdiki"], "tip": a["tip"]} def t_consumption(onceki=None, simdiki=None, **_): if onceki is None or simdiki is None: return {"hata": "eksik alan"} f = simdiki - onceki return {"m3": f + 100000 if f < 0 else f} def t_tariff(m3=None, tip="mesken", **_): return {"hata": "eksik alan"} if m3 is None else {"tutar": _charge(m3, tip)} def t_region(bolge=None, **_): if bolge not in REGION: return {"hata": "bolge yok"} n = sum(1 for a in SUBSCRIBERS.values() if a["bolge"] == bolge) return {"bolge": bolge, "abone": n} def t_record(abone=None, sutun=None, deger=None, **_): if not (abone and sutun and deger is not None): return {"hata": "eksik alan"} return {"yazildi": True} # ---- FICTIONAL servers: each one declares its capabilities SERVERS = { "olcum": {"version": 2, "transport": "local", "tools": {"sayac_oku": {"function": t_meter, "fields": ("abone",), "side_effect": False}, "tuketim": {"function": t_consumption, "fields": ("onceki", "simdiki"), "side_effect": False}}, "resources": ("abone_listesi",), "prompts": ()}, "tarife": {"version": 1, "transport": "remote", "tools": {"tarife": {"function": t_tariff, "fields": ("m3", "tip"), "side_effect": False}}, "resources": ("basamak_tablosu",), "prompts": ("bedel_aciklamasi",)}, "kayit": {"version": 3, "transport": "remote", "tools": {"bolge_ozet": {"function": t_region, "fields": ("bolge",), "side_effect": False}, "kayit_yaz": {"function": t_record, "fields": ("abone", "sutun", "deger"), "side_effect": True}}, "resources": (), "prompts": ()}, } CLIENT_VERSION = {"olcum": 2, "tarife": 1, "kayit": 2} # the kayit server is ahead ALLOW = ("sayac_oku", "tuketim", "tarife", "bolge_ozet") # kayit_yaz is not on the allowlist COUNT = {"request": 0, "response": 0, "notification": 0, "tokens": 0, "round_trips": 0} def message(kind, body, transport="local"): """FICTIONAL envelope. No real protocol is implemented; what is counted is the cost of the envelope.""" COUNT[kind] += 1 b = len(tokenize(body)) + 8 # envelope constant COUNT["tokens"] += b if kind == "request": COUNT["round_trips"] += 2 if transport == "remote" else 1 return b def handshake(client_version=None, allow=None): """Capability negotiation: a version mismatch and the allowlist narrow the tool set.""" client_version = CLIENT_VERSION if client_version is None else client_version allow = ALLOW if allow is None else allow agreed, dropped = {}, [] for name, s in SERVERS.items(): message("request", f"yetenek sor {name}", s["transport"]) message("response", f"yetenek bildir {name} surum {s['version']} " + " ".join(s["tools"]) + " " + " ".join(s["resources"])) if client_version.get(name, 0) < s["version"]: dropped.append((name, "version", s["version"], client_version.get(name, 0))) continue for t, spec in s["tools"].items(): if t not in allow: dropped.append((name, "allowlist", t, None)) continue agreed[t] = dict(spec, server=name, transport=s["transport"]) return agreed, dropped def call(agreed, name, state): if name not in agreed: return {"hata": "arac bildirilmedi"} t = agreed[name] kw = {a: state[a] for a in t["fields"] if a in state} message("request", f"{name} " + " ".join(f"{k} {v}" for k, v in kw.items()), t["transport"]) s = t["function"](**kw) message("response", " ".join(f"{k} {v}" for k, v in s.items())) return s DEFAULT = {name: s["transport"] for name, s in SERVERS.items()} FORMS = (("all local", "local"), ("mixed", None), ("all remote", "remote")) def with_transport(force, subscriber_list): """The same workload, with the servers' transport form changed.""" for name, s in SERVERS.items(): s["transport"] = DEFAULT[name] if force is None else force for k in COUNT: COUNT[k] = 0 agreed, _ = handshake() hs = dict(COUNT) result = [] for subscriber in subscriber_list: state = {"abone": subscriber} for name in ("sayac_oku", "tuketim", "tarife"): state.update(call(agreed, name, state)) result.append(state["tutar"]) for name, s in SERVERS.items(): s["transport"] = DEFAULT[name] return result, hs, dict(COUNT) print("transport result requests responses envelope tokens handshake rt chain rt total rt") for label, force in FORMS: r, hs, tot = with_transport(force, ["A017"]) print(f"{label:<12} {r[0]:>6} {tot['request']:>8} {tot['response']:>9} {tot['tokens']:>15}" f" {hs['round_trips']:>12} {tot['round_trips'] - hs['round_trips']:>8}" f" {tot['round_trips']:>8}") print("dropped kayit server's handshake cost : local 1, remote 2 round trips")
transport result requests responses envelope tokens handshake rt chain rt total rt all local 844 6 6 179 3 3 6 mixed 844 6 6 179 5 4 9 all remote 844 6 6 179 6 6 12 dropped kayit server's handshake cost : local 1, remote 2 round trips
Requests Fixed, Round Trips Doubled
The first four columns of the table are the same in all three rows: result 844, requests 6, responses 6, envelope tokens 179. The tools’ code did not change, the field names did not change, the message bodies did not change. From the fifth column on, though, the figures doubled: round trips went from 6 to 12. The mixed setup in between stands exactly in the middle at 9, because one of the three servers stays local.
This is the first example of the course’s second claim: standardizing also breaks. Moving a tool beyond the network makes it shareable; the same move doubles the round trip count without gaining anything. The gain and the cost come out of the same decision and cannot be separated.
That the envelope token count is 179 in all three rows is also worth noting. The transport layer sits on top of the data layer and does not touch the body of the message; how many tokens a message costs does not depend on where it passes through. This is numerical proof that the two layers are really separate, and it yields a design conclusion: the cost of remote transport cannot be reduced by cutting from the token budget, because it has nothing to do with the token budget. What can be reduced is the request count.
The last line is a loss. The kayit server is dropped by version skew in all three setups and
gains no tool; even so, transport cost is paid for the handshake: 1 round trip local, 2
remote. In the all-remote setup, two of the handshake’s six round trips go to this server. The
cost of a connection is paid whether or not that connection turns out to be useful.
As the Workload Grows
A single-subscriber chain is a narrow measure. The same measure is also taken on a chain repeated for twelve subscribers after the same handshake: as the batch grows, the fixed share of the handshake melts away and only the transport cost of the calls remains.
BATCH = [f"A{1 + 5 * i:03d}" for i in range(12)] print("subscribers local: requests rt mixed: requests rt remote: requests rt" " remote/local rt difference") for k in (1, 4, 12): row, rt = [], {} for label, force in FORMS: _, _, tot = with_transport(force, BATCH[:k]) row += [tot["request"], tot["round_trips"]] rt[label] = tot["round_trips"] print(f"{k:>11} {row[0]:>15} {row[1]:>2} {row[2]:>15} {row[3]:>2}" f" {row[4]:>16} {row[5]:>2} {rt['all remote'] - rt['all local']:>26}") local_res, _, local_cnt = with_transport("local", BATCH) remote_res, _, remote_cnt = with_transport("remote", BATCH) print(f"twelve subscribers : results same {local_res == remote_res} ; envelope tokens" f" local {local_cnt['tokens']} remote {remote_cnt['tokens']} ;" f" round trips {local_cnt['round_trips']} and {remote_cnt['round_trips']}")
subscribers local: requests rt mixed: requests rt remote: requests rt remote/local rt difference
1 6 6 6 9 6 12 6
4 15 15 15 21 15 30 15
12 39 39 39 53 39 78 39
twelve subscribers : results same True ; envelope tokens local 1059 remote 1059 ; round trips 39 and 78
The request column stays the same across the three setups: 6 for one subscriber, 15 for four, 39 for twelve. The round trip column equals the requests in local, and exactly double in remote. The last column reduces this relationship to one number: the difference between remote and local is always equal to the request count. The mixed setup stays between the two and moves toward the remote side as it grows, because every chain in the batch contains a remote call.
The twelve-subscriber batch gives the same results too, and the envelope token count is 1059 in both setups. The result lists of the twelve subscribers are identical in local and remote; there is no nondeterminism in the simulator and the counts do not depend on the run. The transport layer determines not what the work is but how many times it goes back and forth.
The split between the fixed share and the variable share can be read from this table as well. In the all-remote setup the handshake costs 6 round trips and stays at 6 however large the batch grows; the chains’ share is 6 for one subscriber, 24 for four and 72 for twelve. With a single subscriber half of the total goes to establishing the connection, with twelve one thirteenth. In the local setup the handshake stays fixed at 3 and the chain share rises from 3 to 36; the shape of the split is the same, its scale half. The handshake is a cost paid once, and the longer the session is used, the smaller its share per call; re-establishing the session for every job makes that share be paid again each time.
The engineering decision that follows is about reducing round trips, and it has two ways. The first is to bring the server local; this makes that server unreachable for clients beyond the network and gives portability back. The second is to lower the request count; the requests staying at 39 in the twelve-subscriber batch comes from the handshake being made once. The fixed price paid per session gets cheaper the longer the session is used.
Summary
- The protocol is split into two layers: the data layer says what the message is, the transport layer says how it is carried; in local transport the server is on the same machine, in remote transport it is across the network.
- The same three-tool chain gives 844 with 6 requests, 6 responses and 179 envelope tokens in all three transport setups; the only thing that changes is the round trip: 6 all local, 9 mixed, 12 all remote.
- The envelope’s token cost is independent of transport; the cost of remote transport cannot be reduced by cutting from the token budget, what must be reduced is the request count.
- In the twelve-subscriber batch requests stay fixed at 39 while round trips go from 39 to 78, and the difference is exactly the request count at every scale.
- The
kayitserver dropped by version skew gains no tool yet uses 1 round trip local and 2 remote for the handshake. - Moving a tool beyond the network makes it shareable and the same move doubles the round trips; the gain and the cost come out of the same decision.
Next Step
In three lessons how the connection is established was measured: roles were named, responsibilities distributed, the price of transport counted. Throughout the messages the same two kinds were used, a request went and a response came, and whether anything existed outside this pair was never asked. Yet a message that awaits a response in the handshake and a message that only tells the other side a status are not the same thing; the second is responseless, halves the round trip and makes something invisible in return. The next lesson looks inside the envelope: it counts the separate cost of the request, response and notification kinds and measures what a response-less message gains and what it hides.
To keep your progress and take notes, Log in
My notes
Log in to take notes.