Skip to main content

Lesson 17: Sockets, TCP & UDP

  • Module 9: Networking Foundations
  • Lesson 17 of 27
  • โฑ๏ธ About 2 h (instruction + lab)

Every online match you have played, from a two-player card game to a sixty-player shooter, is a handful of computers passing small messages back and forth very quickly. In this lesson you learn how those messages travel, why they arrive late, out of order or not at all, and you write your first TCP and UDP servers in plain Python.

๐ŸŽฏ Learning Objectives

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

  • Compare client-server and full-mesh peer-to-peer topologies by counting their connections and messages as the player count grows.
  • Explain latency, round-trip time, bandwidth, jitter and packet loss, and say which one makes a game "feel laggy".
  • Choose between TCP and UDP for a given kind of game message, and explain head-of-line blocking.
  • Build a TCP echo server and a UDP echo server with Python's socket module, bound to 127.0.0.1 on a port the OS picks.
  • Debug the common socket errors: connection refused, address already in use, a timeout, and a firewall prompt.

Project: a Connection Scaling Visualizer that draws both topologies side by side and animates the messages they need for N players.

In This Lesson

๐ŸŒ How Games Talk: Topologies

Think of a group chat. Either everyone sends their message to one phone that forwards it to the rest, or everyone texts everyone else directly. Multiplayer games face the same choice, and it is called the topology of the network.

  • Client-server. One program, the server, runs the real game. Every player's program, a client, connects only to the server. A dedicated server is a machine that only runs the game; a listen server is one player's machine doing both jobs.
  • Peer-to-peer (full mesh). Every player's machine connects to every other one and they all run the game together. There is no one in charge.
  • Relay. Peers send everything through a relay machine that forwards it but does not run the game. It looks like client-server on the wire, but no one is in charge of the rules.
graph TD A["Network topologies"] --> B["Client-server"] A --> C["Peer-to-peer"] A --> D["Relay"] B --> E["Dedicated server<br/>(runs only the game)"] B --> F["Listen server<br/>(one player hosts)"] C --> G["Full mesh<br/>(everyone to everyone)"] D --> H["Forwards messages<br/>(no game rules)"]

Count the connections for N players. Client-server needs N: one per client. A full mesh needs one for every pair of players, which is N ร— (N โˆ’ 1) รท 2.

Players (N)Client-server linksFull-mesh linksMesh รท client-server
4461.5ร—
88283.5ร—
1212665.5ร—
16161207.5ร—

Links are only half the story. If every player sends one state update per tick, a full mesh moves N ร— (N โˆ’ 1) messages, and each player must upload N โˆ’ 1 copies of their own state. With a server, each player uploads one message and downloads one snapshot, so a player's traffic stays about the same as the match grows. The server's own outgoing traffic still grows quickly (N snapshots, each describing N players), but that cost lands on one machine you can choose, not on every player's home connection.

There is a second, bigger reason most action games use a server: authority. When one program owns the real game state, a player who edits their client cannot simply announce "I am standing behind you with full health". Every later lesson in this module builds on that authoritative server.

๐Ÿ’ก Why this matters

The topology decides who pays for bandwidth, who can cheat, and what happens when one player's connection is bad. Peer-to-peer can suit a two-player game with no server budget; for anything bigger or competitive, you will almost always start from client-server.

โฑ๏ธ Latency, Bandwidth, Jitter and Loss

Picture the postal service. How long a letter takes to arrive is latency. How many letters fit through the mail slot per day is bandwidth. Letters that take a different amount of time each day are jitter, and letters that never arrive are packet loss. A network has all four, and each one hurts a game differently.

TermWhat it measuresWhat players notice
Latency (one-way)Time for one message to travel from sender to receiverEverything you see is a little old
Round-trip time (RTT, "ping")Time for a message to go there and for the answer to come backThe delay between pressing a key and seeing the server's answer
BandwidthHow much data per second the connection can carryStutter or dropped updates only when a game sends too much
JitterHow much the latency changes from message to messageMovement that speeds up and slows down
Packet lossThe share of messages that never arriveRubber-banding, missing sounds or actions

A few rules of thumb that hold everywhere:

  • Bandwidth does not fix latency. A faster plan lets you send more data per second; it does not make one message arrive sooner. A player with a 100 Mbps line and a 200 ms ping still waits 200 ms for every answer.
  • You usually measure RTT, not one-way latency. A client can time a message out and back with its own clock. One-way latency needs two synchronized clocks, so games often estimate it as RTT รท 2, which is only right when both directions are equally fast.
  • Latency can be hidden, not removed. Nothing travels faster than the physics of the cable allows. The techniques in the Client Prediction & Reconciliation and Lag Compensation lessons exist because of this.
  • Game time is counted in ticks. A server that updates 30 times a second has a tick rate of 30 Hz, so one tick is 1/30 s. Later lessons measure delays in ticks.

๐Ÿ”Œ Sockets, Addresses and Ports

A socket is one end of a network conversation, like one telephone handset. To reach one you need two things:

  • an IP address, the "street address" of a computer, such as 127.0.0.1;
  • a port, the "apartment number" of one program on that computer, a number from 0 to 65535.

In Python, an address is the tuple (host, port). Two addresses matter for this module:

  • "127.0.0.1" is the loopback address: "this computer". Messages sent to it never leave your machine, so there is no firewall prompt and no one else can connect. Every lab in this module uses it and runs the server and its clients as threads in one program.
  • Port 0 means "any free port". bind(("127.0.0.1", 0)) lets the operating system pick an unused port, and getsockname() tells you which one it chose, so two programs never fight over the same number.

A server binds its socket to an address and waits. A client connects to the server's address. When you are ready to play with a friend on your home network, the server binds to its LAN address (or to "0.0.0.0", meaning "every network interface"), and the operating system's firewall asks whether to allow it. Only do that on a network you trust.

โœ… Growth Mindset: Networking Feels Invisible, Until You Print It

Networking code is harder to debug than drawing code because you can't see the messages. That is a skill, not a talent, and it grows fast once you make the invisible visible: print every message you send and receive with the time it happened, and run the server and client in one program first, like the labs do. If your first server hangs or refuses connections, you haven't failed; you've found the exact spot where the two sides disagree. Every network programmer builds their intuition this way.

๐Ÿ“ฆ TCP or UDP?

Python sockets speak one of two transport protocols. They make opposite promises.

TCP (SOCK_STREAM)UDP (SOCK_DGRAM)
ConnectionYes: connect() first, then talkNo: send each datagram to any address
DeliveryEvery byte arrives, or the connection reports an errorA datagram may be lost, and no one tells you
OrderBytes arrive in the order they were sentDatagrams can arrive out of order
Message boundariesNone: a stream of bytesEach recvfrom() returns one whole datagram
Good forLogin, chat, lobbies, trades, turn-based moves, final scoresFast-changing state such as positions, sent many times a second

TCP's promise has a cost called head-of-line blocking. If one piece of data is lost, TCP re-sends it after a timeout, and every piece that arrived after it waits, because TCP must hand your program the bytes in order. For a chat message that is exactly right. For a player's position it is wasteful: by the time the old position is re-sent, three newer ones are already sitting in the buffer, and the newest one is the only one you want.

Try it below. "Drop next packet" loses the same update on both connections. Watch TCP hold every later update until the re-sent one arrives, while UDP delivers the next update the moment it lands.

The demo's timings are a teaching model; real TCP adjusts its timeout from the round-trip times it measures. The behavior it shows, in-order delivery that waits for the missing piece, is exactly what TCP promises.

Many real-time games therefore send position updates over UDP and add only the reliability they need on top (a sequence number to drop stale updates, re-sending only the messages that must arrive). In this module you use TCP for everything that must arrive and simulate UDP-like delay where it keeps the code short; the optional reading State Sync & Bandwidth shows a small reliability layer.

๐Ÿ” Your First TCP Echo Server

An echo server sends back whatever it receives. It is the "hello world" of networking because it tests the whole round trip. This complete program runs the server in a background thread and the client in the main thread, so one terminal is enough.

import socket
import threading
import time

HOST = "127.0.0.1"                      # loopback: this computer only


def echo_server(listener):
    conn, addr = listener.accept()      # wait for ONE client
    with conn:
        print("server: client connected from", addr)
        while True:
            data = conn.recv(4096)      # up to 4096 bytes of whatever has arrived
            if not data:                # b"" means the client closed its side
                break
            conn.sendall(data)          # send every byte back
    print("server: client left")


listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM)   # a TCP socket
listener.bind((HOST, 0))                # port 0: the OS picks any free port
listener.listen()
port = listener.getsockname()[1]
print("server: listening on port", port)
server = threading.Thread(target=echo_server, args=(listener,))
server.start()

with socket.create_connection((HOST, port), timeout=5) as client:
    for word in ["hello", "jump", "fire"]:
        payload = word.encode("utf-8")  # sockets send bytes, not str
        start = time.perf_counter()
        client.sendall(payload)
        reply = b""
        while len(reply) < len(payload):        # the echo may come back in pieces
            chunk = client.recv(4096)
            if not chunk:
                break
            reply += chunk
        ms = (time.perf_counter() - start) * 1000
        print(f"client: {reply.decode('utf-8')!r} back after {ms:.3f} ms")

server.join()
listener.close()

The key calls, in the order they happen:

  • socket.socket(AF_INET, SOCK_STREAM) makes an IPv4 TCP socket. bind() gives it an address, and listen() tells the OS to queue incoming connections.
  • accept() blocks (waits) until a client connects, then returns a new socket for that one client plus its address. The listening socket keeps waiting for others.
  • socket.create_connection((host, port), timeout=5) is the client side: it creates a socket and connects it. The timeout makes any later call that waits more than 5 seconds raise socket.timeout instead of hanging forever.
  • sendall(data) keeps sending until every byte is handed to the OS. Plain send() may send only part of the data and return how much it sent, so prefer sendall().
  • recv(4096) returns between 1 and 4096 bytes of whatever has arrived, or b"" when the other side has closed the connection. That is why the client loops until it has the whole echo.
  • threading.Thread(target=echo_server, args=(listener,)) runs echo_server(listener) alongside the rest of the program once you call start(); join() waits for it to finish. That is how one program can be both server and client. Threads get their own section, with locks, in the next lesson.

Run it a few times and read the times it prints. That is your loopback round-trip time: no cables involved, only the operating system passing bytes between two threads.

๐Ÿ”ฎ Predict, then run

What happens if you remove the while len(reply) < len(payload) loop and call client.recv(4096) once? On loopback, with five-byte messages, you will probably see no difference, which is exactly what makes the bug dangerous. TCP only promises the bytes arrive in order, not how many arrive per recv(). The next lesson, Framing & Concurrency, gives every message a clear boundary.

โšก UDP: Datagrams Without a Connection

A UDP socket has no listen(), accept() or connect(). Each sendto(data, address) is one independent datagram, and each recvfrom() returns one whole datagram plus the address it came from.

import socket
import threading

HOST = "127.0.0.1"


def udp_echo(sock):
    while True:
        data, addr = sock.recvfrom(2048)    # one whole datagram, plus who sent it
        if data == b"quit":
            break
        sock.sendto(data, addr)             # reply to that address


server_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)   # a UDP socket
server_sock.bind((HOST, 0))
server_addr = server_sock.getsockname()
server = threading.Thread(target=udp_echo, args=(server_sock,))
server.start()

client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)       # no connect() needed
client.settimeout(1.0)                      # never wait forever for a datagram
for n in range(1, 4):
    client.sendto(f"ping {n}".encode("utf-8"), server_addr)
    try:
        reply, _ = client.recvfrom(2048)
        print("client got", reply.decode("utf-8"))
    except socket.timeout:
        print(f"ping {n} was lost")         # on a real network, this happens

client.sendto(b"quit", server_addr)
server.join()
client.close()
server_sock.close()
  • Because nothing tells you a datagram was lost, the client sets a timeout and treats "no answer in time" as a loss. On loopback nothing gets lost; on Wi-Fi or the internet, it will.
  • One thread reads all datagrams in a loop. A UDP server does not need a thread per client or per datagram: every datagram carries its sender's address, so one loop can serve everyone.
  • Keep datagrams small. A datagram bigger than the path can carry in one piece may be split or dropped along the way, so keep each game update to a few hundred bytes.

๐Ÿงฐ Common Socket Errors

You seeLikely causeFix
ConnectionRefusedErrorNothing is listening at that host and port: the server isn't running yet, or the port is wrongStart the server first; print the port it actually bound to
OSError: [Errno 98] Address already in use (WinError 10048 on Windows)Another program, or your last run, still holds that portBind to port 0 in tests; for a fixed port, set SO_REUSEADDR before bind()
socket.timeout / TimeoutErrorNo data arrived in time: the other side is stuck, gone, or the datagram was lostCatch it and decide: retry, report a loss, or disconnect
A firewall prompt, or friends can't connectThe server bound to a real network interfaceUse 127.0.0.1 while developing; allow the program only on private networks
Program hangs on recv() or accept()Blocking calls wait forever by defaultSet a timeout, or read on a separate thread (next lesson)
OSError on recv() right after creating a socketA TCP socket must be connected (or accepted) before it can receiveCall connect() first, or use the socket accept() returned

To reuse a fixed port right after a restart, set the option before binding:

listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)   # must come before bind()
listener.bind(("127.0.0.1", 50007))

If you ever wait on several sockets with select.select(), pass the timeout as the fourth positional argument: select.select(readers, [], [], 0.1). It has no timeout= keyword, so writing one raises TypeError. The next lesson uses the friendlier selectors module instead.

โœ… Growth Mindset: Read the Error Name First

Socket errors look scary because they come from the operating system, with numbers like Errno 98. You don't need to memorize them. The exception's name (refused, in use, timeout) already tells you which side is wrong, and the table above covers most of what you will meet. When one surprises you, search the exact name plus "Python socket", fix it, and add a row to your own notes. That table is how experienced developers got fast.

๐Ÿ‹๏ธ Practice Exercise: Connection Scaling Visualizer

Objective: draw client-server and full-mesh networks side by side for the same number of players, animate the messages each one needs per update, and show the counts on screen as you change the player count.

Time: about 35 minutes. Starter file: topology_starter.py (your instructor has it). Warm up first with first_echo_starter.py, which asks you to finish the client side of the TCP and UDP echo programs above.

  1. Run the starter. You see the server, the clients and the peers, but no mesh lines and the HUD says 0 links. (โ‰ˆ 2 min)
  2. Make cs_links(n) and mesh_links(n) return the counts from the table in How Games Talk (starter comments 1 and 2). (โ‰ˆ 5 min)
  3. Draw one line for every pair of peers, starting the inner loop at i + 1 so each pair is drawn once (comment 3). (โ‰ˆ 5 min)
  4. In make_burst(), add one message from every peer to every other peer (comment 4). (โ‰ˆ 8 min)
  5. Make - and + (or =) change N between 2 and 12 (comment 5). (โ‰ˆ 5 min)
  6. Set N to 4, 8 and 12 and write down both link counts and both message counts. (โ‰ˆ 5 min)

You are done when:

  • the HUD shows 4 / 6, 8 / 28 and 12 / 66 links at N = 4, 8 and 12;
  • each update sends 2N messages in the client-server panel and N ร— (N โˆ’ 1) in the mesh panel;
  • the yellow dots move at the same speed at any frame rate (they advance by dt);
  • closing the window prints the final N with both counts.
๐Ÿ’ก Hint

For the mesh messages, a nested loop over i and j with if i != j gives every ordered pair: peer 1 to peer 2 and peer 2 to peer 1, because each peer sends its own state. For the lines, you want each unordered pair once, so start j at i + 1 instead. start.lerp(end, t) gives the point a fraction t of the way along a link.

โœ… Example Solution

If your instructor hands you the lab file, you will see a few extra lines marked lab runtime near the top and and frame_budget() in the loop. They let the instructor's checker run the program automatically for a fixed number of frames; when you run it yourself they do nothing.

"""Connection Scaling Visualizer: Advanced Lesson 17 practice exercise (solution).

Left panel: client-server (one server, N clients). Right panel: full mesh
(every peer linked to every other peer). Every 1.5 seconds each player sends
one state update to everyone who needs it; the yellow dots are those messages.
Press - and + (or =) to change the number of players N from 2 to 12.
"""
import pygame


WIDTH, HEIGHT = 900, 520
MIN_N, MAX_N = 2, 12
BURST_EVERY = 1.5              # seconds between state updates
TRAVEL_TIME = 0.8              # seconds a message takes to cross its link on screen
CS_CENTER = pygame.Vector2(225, 230)
MESH_CENTER = pygame.Vector2(675, 230)
RING_RADIUS = 150

BG = (15, 18, 30)
SERVER_COLOR = (240, 90, 90)
CLIENT_COLOR = (90, 160, 240)
PEER_COLOR = (220, 110, 210)
MESSAGE_COLOR = (255, 230, 90)
TEXT_COLOR = (225, 225, 235)


def cs_links(n):
    """Client-server: every client has one link, to the server."""
    return n


def mesh_links(n):
    """Full mesh: one link for every distinct pair of peers."""
    return n * (n - 1) // 2


def ring(center, radius, n):
    """n points evenly spaced on a circle, the first one at the top."""
    return [center + pygame.Vector2(0, -radius).rotate(360 * i / n) for i in range(n)]


def make_burst(n):
    """One state update from every player, as (start, end) message pairs per topology.

    Client-server: each client uploads to the server, then the server sends one
    snapshot down to each client (2N messages). Full mesh: each peer sends its
    state straight to every other peer (N * (N - 1) messages).
    """
    server = CS_CENTER
    clients = ring(CS_CENTER, RING_RADIUS, n)
    peers = ring(MESH_CENTER, RING_RADIUS, n)
    cs = [(c, server) for c in clients] + [(server, c) for c in clients]
    mesh = [(peers[i], peers[j]) for i in range(n) for j in range(n) if i != j]
    return cs, mesh


def advance(messages, dt):
    """Move every message along its link; drop the ones that arrived."""
    for m in messages:
        m[2] += dt / TRAVEL_TIME
    return [m for m in messages if m[2] < 1.0]


def draw_topologies(screen, n, cs_msgs, mesh_msgs):
    clients = ring(CS_CENTER, RING_RADIUS, n)
    peers = ring(MESH_CENTER, RING_RADIUS, n)
    for c in clients:
        pygame.draw.line(screen, CLIENT_COLOR, CS_CENTER, c, 2)
    for i in range(n):
        for j in range(i + 1, n):
            pygame.draw.line(screen, PEER_COLOR, peers[i], peers[j], 1)
    for start, end, t in cs_msgs + mesh_msgs:
        pygame.draw.circle(screen, MESSAGE_COLOR, start.lerp(end, t), 4)
    pygame.draw.circle(screen, SERVER_COLOR, CS_CENTER, 16)
    for c in clients:
        pygame.draw.circle(screen, CLIENT_COLOR, c, 10)
    for p in peers:
        pygame.draw.circle(screen, PEER_COLOR, p, 10)


def main():
    pygame.init()
    screen = pygame.display.set_mode((WIDTH, HEIGHT))
    pygame.display.set_caption("Connection Scaling: client-server vs full mesh")
    clock = pygame.time.Clock()
    font = pygame.font.Font(None, 26)           # fonts are created once, before the loop
    title_font = pygame.font.Font(None, 30)

    n = 4
    cs_msgs, mesh_msgs = [], []
    burst_timer = 0.0                           # counts seconds up to BURST_EVERY

    running = True
    while running:
        dt = clock.tick(60) / 1000
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
            elif event.type == pygame.KEYDOWN:
                old_n = n
                if event.key in (pygame.K_MINUS, pygame.K_KP_MINUS):
                    n = max(MIN_N, n - 1)
                elif event.key in (pygame.K_EQUALS, pygame.K_PLUS, pygame.K_KP_PLUS):
                    n = min(MAX_N, n + 1)
                if n != old_n:                  # the layout moved: old messages are stale
                    cs_msgs, mesh_msgs = [], []

        burst_timer += dt
        if burst_timer >= BURST_EVERY:
            burst_timer -= BURST_EVERY
            cs, mesh = make_burst(n)
            cs_msgs += [[a, b, 0.0] for a, b in cs]
            mesh_msgs += [[a, b, 0.0] for a, b in mesh]
        cs_msgs = advance(cs_msgs, dt)
        mesh_msgs = advance(mesh_msgs, dt)

        screen.fill(BG)
        draw_topologies(screen, n, cs_msgs, mesh_msgs)
        screen.blit(title_font.render("Client-server", True, TEXT_COLOR), (160, 30))
        screen.blit(title_font.render("Full mesh (peer-to-peer)", True, TEXT_COLOR), (560, 30))
        hud = [
            f"N = {n} players   (- / + to change)",
            f"Links:    client-server {cs_links(n):3d}    full mesh {mesh_links(n):3d}"
            f"    ratio {(n - 1) / 2:.1f}x",
            f"Messages per update:  client-server {2 * n:3d}    full mesh {n * (n - 1):3d}",
        ]
        for i, line in enumerate(hud):
            screen.blit(font.render(line, True, TEXT_COLOR), (20, HEIGHT - 90 + i * 26))
        pygame.display.flip()

    pygame.quit()
    print(f"Final N = {n}: client-server {cs_links(n)} links / {2 * n} messages, "
          f"full mesh {mesh_links(n)} links / {n * (n - 1)} messages")


if __name__ == "__main__":
    main()

๐Ÿ““ Learning Journal

Take five minutes to write in your learning journal (a notebook or a plain text file works). Jot down:

  • Key concepts you learned today
  • Techniques that clicked (and the ones that haven't, yet)
  • Questions or confusion to bring to the next session
  • Ideas to try in your own game
  • Progress and feelings: how did this lesson go for you?

โœ๏ธ This lesson's prompts:

  1. Pick a multiplayer game you know. Which messages in it must arrive (TCP-style), and which are only useful if they arrive quickly (UDP-style)?
  2. Explain to a friend, without jargon, why a faster internet plan does not lower their ping.
  3. Your visualizer showed the mesh growing much faster than client-server. At what player count would you stop considering peer-to-peer for your own game, and why?

๐Ÿ“ Summary

Multiplayer games are messages between machines. You compared the two main ways to connect players: client-server, where links grow with N and one program has authority, and a full mesh, where links grow with N ร— (N โˆ’ 1) รท 2 and every player uploads to every other. You met the four enemies of a smooth game (latency, low bandwidth, jitter and loss) and learned that latency can only be hidden. Then you opened real sockets: a TCP echo server that streams bytes reliably and in order, and a UDP echo server that trades those promises for speed.

๐ŸŽ“ Key Takeaways

  • Client-server needs N links and gives one program authority; a full mesh needs N ร— (N โˆ’ 1) รท 2 links.
  • Ping is round-trip time. Bandwidth is capacity, and more of it does not make a message arrive sooner.
  • An address is (host, port); 127.0.0.1 is this computer, and port 0 lets the OS pick a free port.
  • TCP is a reliable, ordered byte stream with no message boundaries; UDP sends independent datagrams that may be lost or reordered.
  • Use sendall(), loop on recv(), treat b"" as "closed", and set timeouts so nothing waits forever.

๐Ÿ”ญ Looking Ahead

Your echo server trusted recv() to return whole messages and could only serve one client. In the next lesson, Framing & Concurrency, you give every message a length header, serve many clients at once with threads and locks, and meet selectors, the single-threaded alternative.

โ“ Common Questions

Why 127.0.0.1 and not localhost?

Both mean "this computer", but localhost is a name that must be looked up first, and on some systems it resolves to the IPv6 address ::1 while your server listens on IPv4. Writing 127.0.0.1 removes that surprise.

Do I need port forwarding or a public server to follow this module?

No. Every example and lab runs the server and its clients in one program on the loopback address. Playing across the internet needs a reachable server (a rented machine, or port forwarding on a home router), which is a deployment step you can take later.

Can I make an action game with only TCP?

Yes, and many small and hobby games do. On a good connection the difference is small. The cost appears with packet loss, when head-of-line blocking holds newer updates behind a lost one. Start with TCP, measure, and switch the fast-changing traffic to UDP only if you need to.

Why does my program hang instead of printing an error?

accept(), recv() and recvfrom() wait forever by default. Pass timeout= to create_connection() or call sock.settimeout(seconds), and a stuck call raises socket.timeout that you can catch and print.

What about WebSockets or asyncio?

WebSockets are a message protocol built on TCP, mainly used so browser games can connect. asyncio is another way to wait on many sockets in one thread. Both build on the same ideas as this module; once framing and threads make sense, their documentation will read easily.

๐ŸŽฏ Quick Quiz

Question 1: A 16-player match could use one server or a full mesh. How many links does the full mesh need?

Question 2: A TCP server's conn.recv(4096) returns b"". What does that mean?

Question 3: A player has a 100 Mbps connection and a 200 ms ping, and the game feels laggy. What is the main problem?

Question 4: Which statement about UDP is true?

Question 5: Why do the labs call bind(("127.0.0.1", 0))?

๐ŸŒŸ Going Further

  • Loopback ping chart: send 200 UDP pings in the echo program and print the smallest, median and largest round-trip time. Then run a CPU-heavy program at the same time and compare. Jitter appears even without a network.
  • Bytes per second: add a HUD line to your visualizer that multiplies the messages per update by a 100-byte update and an update rate of 20 per second, for each topology.
  • Two terminals: split the TCP echo program into a server script and a client script, and connect them on your own machine. Then try it between two computers on a network you trust.
  • Read the docs: the Python Socket Programming HOWTO and the socket module reference.
  • Coming up in Game Dev III: Advanced: Client Prediction & Reconciliation and Lag Compensation hide latency from the player; the Lobby Server & Client lesson turns a TCP server into a real game lobby.