Skip to content
academia.sh

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 (olcum local, tarife and kayit remote) and all remote. The version check is independent of transport; kayit is 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 kayit server 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.

Start typing to search.

↑↓ ↵ Esc navigate · open · close