Build a TCP Message Inspector

Build a Python tool that inspects TCP byte chunks and framed messages.

Introduction

30 Second Summary

A message can look complete on your screen even when a computer moves it in smaller pieces. Those pieces need clear boundaries before another program can rebuild the original text.

In this project, you will build a local TCP message inspector with Python. You will watch raw byte chunks become a complete message as text travels between a client and server.

What You'll Build

A pair of terminals will show your message crossing a real local connection as raw bytes before the complete text echoes back.

By the end of this project, you'll have:

  • A working message exchange that sends text from a local client to a server before echoing it back.
  • A reproducible partial-read bug that reveals why one receive call cannot guarantee a complete message.
  • A length-prefixed message inspector that rebuilds long messages while displaying each chunk as bytes and hexadecimal output.
  • Secret Mission: Add a three-second timeout that lets the client exit cleanly when the server stalls.

Are there any prerequisites?

Basic familiarity with variables, functions, loops, and terminal commands is enough. Python 3.14 and Visual Studio Code are already available in your learner environment.

Before We Start

This checkpoint gives you a clear reason for building a local TCP message inspector before the hands-on work begins. Your connection to raw bytes and message boundaries gives the project a purpose you can return to.

Set Up the Local Workspace

Your inspector will run a client process beside a server process. Both use Python to reveal what crosses a TCP connection.

Keeping both processes in one verified workspace removes environment uncertainty. A version check confirms the interpreter before any socket starts.

In this step, get ready to:
  • Create the tcp-message-inspector workspace folder through Finder.
  • Prepare blank server.py and client.py role files in Visual Studio Code.
  • Confirm Python 3.14.x in the integrated terminal.
Create the workspace folder

A workspace folder gives Visual Studio Code one location to display in its Explorer. It also gives both future processes the same starting directory.

  • Click the Finder icon in the Dock.
  • Select Desktop in the Finder sidebar.
  • Click File in the macOS menu bar.
  • Click New Folder.
  • Type tcp-message-inspector as the folder name.
  • Press Return to create the folder.

You’ll see tcp-message-inspector on your Desktop.

Why One Folder?

The folder keeps the client beside the server. Both terminals can run from the same location without extra navigation.

  • Press Cmd+Space to open macOS search.
  • Type Visual Studio Code into the search field.
  • Press Return to open Visual Studio Code.
  • Click File in the top menu bar.
  • Click Open Folder.
  • Select tcp-message-inspector from your Desktop.
  • Click Open.

You’ll see tcp-message-inspector at the top of the Explorer sidebar. This confirms that Visual Studio Code opened the correct workspace.

Create the role files

The inspector uses one file for each side of the connection. Both files stay empty during this setup step.

  • Click the New File icon beside tcp-message-inspector in the Explorer sidebar.
  • Type server.py as the file name.
  • Press Return to create the file.
  • Click the New File icon beside tcp-message-inspector again.
  • Type client.py as the file name.
  • Press Return to create the file.

You’ll see server.py and client.py under the workspace name in Explorer.

Keep Both Files Blank

Each empty file reserves one process role. server.py accepts the future connection. client.py initiates it.

At this checkpoint, both files should contain zero lines of code.

✔️ Awesome, I've got everything!

Your Explorer lists server.py and client.py. Both files are empty.

ⓧ I'd like to double check the full code

The complete contents of client.py are empty. The complete contents of server.py are also empty.

Verify Python 3.14

The integrated terminal runs commands from the opened workspace. Its version output confirms that the expected interpreter is available.

  • Click Terminal in the top menu bar.
  • Click New Terminal.

Before you run the check, what Python version do you expect the integrated terminal to report?

  • Check the installed Python version by running this command:
python3 --version

What Does This Command Do?

  • The python3 command selects the Python 3 executable available in the terminal.
  • The --version option asks the executable to report its installed version.

You’ll see output in the form Python 3.14.x. The terminal prompt returns immediately because no server or client process is running.

Version Check Looks Wrong?

  • Check that your command matches the code block exactly.
  • Click Terminal in the top menu bar.
  • Select New Terminal to create a fresh integrated terminal.

Help me diagnose my Python version check.

Your workspace is ready for two local processes. Next, you’ll send a short message across their first TCP connection.

Make a Short Message Round Trip

Your workspace is ready for its first real exchange. A TCP socket lets two separate Python processes communicate through a local network connection.

In this step, you will send a short message from a client to a server. The echoed reply proves that the address, port, UTF-8 encoding, and connection lifecycle work together.

In this step, get ready to:
  • Build a local TCP server that accepts one connection.
  • Build a client that sends UTF-8 bytes to the server.
  • Run both programs and echo a short message.
Build the echo server

The server owns the listening side of the connection. It waits at a local IPv4 address until one client arrives.

Why raw blocking sockets?

Raw blocking sockets expose every stage of the server lifecycle. You can see the server bind, listen, accept, receive, and send.

The socketserver module hides parts of that setup. The asyncio module introduces an event loop before you need one.

  • Select server.py in the Explorer sidebar.
  • Replace the empty file with the code below:
import socket

HOST = "127.0.0.1"
PORT = 50007


def run_server():
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server:
        server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        server.bind((HOST, PORT))
        server.listen()
        print(f"Server listening on {HOST}:{PORT}")

        connection, address = server.accept()
        with connection:
            print(f"Connected by {address}")
            received_bytes = connection.recv(8)
            print(f"Received bytes: {received_bytes!r}")
            connection.sendall(received_bytes)
            print("Echo sent. Server closing.")


if __name__ == "__main__":
    run_server()

How does the server work?

  • The HOST value keeps the connection on your Mac.
  • The PORT value identifies the local endpoint used by both programs.
  • The socket.AF_INET value selects IPv4 addressing.
  • The socket.SOCK_STREAM value creates a TCP stream socket.
  • The socket.SO_REUSEADDR option makes rapid local reruns more reliable.
  • The accept() call waits for one client connection.
  • The recv(8) call receives up to eight bytes.
  • The sendall() call echoes the received bytes.
  • Save server.py.
  • Start the server in the integrated terminal from earlier by running this command:
python3 server.py

What should you see?

You should see Server listening on 127.0.0.1:50007.

The terminal stays occupied because accept() is waiting for the client you build next.

Server does not stay waiting?

  • Confirm that the integrated terminal is inside the tcp-message-inspector workspace.
  • Compare the indentation in server.py with the full reference below.
  • Check that run_server() appears beneath the entry guard.

Help me debug why my TCP server does not stay ready for a connection.

✔️ Awesome, I've got everything!

Your server is saved and waiting for its first client.

ⓧ I'd like to double check the full code

import socket

HOST = "127.0.0.1"
PORT = 50007


def run_server():
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server:
        server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        server.bind((HOST, PORT))
        server.listen()
        print(f"Server listening on {HOST}:{PORT}")

        connection, address = server.accept()
        with connection:
            print(f"Connected by {address}")
            received_bytes = connection.recv(8)
            print(f"Received bytes: {received_bytes!r}")
            connection.sendall(received_bytes)
            print("Echo sent. Server closing.")


if __name__ == "__main__":
    run_server()

This reference contains the complete one-shot server for this step.

Build the echo client

The client starts the exchange. It converts your text into bytes before sending those bytes to the waiting server.

  • Select client.py in the Explorer sidebar.
  • Replace the empty file with the code below:
import socket

HOST = "127.0.0.1"
PORT = 50007


def run_client():
    message = input("Message to send: ")
    message_bytes = message.encode("utf-8")

    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client:
        client.connect((HOST, PORT))
        client.sendall(message_bytes)
        echoed_bytes = client.recv(8)

    print(f"Echo received: {echoed_bytes.decode('utf-8')}")


if __name__ == "__main__":
    run_client()

How does the client work?

  • The input() call collects the message as a Python string.
  • The encode("utf-8") call converts that string into bytes.
  • The client socket uses the same address family as the server.
  • The connect() call reaches the server at the shared address.
  • The first sendall() call sends the message bytes.
  • The recv(8) call waits for up to eight echoed bytes.
  • The decode('utf-8') call turns the echoed bytes back into readable text.
  • Save client.py.
  • Check that the unsaved dot has disappeared from the client.py tab.

✔️ Awesome, I've got everything!

Your client is saved and ready to connect to the waiting server.

ⓧ I'd like to double check the full code

import socket

HOST = "127.0.0.1"
PORT = 50007


def run_client():
    message = input("Message to send: ")
    message_bytes = message.encode("utf-8")

    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client:
        client.connect((HOST, PORT))
        client.sendall(message_bytes)
        echoed_bytes = client.recv(8)

    print(f"Echo received: {echoed_bytes.decode('utf-8')}")


if __name__ == "__main__":
    run_client()

This reference contains the complete one-shot client for this step.

Client file missing something?

  • Compare the spacing around run_client() with the full reference above.
  • Confirm that HOST matches the value in server.py.
  • Confirm that PORT matches the value in server.py.

Help me compare my TCP client with the expected client for this step.

Run the short message round trip

Your first terminal is waiting inside accept(). A second integrated terminal lets the client run while the server remains available.

What does blocking mean?

A blocking call pauses the current program until its network operation can continue. The server moves past accept() when your client connects.

  • Keep the original server terminal running.
  • Click the + icon in the terminal panel to create a second integrated terminal.
  • Confirm that the new terminal prompt is inside tcp-message-inspector.

Before you run the client, take a moment to predict which terminal will print first after you enter hello.

  • Start the client in the second integrated terminal by running this command:
python3 client.py

What happens next?

Python runs client.py. The program pauses at Message to send: because input() is waiting for your text.

  • Type hello after the message prompt.
  • Press Enter.

The client terminal prints Echo received: hello. The server terminal shows a line beginning with Connected by.

The server also prints Received bytes: b'hello'. Its final line is Echo sent. Server closing..

That is your first socket round trip complete. Both one-shot programs have exited after exchanging the message.

Message did not make the round trip?

  • Return to the server terminal if the client cannot connect.
  • Restart server.py before retrying the client.
  • Enter hello exactly when the client displays its prompt.

Help me debug why my local TCP echo client and server did not complete the round trip.

Your client and server can now exchange a short message. Next, you will test what happens when the message grows beyond a single eight-byte read.

Break the One-Read Assumption

Your short hello round trip proved that the client can exchange data with the server over TCP. The message fit inside the eight-byte receive limit.

That success left a hidden assumption untested. This step puts the assumption under pressure with a longer message while keeping both programs unchanged.

In this step, get ready to:
  • Restart the unchanged one-shot programs.
  • Send a message longer than the eight-byte receive limit.
  • Compare the sent text with the truncated echo.
Prepare the unchanged test

The first version reads from each connection once. Keeping that version intact lets you test what one receive call captures from a longer message.

  • Leave server.py unchanged for this test.
  • Leave client.py unchanged for this test.
  • Return to the first integrated terminal from earlier.
  • Start the one-shot server by running this command:
python3 server.py

What Should You See?

The server prints Server listening on 127.0.0.1:50007. It then waits for a client at server.accept().

Run the long-message test
  • Switch back to the second integrated terminal from earlier.
  • Start the client by running this command:
python3 client.py

What Should You See?

The client stops at the Message to send: prompt. This confirms that it connected to the waiting server.

  • Predict how much of the longer message one eight-byte receive call can return.
  • Enter Networking makes bytes visible at the client prompt.

The server prints Received bytes: b'Networki'. It echoes those same eight bytes.

The client prints Echo received: Networki. Both one-shot programs then exit.

That shortfall is the result you wanted. You have now made the hidden message-boundary assumption visible.

  • Compare Networking makes bytes visible with Networki.
  • Find connection.recv(8) in server.py.
  • Find client.recv(8) in client.py.

What Did the Test Prove?

A TCP socket supplies a stream of bytes. One call to recv(8) returns no more than eight bytes.

The connection.recv(8) call limits the server's read to eight bytes. The client.recv(8) call places the same limit on the echo.

The stream does not preserve your application's message boundary. Your code needs its own way to know when a complete message has arrived.

Don't See the Truncated Echo?

  • Confirm that you entered Networking makes bytes visible exactly.
  • Confirm that the server was running before you started the client.
  • Check that both receive calls still use recv(8).

Help me diagnose why my TCP test did not produce the truncated Networki echo.

The one-read limit is now visible. Next, you will build a protocol that knows when the full message has arrived.

Frame and Reassemble Complete Messages

Your last run in Visual Studio Code proved that one recv(8) call can return only the first eight bytes of a longer TCP message. The client needs a way to identify the complete payload.

Length-prefixed message framing solves that gap. A four-byte header declares the payload length. A receive loop then collects exactly that many bytes.

In this step, get ready to:
  • Build a shared module that frames each message.
  • Use the shared protocol helpers in both socket programs.
  • Repeat the long-message test to inspect the complete result.
Build the framing protocol

The shared protocol.py module gives the client and server one set of rules. Each side uses the same functions to send complete text or receive complete text.

Why a four-byte header?

The header stores the number of bytes in the payload. The receiver reads that value before collecting the message.

The header uses big-endian byte order. Both the header and payload need receive loops because TCP can split either part.

  • Create protocol.py inside the tcp-message-inspector workspace by using the new-file control in the Explorer sidebar.
  • Add the size constants and exact-byte receive loop by copying this code into protocol.py:
HEADER_SIZE = 4
BUFFER_SIZE = 8


def receive_exact(connection, byte_count):
    chunks = []
    bytes_received = 0

    while bytes_received < byte_count:
        remaining = byte_count - bytes_received
        chunk = connection.recv(min(BUFFER_SIZE, remaining))

        if not chunk:
            raise ConnectionError(
                "The connection closed before the full message arrived."
            )

        chunks.append(chunk)
        bytes_received += len(chunk)
        print(
            f"Received chunk {len(chunks)}: {chunk!r} | "
            f"hex: {chunk.hex(' ')}"
        )

    return b"".join(chunks)

How does receive_exact() work?

  • The HEADER_SIZE constant reserves four bytes for the payload length.
  • The BUFFER_SIZE constant keeps each read at eight bytes or fewer.
  • The receive_exact() loop keeps reading until bytes_received reaches byte_count.
  • The chunk.hex(' ') call exposes the hexadecimal value of every received byte.
  • The ConnectionError stops the loop if the connection closes before the requested bytes arrive.

Trace One Receive Loop

When receive_exact(connection, 28) starts, bytes_received is zero. Each pass recalculates remaining from the bytes still needed.

Appending each chunk preserves its arrival order. The final b"".join(chunks) rebuilds one complete bytes payload.

  • Save protocol.py.
  • Check the receive loop for syntax errors by running this command:
python3 -m py_compile protocol.py

What Does This Check Prove?

The py_compile module parses protocol.py as Python source. A successful check confirms that this first chunk has valid syntax.

The terminal returns to its prompt without reporting a syntax error.

Seeing a Syntax Error in the Receive Loop?

  • Check that every line inside receive_exact() uses the indentation shown above.
  • Check that both formatted strings inside print() remain inside the same pair of parentheses.

Help me find the syntax error in my receive loop.

  • Add send_message() below receive_exact() by copying this code:
def send_message(connection, message):
    payload = message.encode("utf-8")
    header = len(payload).to_bytes(HEADER_SIZE, "big")
    connection.sendall(header + payload)
    print(f"Sent header: {header!r} | payload: {payload!r}")

How Does send_message() Frame Text?

  • The payload variable holds the message after UTF-8 encoding converts it to bytes.
  • The header variable stores the payload length as four big-endian bytes.
  • The sendall() call sends the header immediately before the payload.
  • The final log exposes both parts of the framed message.
  • Save protocol.py.
  • Check the sender for syntax errors by running this command:
python3 -m py_compile protocol.py

Why Check This Chunk Now?

This second compile includes the new send_message() function. It catches malformed strings or parentheses before either socket program imports the module.

The terminal returns to its prompt without reporting a syntax error.

Sender Failing the Syntax Check?

  • Check that "utf-8" and "big" each retain both quotation marks.
  • Check that sendall() receives header + payload inside one pair of parentheses.

Help me debug the send_message function.

  • Add receive_message() below send_message() by copying this code:
def receive_message(connection):
    header = receive_exact(connection, HEADER_SIZE)
    message_length = int.from_bytes(header, "big")
    print(f"Declared payload length: {message_length} bytes")
    payload = receive_exact(connection, message_length)
    return payload.decode("utf-8")

How Does receive_message() Rebuild Text?

  • The first receive_exact() call collects the complete four-byte header.
  • The int.from_bytes() call converts that header into message_length.
  • The second receive_exact() call collects the declared payload size.
  • The final decode() call turns the complete UTF-8 payload back into text.
  • Save protocol.py.
  • Check the completed protocol module for syntax errors by running this command:
python3 -m py_compile protocol.py

What Does the Final Protocol Check Cover?

This compile parses all three protocol functions together. It confirms that the shared module is ready to be imported by server.py and client.py.

Good progress. Your shared framing module now parses without a syntax error.

Protocol Module Still Failing?

  • Check that receive_message() starts at the left edge of the file.
  • Check that return payload.decode("utf-8") remains inside receive_message().

Help me compare my complete protocol module with the expected structure.

Connect both socket programs

The framing rules are ready. The server now receives complete text through receive_message() before sending the same text back through send_message().

  • Select server.py in the Explorer sidebar.
  • Replace its contents with this framed server version:
import socket

from protocol import receive_message, send_message

HOST = "127.0.0.1"
PORT = 50007


def run_server():
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server:
        server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        server.bind((HOST, PORT))
        server.listen()
        print(f"Server listening on {HOST}:{PORT}")

        connection, address = server.accept()
        with connection:
            print(f"Connected by {address}")
            message = receive_message(connection)
            print(f"Complete message: {message}")
            send_message(connection, message)
            print("Echo sent. Server closing.")


if __name__ == "__main__":
    run_server()

What Changed in the Server?

  • The import gives the server access to both framing helpers.
  • The receive_message() call replaces the single eight-byte receive operation.
  • The message variable holds complete decoded text.
  • The send_message() call frames the complete echo before sending it.
  • Save server.py.
  • Check the updated server for syntax errors by running this command:
python3 -m py_compile server.py

What Does the Server Check Prove?

Python parses the updated imports and run_server() body. A successful check confirms that the framed server has valid syntax.

The terminal returns to its prompt without reporting a syntax error.

Server Import or Syntax Check Failing?

  • Check that protocol.py sits beside server.py in the Explorer sidebar.
  • Check that the import names match receive_message and send_message exactly.

Help me debug the protocol import in server.py.

The client needs the same protocol rules. Its input remains text until send_message() handles encoding and framing.

  • Select client.py in the Explorer sidebar.
  • Replace its contents with this framed client version:
import socket

from protocol import receive_message, send_message

HOST = "127.0.0.1"
PORT = 50007


def run_client():
    message = input("Message to send: ")

    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client:
        client.connect((HOST, PORT))
        send_message(client, message)
        echoed_message = receive_message(client)

    print(f"Echo received: {echoed_message}")


if __name__ == "__main__":
    run_client()

What Changed in the Client?

  • The client passes the input string directly to send_message().
  • The shared sender converts that string into a framed bytes sequence.
  • The receive_message() call waits for the complete framed echo.
  • The echoed_message variable already contains decoded text.
  • Save client.py.
  • Check the updated client for syntax errors by running this command:
python3 -m py_compile client.py

What Does the Client Check Prove?

Python parses the updated import and run_client() body. A successful check confirms that the client can load its framed workflow.

The terminal returns to its prompt without reporting a syntax error.

Client Check Failing?

  • Check that the old message_bytes and echoed_bytes lines are gone.
  • Check that echoed_message uses the same spelling in both client lines.

Help me find the mismatch in my framed client.

✔️ Awesome, I've got everything!

Great. Double-check that all three files are saved before you start the final test.

ⓧ I'd like to double check the full code

protocol.py
HEADER_SIZE = 4
BUFFER_SIZE = 8


def receive_exact(connection, byte_count):
    chunks = []
    bytes_received = 0

    while bytes_received < byte_count:
        remaining = byte_count - bytes_received
        chunk = connection.recv(min(BUFFER_SIZE, remaining))

        if not chunk:
            raise ConnectionError(
                "The connection closed before the full message arrived."
            )

        chunks.append(chunk)
        bytes_received += len(chunk)
        print(
            f"Received chunk {len(chunks)}: {chunk!r} | "
            f"hex: {chunk.hex(' ')}"
        )

    return b"".join(chunks)


def send_message(connection, message):
    payload = message.encode("utf-8")
    header = len(payload).to_bytes(HEADER_SIZE, "big")
    connection.sendall(header + payload)
    print(f"Sent header: {header!r} | payload: {payload!r}")


def receive_message(connection):
    header = receive_exact(connection, HEADER_SIZE)
    message_length = int.from_bytes(header, "big")
    print(f"Declared payload length: {message_length} bytes")
    payload = receive_exact(connection, message_length)
    return payload.decode("utf-8")
server.py
import socket

from protocol import receive_message, send_message

HOST = "127.0.0.1"
PORT = 50007


def run_server():
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server:
        server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        server.bind((HOST, PORT))
        server.listen()
        print(f"Server listening on {HOST}:{PORT}")

        connection, address = server.accept()
        with connection:
            print(f"Connected by {address}")
            message = receive_message(connection)
            print(f"Complete message: {message}")
            send_message(connection, message)
            print("Echo sent. Server closing.")


if __name__ == "__main__":
    run_server()
client.py
import socket

from protocol import receive_message, send_message

HOST = "127.0.0.1"
PORT = 50007


def run_client():
    message = input("Message to send: ")

    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client:
        client.connect((HOST, PORT))
        send_message(client, message)
        echoed_message = receive_message(client)

    print(f"Echo received: {echoed_message}")


if __name__ == "__main__":
    run_client()
Prove the complete round trip

The long message now has enough information to survive several small reads. This run proves that framing closes the truncation gap you observed earlier.

  • Return to the first integrated terminal from earlier.
  • Start the framed server by running this command:
python3 server.py

What Happens When the Server Starts?

The server binds to the same local address. It waits at server.accept() until the client connects.

You will see Server listening on 127.0.0.1:50007 in the first terminal.

  • Switch to the second integrated terminal from earlier.

Before you run the client, do you expect the long message to return as a truncated piece or as the complete sentence?

  • Start the framed client by running this command:
python3 client.py

What Happens When the Client Starts?

The client connects to the waiting server. It then displays the Message to send: prompt.

The shared protocol handles the header and receive loop after you enter the message.

  • Enter Networking makes bytes visible at the client prompt.

You will see Echo received: Networking makes bytes visible in the client terminal. The entire sentence now returns.

  • Return to the first integrated terminal.

You will see the four-byte header arrive before the payload. You will then see several received chunks with hexadecimal values.

The server prints Complete message: Networking makes bytes visible before sending its framed echo. Both terminal prompts return after the exchange closes.

Long Message Still Missing or Truncated?

  • Start server.py before running client.py if the client cannot connect.
  • Confirm that both files import helpers from protocol.py if the old eight-byte result returns.
  • Confirm that receive_message() calls receive_exact() once for the header and once for the payload.

Help me diagnose why my framed TCP message is incomplete.

That is the truncation bug closed. Your protocol now reconstructs the complete message while exposing every byte chunk that carried it.

Secret mission

Defuse a Stalled Connection

A healthy server responds quickly, but a stalled connection can leave a client waiting indefinitely. Add a three-second timeout, trigger it with a deliberate server pause, and prove that normal framed messages still work afterward.

Clean Up Your Resources

Clean Up Your Resources

Your TCP inspector ran entirely on your Mac, so there are no cloud resources or ongoing costs. Decide whether to keep your files, leave the stopped workspace for later, or delete it entirely.

Resources you used:

  • The local tcp-message-inspector workspace containing protocol.py, server.py, and client.py.

Keep everything running

No action needed. Choose this if you want to rerun the inspector or review how its framed messages work.

  • Keep the tcp-message-inspector folder for future experiments.
  • Use the existing server.py, client.py, and protocol.py files when you return.

Your Python installation remains available. Visual Studio Code remains installed for future projects.

Pause - I'll come back to this later

Both Python programs are already stopped, so the workspace is paused. Your files remain ready for your next session.

  • Leave the tcp-message-inspector folder in place.
  • Close Visual Studio Code when you finish reviewing the workspace.

Delete - I don't want to use this again

Remove the local workspace if you no longer need the inspector. Deleting this folder is permanent, so copy any code you want to keep before continuing.

  • Return to either integrated terminal from earlier.
  • Move one folder above tcp-message-inspector by running this command:
cd ..

Why Move Up First?

The cd .. command moves the terminal out of the workspace. This allows the terminal to remove the complete project folder.

  • Delete the tcp-message-inspector folder by running this command:
rm -rf tcp-message-inspector

What Does This Command Do?

  • The rm -rf command removes the named folder with everything inside it.
  • The tcp-message-inspector target limits the deletion to this project workspace.
  • Return to Finder.
  • Open the location that previously contained tcp-message-inspector.

You should no longer see the tcp-message-inspector folder in that location.

Still See the Workspace?

The terminal was likely pointing at a different parent folder when you ran the deletion command.

Help me safely locate the correct parent folder before I try the deletion again.

Nice Work!

Nice Work!

You did it! You built a local Python TCP message inspector that turns stream behavior into visible terminal output.

You've learned how to:

  • Built a local IPv4 TCP exchange that carries text between two terminal processes.
  • Reproduced an eight-byte partial-read bug by comparing the original message with its truncated echo.
  • Implemented length-prefixed message framing with a four-byte big-endian header. Used a receive loop to reconstruct complete UTF-8 messages. Logged each chunk as a bytes value plus hexadecimal output.
  • Secret Mission: Added a client-side timeout set to 3.0 seconds. Tested it against a deliberate server stall. Restored the echo path to prove healthy framed messages still succeed.

Ready to quiz yourself?