Sockets: Socket Types SOCK_STREAM vs SOCK_DGRAM · TCP vs UDP ·

 

Chapter 56 – Sockets: Socket Types
Part 3 of 4  |  SOCK_STREAM vs SOCK_DGRAM · TCP vs UDP · Connection Concepts
Series
Linux IPC
Chapter
56 – TLPI
Level
Beginner–Intermediate

What You Will Learn

After picking a domain, the second major choice when calling socket() is the socket type. The two most common types are SOCK_STREAM (reliable, connection-based) and SOCK_DGRAM (message-based, no connection). Understanding the guarantees and trade-offs of each is essential before writing any network code.

Key Terms

SOCK_STREAM SOCK_DGRAM TCP UDP Reliable Delivery Message Boundaries Connection-Oriented Connectionless Byte Stream Datagram Peer Socket

1. The Two Main Socket Types

Every socket implementation provides at least two types. They are supported in both the Unix domain and the Internet domain.

Property SOCK_STREAM SOCK_DGRAM
Reliable delivery? Yes ✔ No ✘
Message boundaries preserved? No ✘ (byte stream) Yes ✔
Connection required? Yes (connection-oriented) No (connectionless)
Internet protocol TCP UDP
Typical use case HTTP, SSH, FTP, databases DNS, video streaming, games

2. SOCK_STREAM — Stream Sockets Explained

A stream socket delivers a reliable, bidirectional, byte-stream channel. Breaking this down:

Reliable — Data you send is either received intact and in order by the other end, or you receive an error notification. Nothing silently disappears. In the Internet domain this is provided by TCP.

Bidirectional — Both sides can send and receive on the same connection simultaneously.

Byte-stream — There are no message boundaries. If you call write(fd, buf, 100) twice, the receiver may get all 200 bytes in one read(), or split across several calls. This is identical to a pipe’s behaviour. You are responsible for adding your own framing if you need to distinguish individual messages.

Stream sockets are connection-oriented: before any data can flow, a connection must be established between two sockets. A stream socket can be connected to exactly one peer at a time.

Byte-stream: writes do not match reads 1-to-1
Sender writes Receiver may read
write(fd, “Hello”, 5)
write(fd, “World”, 5)
read() → “HelloWorld” (10 bytes)
OR two separate reads of 5 each

Creating a TCP (stream) socket:

#include <sys/socket.h>

/* TCP socket — AF_INET domain, stream type */
int tcpfd = socket(AF_INET, SOCK_STREAM, 0);

/* Unix domain stream socket */
int unixfd = socket(AF_UNIX, SOCK_STREAM, 0);

Terminology around stream sockets:

  • Peer socket — the socket at the other end of the connection.
  • Peer address — the address of that peer socket.
  • Peer application — the application using the peer socket.
  • Remote / foreign — synonyms for peer (from the sender’s perspective).
  • Local — refers to this end of the connection.

3. SOCK_DGRAM — Datagram Sockets Explained

A datagram socket sends and receives data in discrete chunks called datagrams. Each datagram is a self-contained message. Unlike stream sockets:

  • Message boundaries are preserved — each sendto() corresponds to exactly one recvfrom(). You never get half a message.
  • Delivery is not guaranteed — datagrams can be lost, reordered, or duplicated. No error notification is given when a message is dropped.
  • No connection required — you can send to any address at any time. This is called connectionless.

In the Internet domain, datagram sockets use UDP (User Datagram Protocol). This is why we often just say “UDP socket” for an Internet domain datagram socket.

Datagram: each send = one receive (but may be lost/reordered)
Sender Network Receiver
sendto() “Msg1” ✔ delivered recvfrom() → “Msg1”
sendto() “Msg2” ✘ dropped never received
sendto() “Msg3” ⟳ reordered recvfrom() → “Msg3” (arrived before Msg2)

Creating a UDP (datagram) socket:

#include <sys/socket.h>

/* UDP socket — AF_INET domain, datagram type */
int udpfd = socket(AF_INET, SOCK_DGRAM, 0);

/* Unix domain datagram socket */
int unixdg = socket(AF_UNIX, SOCK_DGRAM, 0);

Basic UDP send/receive (no connect needed):

#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

/* ---- SENDER side ---- */
void udp_sender(void)
{
    int sockfd;
    struct sockaddr_in dest;
    const char *msg = "Hello UDP";

    sockfd = socket(AF_INET, SOCK_DGRAM, 0);

    memset(&dest, 0, sizeof(dest));
    dest.sin_family      = AF_INET;
    dest.sin_port        = htons(9000);
    inet_pton(AF_INET, "127.0.0.1", &dest.sin_addr);

    /* sendto: specify destination each time — no prior connect needed */
    sendto(sockfd, msg, strlen(msg), 0,
           (struct sockaddr *)&dest, sizeof(dest));

    close(sockfd);
}

/* ---- RECEIVER side ---- */
void udp_receiver(void)
{
    int sockfd;
    struct sockaddr_in me, sender;
    socklen_t slen = sizeof(sender);
    char buf[256];
    ssize_t n;

    sockfd = socket(AF_INET, SOCK_DGRAM, 0);

    memset(&me, 0, sizeof(me));
    me.sin_family      = AF_INET;
    me.sin_port        = htons(9000);
    me.sin_addr.s_addr = INADDR_ANY;   /* listen on all interfaces */

    bind(sockfd, (struct sockaddr *)&me, sizeof(me));

    /* recvfrom: also tells you who sent it */
    n = recvfrom(sockfd, buf, sizeof(buf)-1, 0,
                 (struct sockaddr *)&sender, &slen);
    buf[n] = '\0';
    printf("Got: '%s'\n", buf);

    close(sockfd);
}

4. Connected Datagram Sockets — A Subtle Feature

It is possible to call connect() on a datagram socket. This does not establish a real connection the way TCP does. Instead it simply records a default destination address in the kernel. After that you can use send()/write() instead of sendto(), and the kernel fills in the destination automatically.

The socket remains connectionless underneath — no handshake happens. You can call connect() again to change the destination, or pass a null address to unset it.

/* Connect a UDP socket to a default destination */
connect(udpfd, (struct sockaddr *)&dest, sizeof(dest));

/* Now write() instead of sendto() — dest auto-filled */
write(udpfd, "data", 4);

/* Can still override with sendto() if needed */

5. TCP vs UDP — Choosing the Right Socket Type
Use TCP (SOCK_STREAM) when… Use UDP (SOCK_DGRAM) when…
Data must arrive and be in order (e.g. file transfer, HTTP) Speed matters more than reliability (e.g. live video, VoIP)
You cannot afford lost data You want low latency and can handle occasional loss in app logic
Message size is variable or large Short, self-contained queries like DNS lookups
You need flow control and congestion control You want to broadcast/multicast to many receivers

Interview Questions — Part 3

Q1. What does “reliable delivery” mean for SOCK_STREAM?

It means either the data arrives intact and in the exact order it was sent, or the application receives an error. Nothing is silently dropped. TCP achieves this through sequence numbers, acknowledgements, and retransmission.

Q2. What is a “byte stream” and why does it matter to the programmer?

A byte stream has no inherent message boundaries. A single write() call by the sender may be split across multiple read() calls at the receiver, or several writes may arrive in a single read. Programmers must add application-level framing (e.g. a length header before each message) to know where one message ends and the next begins.

Q3. What happens if a UDP datagram is lost?

Nothing automatic. UDP provides no acknowledgement or retransmission. The sender gets no error. If the application requires reliability over UDP it must implement its own ACK/retry logic (as QUIC and many game protocols do).

Q4. What is a “connectionless” socket?

A socket that does not require a prior handshake before sending data. SOCK_DGRAM sockets are connectionless — you can call sendto() to any destination address at any time without first establishing a session.

Q5. What does calling connect() on a UDP socket actually do?

It records a default peer address in the kernel for that socket. No handshake occurs. After this you can use send()/write() without specifying a destination each time. The socket remains connectionless — you can call connect() again to change the default destination.

Q6. Name the socket type and protocol used by a web browser connecting to an HTTP server.

SOCK_STREAM type in the AF_INET (or AF_INET6) domain, which maps to TCP at the transport layer.

Q7. What does “message boundaries are preserved” mean for SOCK_DGRAM?

Each sendto() call produces exactly one datagram, and each recvfrom() call receives exactly that one datagram — it will never be split or merged with another. This is the opposite of the byte-stream behaviour of SOCK_STREAM.

Next: Part 4 — Socket System Calls

Deep dive into bind(), listen(), accept(), connect() with full code examples.

Part 4 → ← Part 2

Leave a Reply

Your email address will not be published. Required fields are marked *