Transport

Socket Programming

Network programming interfaces and socket APIs for TCP and UDP.

Introduction: The Socket Interface

TCP and UDP define transport services, but an application still needs an operating-system interface to use them. A web browser, chat program, or other network application calls the socket API to create an endpoint, select a transport protocol, and exchange data through the system networking stack.

The Socket Application Programming Interface (API)is a set of functions and data structures for creating network endpoints, assigning addresses, establishing connections, and sending or receiving data. It separates application logic from the internal state and packet processing of the operating system's TCP/IP stack.

A socket is an operating-system endpoint represented to the application by a descriptor. The application supplies protocol parameters and addresses through API calls; the operating system maintains connection state, performs transport processing, and selects the required network path.

The descriptor identifies one local socket object and is used as an argument to later calls such as 'bind()', 'send()', and 'close()'. Its numeric value has meaning only in the owning process; the peer sees an address and transport state, not the descriptor itself.

Understanding the Socket API

An defines the calls and data contracts that software components use to interact. The Socket API exposes functions such as 'socket()', 'connect()', 'send()', and 'recv()', so a program can request network services without implementing TCP handshakes, checksum calculations, or IP routing itself.

The Berkeley Sockets API was introduced in the Berkeley Software Distribution (BSD) version of UNIX and became a widely adopted interface for network programming. Implementations are available on systems such as Windows, Linux, macOS, iOS, and Android, although details and supported options can differ between platforms.

A typical 'socket()' call selects an address family such as 'AF_INET', a type such as 'SOCK_STREAM' or 'SOCK_DGRAM', and a protocol value. The returned descriptor is checked for an error before the program uses it, since resource limits or an unsupported combination can prevent creation.

What is a Socket? The File-Descriptor Model

A socket is a network endpoint object that an application creates to communicate over a network. On systems with a file-descriptor interface, the operating system represents it as a descriptor that can be used with input and output operations. The basic operations are:

  • Open a file to begin reading or writing.
  • Read from a file to receive data.
  • Write to a file to send data.
  • Close a file when you are finished.

In socket programming, the corresponding operations are:

  • Create a socket to get a "handle" for communication.
  • Read from (or receive on) the socket to get data from the network.
  • Write to (or send on) the socket to transmit data over the network.
  • Close the socket to terminate the connection.

A descriptor can also be registered with readiness mechanisms such as 'select()', 'poll()', or an operating-system event facility. Those mechanisms report when an operation may proceed; the subsequent read or write can still transfer fewer bytes than requested and must be checked by the application.

The Two Primary Types of Sockets

When a programmer creates a socket, they must choose its type, which directly corresponds to the underlying transport protocol they wish to use. The two most common types are stream sockets and datagram sockets.

Stream Sockets (TCP)

Type: 'SOCK_STREAM'

These sockets use TCP and provide a reliable, connection-oriented, full-duplex, ordered byte stream. TCP does not preserve message boundaries. If an application writes 10 bytes and then 20 bytes, a receive call may return all 30 bytes, only part of them, or data from both writes in another grouping.

Example: An application protocol over TCP must define its own framing, such as a length prefix or a delimiter, before it can separate requests.

A single 'send()' or 'write()' can also accept only part of the supplied buffer. A sender therefore repeats the operation until the complete framed message is queued, while a receiver accumulates bytes until its framing rule is satisfied.

Datagram Sockets (UDP)

Type: 'SOCK_DGRAM'

These sockets use UDP and provide an unreliable, connectionless, message-oriented datagram service. Datagram boundaries are preserved when the receive buffer is sufficient. If an application sends a 10-byte datagram and then a 20-byte datagram, the receiver obtains two datagrams of 10 and 20 bytes.

Example: Each call to 'sendto()' supplies one datagram. It can be lost or arrive out of order, and the application must handle any required reliability.

If the receive buffer is too small, the API can return only the part that fits and report truncation according to the platform. The application must choose a buffer for the protocol's maximum datagram size when it needs to preserve every byte.

TCP Socket Lifecycle

The TCP socket lifecycle consists of different calls on the server and client. The server prepares a passive listening socket, while the client initiates the connection.

Server-Side TCP Workflow

  1. 'socket()' - Create the Socket:The server's first step is to ask the operating system to create a socket endpoint. The programmer specifies the address family (e.g., IPv4) and the socket type ('SOCK_STREAM' for TCP). The OS returns a file descriptor, a small integer that acts as an ID for this new socket.
    Result: The descriptor identifies this endpoint for later socket calls.
    Typical arguments:An IPv4 TCP server commonly selects 'AF_INET', 'SOCK_STREAM', and protocol 0 so the system chooses TCP for the requested type.
  2. 'bind()' - Assign an Address:A new socket is just a generic endpoint. To be useful, a server must associate it with a specific IP address and port number on the machine. The 'bind()' function does this. A web server would bind its socket to the server's public IP address and the well-known port 80.
    Example: A web server can bind a listening socket to a local address and the well-known port 80.
    A wildcard local address can request reception on the host's eligible interfaces, while binding a specific address limits the endpoint to that local interface.
  3. 'listen()' - Enter the Listening State:Binding the socket assigns its local address, but does not make it a listener. The 'listen()' function places a stream socket in passive mode. Its 'backlog' parameter supplies a limit for pending connections; an application might request a queue of 5, subject to operating-system rules.
    The backlog concerns connections waiting for the application to accept them. It does not reserve bandwidth and does not determine how many established sockets the process can maintain.
  4. 'accept()' - Return a Connection: On a blocking socket, 'accept()' waits for a completed connection in the pending queue after the operating system has completed the TCP three-way handshake. It returns a new socket dedicated to that client and its file descriptor. The original listening socket remains in the LISTEN state and can accept more connections.
  5. 'read()'/'recv()' and 'write()'/'send()' - Communicate: The server can now use the new connection socket to communicate with the client, reading requests and writing responses, just like writing to and reading from a file.
    Because TCP is a byte stream, these calls can complete partially or return zero after an orderly peer shutdown. The application must handle the returned count and define when a complete request or response has been received.
  6. 'close()' - Terminate the Connection:When the exchange is over, the server calls 'close()' on the connected socket. For an orderly TCP close this starts the FIN exchange, commonly described as a four-way close. The listening socket is closed separately when the server stops accepting connections.

Client-Side TCP Workflow

The client creates an endpoint and actively initiates the connection.

  1. 'socket()' - Create the Socket:Just like the server, the client must first create a socket endpoint ('SOCK_STREAM' for TCP).
  2. 'connect()' - Establish a Connection:Instead of binding and listening, the client calls 'connect()' with the server's IP address and port. The operating system performs the TCP three-way handshake. A blocking call waits until the connection reaches 'ESTABLISHED' or an error occurs; a nonblocking call reports progress through the usual readiness and error mechanisms. The OS normally assigns an ephemeral client port if the application has not bound one.
    The resulting TCP flow is identified by the local and remote address and port together with the protocol. A client can reuse one descriptor for a sequence of ordered requests, subject to the application protocol and connection state.
  3. 'write()'/'send()' and 'read()'/'recv()' - Communicate: Once the connection is established, the client can begin sending requests to the server and receiving responses through its socket.
  4. 'close()' - Terminate the Connection:When the client has received the required data, it calls 'close()' on its socket and starts its side of the orderly TCP FIN exchange.

The UDP Socket Lifecycle: A Connectionless Model

UDP sockets do not require a handshake or a persistent transport association. Each datagram is an independent unit, although the application may call 'connect()' to select a default peer for the socket.

Calling 'connect()' for UDP does not create a TCP-style session. It records a default destination and can restrict received datagrams to that peer on many systems, while delivery, ordering, and retransmission remain outside UDP.

Server-Side UDP Workflow

  1. 'socket()' - Create the Socket:The server creates a socket, but this time it specifies the type as 'SOCK_DGRAM' for datagram communication.
  2. 'bind()' - Assign an Address: This step is identical to TCP. The UDP server must bind its socket to a well-known port so clients know where to send their datagrams.
  3. 'recvfrom()' - Wait for a Datagram: There is no 'listen()' or 'accept()'. A UDP server simply calls 'recvfrom()'. This function blocks until a datagram arrives on the bound port. When it returns, it provides the received data together with the source address (IP and port) of the client who sent it.
    Result: The call returns the datagram and the source address, so a server can associate a reply with the sender.
    The returned length describes the received datagram as represented by the API. The server should validate that length and any application fields before constructing a response.
  4. 'sendto()' - Send a Reply:Without a connected UDP peer, the server uses 'sendto()' and supplies both the data and the destination address obtained from 'recvfrom()'.

Client-Side UDP Workflow

  1. 'socket()' - Create the Socket:The client creates a datagram socket ('SOCK_DGRAM').
  2. 'sendto()' - Send a Datagram:The client can prepare its data and call 'sendto()' with the server's known address (IP and port), without a handshake. It may instead call 'connect()' to set a default peer and then use 'send()' or 'write()'. The OS assigns an ephemeral port if the socket has not already been bound.
  3. 'recvfrom()' - Wait for a Reply:The client then typically calls 'recvfrom()' to wait for the server's response datagram to arrive on its ephemeral port.
    The client can compare the returned source address with the expected server and discard an unrelated response when the protocol requires that check.

Related articles