Free tech learning for everyone, from basics to pro

Python for NetworkingProgramming and Application SecurityIntermediate

Network programming with Python sockets: TCP client and server basics

In short: A socket is a program’s endpoint for network communication, identified by an IP address, port and protocol. Python’s socket module lets you build TCP and UDP clients and servers. Writing a small echo server and client shows how ports, handshakes and timeouts really behave.

What a socket is

A socket is the interface a program uses to send and receive data over a network. Each connection is identified by protocol, local address and port, and remote address and port. Higher-level libraries such as requests or Netmiko all sit on top of sockets. Writing a few socket programs yourself makes ideas such as ports, the TCP handshake and timeouts concrete.

TCP and UDP socketsTCP (SOCK_STREAM)Connection set up firstReliable, ordered bytesconnect, send, recvSSH, HTTPS, SMTPUDP (SOCK_DGRAM)No connectionSeparate datagramssendto, recvfromDNS, syslog, SNMP
Figure 1. TCP sockets carry a reliable byte stream; UDP sockets send independent datagrams.

A simple TCP echo server and client

TCP server and clientbindserver picksportlistenwait forclientsconnectclient dialsinacceptnew socketsend / recvexchangedataclosetidy up
Figure 2. The life of a simple TCP connection between a Python server and client.

The server binds to an address and port, listens, then accepts each connection, which gives a new socket for that client. This example only listens on the loopback address 127.0.0.1, so it is not reachable from other machines.

# echo_server.py
import socket

HOST, PORT = "127.0.0.1", 5050

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as srv:
    srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    srv.bind((HOST, PORT))
    srv.listen()
    print(f"Listening on {HOST}:{PORT}")
    conn, addr = srv.accept()
    with conn:
        print("Client", addr)
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data.upper())
# echo_client.py
import socket

with socket.create_connection(("127.0.0.1", 5050), timeout=5) as s:
    s.sendall(b"hello from the client")
    print(s.recv(1024).decode())

Run the server in one terminal and the client in another. Sockets send bytes, not text, so encode strings with .encode() and decode replies with .decode(). Note that one recv() may return only part of a message: TCP is a stream, not a series of messages, so real protocols add lengths or delimiters.

UDP and a reachability check

UDP sockets have no connection: you send a datagram with sendto() and read with recvfrom(). No reply might mean the packet was lost, filtered or simply ignored. For engineers, the most practical socket job is checking whether a TCP service answers, for example after a firewall change.

import socket


def tcp_port_open(host: str, port: int, timeout: float = 2.0) -> bool:
    try:
        with socket.create_connection((host, port), timeout=timeout):
            return True
    except OSError:
        return False


for port in (22, 443):
    print(port, tcp_port_open("192.168.10.1", port))

Tip: only test hosts and networks you own or are authorised to test. Repeated connection attempts across many ports look like a port scan, may trigger alerts and can breach policy or the law.

Common mistakes and security points

  • No timeout: a blocked connection can hang a script forever. Always set one.
  • Binding to 0.0.0.0 exposes a test server to the whole network. Use 127.0.0.1 unless you mean it.
  • "Address already in use": a recently closed port is still in the TIME_WAIT state. SO_REUSEADDR helps in testing.
  • Plain text: raw sockets are unencrypted. Wrap them with the ssl module, or better, use an existing protocol such as HTTPS or SSH.
  • Trusting input: a server must treat everything it receives as untrusted. Limit message sizes and validate content.
CallSidePurpose
bind()ServerClaim a local address and port
listen() / accept()ServerWait for and accept TCP clients
create_connection()ClientResolve, connect and set a timeout
sendall() / recv()BothExchange bytes over TCP
sendto() / recvfrom()BothExchange UDP datagrams

Practice

Why does accept() return a new socket?
The listening socket keeps waiting for more clients, while the new socket handles the conversation with the one client that just connected.
A client calls recv(1024) once and gets half a message. Is that a bug in TCP?
No. TCP is a byte stream with no message boundaries, so the application must keep reading until it has a complete message.
Why bind a test server to 127.0.0.1 rather than 0.0.0.0?
127.0.0.1 is reachable only from the same machine, so an experimental, unauthenticated service is not exposed to the network.

Key takeaways

  • A socket is identified by protocol, addresses and ports.
  • TCP sockets carry a reliable byte stream; UDP sockets send datagrams.
  • Servers bind, listen and accept; clients connect.
  • Always set timeouts and work in bytes, not text.
  • Only test systems you own; use TLS or existing protocols for real data.
Tracks your learning on this device only. See your progress on the learning paths.

About the author

Mohamed Fayas

Founder of FM TecHub. CCNA certified, with a BTEC HND in Network Engineering, and BEng (Hons) in Computer Networking and Cloud Security. He writes practical, plain-English guides on networking, security and cloud.

More about the author

Original content written by Mohamed Fayas for FM TecHub. © 2026 FM TecHub. You're welcome to share a link to this page; please don't copy the text elsewhere without permission.

Stay up to date by email

New notes, tools and security news from FM TecHub, explained simply. Free.

We only use your email to send these updates. See our privacy policy.