Master Linux Socket Programming: From BSD APIs to Real-World TCP/UDP Examples

This article explains Linux's socket networking framework, details BSD Socket APIs, the abstraction and VFS layers, protocol families, and provides comprehensive guides with code samples for creating, configuring, and using TCP and UDP sockets in C.

AI Cyberspace
AI Cyberspace
AI Cyberspace
Master Linux Socket Programming: From BSD APIs to Real-World TCP/UDP Examples

Socket Network Programming Framework

Socket (套接字) describes a communication endpoint used to establish network connections and transfer data. Linux Kernel offers a socket‑oriented programming framework with standard system‑call APIs, enabling userspace development of network applications such as HTTP servers, SMTP mail servers, and FTP file servers.

The Linux socket framework consists of three major modules:

BSD Socket APIs

Socket Abstraction Layer

VFS Layer

BSD Socket APIs Overview

BSD Socket APIs provide a userspace interface compatible with most network protocol families.

socket() : creates a new socket and returns an integer file descriptor.

bind() : binds a socket to a local IP:Port, typically used by servers.

connect() : establishes a connection to a remote host, typically used by clients.

listen() : starts listening for incoming connections on a server socket.

accept() : accepts a pending connection and returns a new socket descriptor for the client.

send() : sends data through a socket.

recv() : receives data from a socket.

close() : closes a socket connection.

Typical socket usage follows four steps: create the socket, configure it (bind, connect, listen), transfer data (send/recv), and close the socket. Socket APIs are not thread‑safe; concurrent access to the same descriptor requires proper synchronization.

Socket Abstraction Layer

The Socket Abstraction Layer implements the socket file system, managing the relationship between user processes and socket file descriptors, and defines core data structures such as struct socket, struct sock, protocol families, and address families. It also implements the TCP/IP stack (TCP, UDP, ICMP) and L4 transport functions.

Socket & Sock

Struct Socket is defined in the Socket Layer and exposed to upper‑level APIs. It stores attributes such as state, type, flags, and a pointer to the underlying struct sock .

Struct Sock resides in the Sock Layer, representing the protocol‑control block (PCB) for a specific protocol (e.g., TCP PCB).

The Socket Layer interacts with the Network Driver via socket buffers (skb). struct socket and struct sock are linked through pointers to realize socket functionality.

Socket Layer

/* linux/include/linux/net.h */
/**
 * struct socket - general BSD socket
 * @state: socket state (SS_CONNECTED, etc)
 * @type: socket type (SOCK_STREAM, etc)
 * @flags: socket flags (SOCK_NOSPACE, etc)
 * @ops: protocol‑specific socket operations
 * @file: file back pointer for gc
 * @sk: internal protocol‑agnostic socket representation
 * @wq: wait queue for several uses
 */
struct socket {
    socket_state state;
    short type;   // socket type, e.g., SOCK_STREAM, SOCK_DGRAM
    unsigned long flags;  // flags like O_NONBLOCK, O_ASYNC
    struct file *file;    // associated file structure
    struct sock *sk;      // pointer to corresponding Sock structure
    const struct proto_ops *ops; // operation set, e.g., inet_stream_ops
    struct socket_wq wq;  // wait queue
};

typedef enum {
    SS_FREE=0,      // not allocated
    SS_UNCONNECTED,  // not connected to any socket
    SS_CONNECTING,  // in the process of connecting
    SS_CONNECTED,   // connected to another socket
    SS_DISCONNECTING // in the process of disconnecting
} socket_state;

Sock Layer

Struct Sock contains low‑level execution state and operation information such as send/receive buffers, socket queues, and protocol‑specific fields.

/* linux/include/net/sock.h */
struct sock {
    unsigned short family;   // protocol family, e.g., AF_INET
    __u16 type;              // socket type, e.g., SOCK_STREAM
    unsigned long flags;    // flags like O_NONBLOCK
    struct proto *ops;       // protocol‑specific operations
    struct net_device *sk_net; // associated network device
    kmem_cache_t *sk_slab;   // memory allocation cache
    atomic_t refcnt;         // reference count
    struct mutex sk_lock;    // lock for state consistency
    struct sk_buff_head sk_receive_queue; // receive queue
    struct sk_buff_head sk_write_queue;   // send queue
    struct sk_buff *sk_send_head; // send buffer head
    struct sk_buff *sk_send_tail; // send buffer tail
    struct sk_buff *sk_receive_skb; // currently receiving packet
    __u32 sk_priority;       // socket priority
    struct dst_entry *sk_dst_cache; // cached destination
    struct dst_entry *sk_dst_pending_confirm;
    struct flowi sk_fl;     // flow information
    struct sk_filter *sk_filter; // filter
    struct sk_buff_head sk_async_wait_queue;
    unsigned long sk_wmem_alloc; // allocated send memory
    unsigned long sk_omem_alloc;
    struct socket_wq *sk_wq; // socket wait queue
    struct page_frag sk_frag; // page fragment allocator
    int sk_forward_alloc;    // forward allocation bytes
    int sk_rxhash;          // receive hash support
};

Protocol Family

Linux supports several protocol families:

PF_INETv4v6 (IP Socket) : IPv4/IPv6 with TCP/UDP. Sub‑types include SOCK_STREAM (TCP), SOCK_DGRAM (UDP), and SOCK_RAW (raw IP packets).

PF_PACKET (Packet Socket) : Direct access to device drivers for packet capture/injection, used in network security and monitoring.

PF_NETLINK (Netlink Socket) : Kernel‑userspace communication for network management.

PF_UNIX (UNIX Socket) : Inter‑process communication on Unix‑like systems.

Specific families may expose extra functions (e.g., PF_PACKET with libpcap, PF_NETLINK with libnetlink) but the basic socket API remains consistent.

VFS Layer

The VFS (Virtual File System) layer provides generic file‑system call APIs, allowing applications to use the same calls for file I/O and socket I/O. When an application calls socket(), a file descriptor is created in the VFS; when close() is called, the VFS releases the descriptor and associated resources.

PF_INET Sockets

socket()

Function : creates a new socket and returns an integer file descriptor.

Prototype :

#include <sys/socket.h>
int socket(int af, int type, int protocol);

Example :

// Create a TCP socket
int tcp_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
// Create a UDP socket
int udp_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);

setsockopt()

Sets socket options such as SO_REUSEADDR, SO_KEEPALIVE, SO_LINGER, and TCP_NODELAY.

#include <sys/socket.h>
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);

bind()

Binds a socket to a local IP:Port, usually for servers.

#include <sys/socket.h>
int bind(int sock, struct sockaddr *addr, socklen_t addrlen);

Example :

int tcp_socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
struct sockaddr_in tcp_socket_addr;
memset(&tcp_socket_addr, 0, sizeof(tcp_socket_addr));
	tcp_socket_addr.sin_family = PF_INET;
	tcp_socket_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
	tcp_socket_addr.sin_port = htons(1314);

bind(tcp_socket, (struct sockaddr *)&tcp_socket_addr, sizeof(tcp_socket_addr));

listen()

Starts listening for incoming connections on a server socket.

#include <sys/socket.h>
int listen(int sock, int backlog);

connect()

Establishes a connection to a remote host, typically used by clients.

#include <sys/socket.h>
int connect(int sock, struct sockaddr *serv_addr, socklen_t addrlen);

Example :

int cli_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
struct sockaddr_in server_sock_addr;
memset(&server_sock_addr, 0, sizeof(server_sock_addr));
server_sock_addr.sin_family = PF_INET;
server_sock_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
server_sock_addr.sin_port = htons(1314);
connect(cli_socket, (struct sockaddr *)&server_sock_addr, sizeof(server_sock_addr));

accept()

Accepts an incoming connection and returns a new socket descriptor for the client.

#include <sys/socket.h>
int accept(int sock, struct sockaddr *addr, socklen_t *addrlen);

getnameinfo()

Converts a socket address to a host or service name.

#include <sys/socket.h>
int getnameinfo(const struct sockaddr *addr, socklen_t addrlen,
                char *host, socklen_t hostlen,
                char *serv, socklen_t servlen, int flags);

Data Transfer

recv() and send()

Blocking I/O functions for TCP sockets.

#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t send(int sockfd, const void *buf, size_t len, int flags);

recvfrom() and sendto()

Used with UDP sockets to receive/send data along with the peer address.

#include <sys/socket.h>
ssize_t recvfrom(int sock, void *buf, size_t nbytes, int flags,
                 struct sockaddr *from, socklen_t *addrlen);
ssize_t sendto(int sock, const void *buf, size_t nbytes, int flags,
               const struct sockaddr *to, socklen_t addrlen);

recvmsg() and sendmsg()

Advanced functions that allow transmission of ancillary data.

#include <sys/socket.h>
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);

Structure struct msghdr defines the message header, including optional address, iovec array, control data, and flags.

struct msghdr {
    void *msg_name;       // optional address
    socklen_t msg_namelen; // size of address
    struct iovec *msg_iov; // scatter/gather array
    size_t msg_iovlen;     // number of iov elements
    void *msg_control;    // ancillary data
    size_t msg_controllen; // ancillary data length
    int msg_flags;         // flags on received message
};

Flags for recv()/send()

MSG_PEEK : Peek at incoming data without removing it from the queue.

MSG_WAITALL : Block until the full request is satisfied.

MSG_DONTWAIT : Return immediately if no data is available.

MSG_OOB : Process out‑of‑band (urgent) data.

MSG_TRUNC : Truncate data that exceeds the buffer size.

MSG_CTRUNC : Truncate ancillary control data.

close()

Closes a socket descriptor.

#include <unistd.h>
int close(int fd);

TCP Socket Programming Example

Server

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define ERR_MSG(err_code) do { \
    err_code = errno; \
    fprintf(stderr, "ERROR code: %d 
", err_code); \
    perror("PERROR message"); \
} while (0)

const int BUF_LEN = 100;

int main(void) {
    struct sockaddr_in srv_sock_addr;
    memset(&srv_sock_addr, 0, sizeof(srv_sock_addr));
    srv_sock_addr.sin_family = AF_INET;
    srv_sock_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    srv_sock_addr.sin_port = htons(6666);

    int srv_socket_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (srv_socket_fd == -1) { printf("Create socket error
"); ERR_MSG(errno); exit(EXIT_FAILURE); }

    int optval = 1;
    if (setsockopt(srv_socket_fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0) {
        printf("Set socket options error
"); ERR_MSG(errno); exit(EXIT_FAILURE);
    }

    if (bind(srv_socket_fd, (struct sockaddr *)&srv_sock_addr, sizeof(srv_sock_addr)) == -1) {
        printf("Bind socket error
"); ERR_MSG(errno); exit(EXIT_FAILURE);
    }

    if (listen(srv_socket_fd, 10) == -1) { printf("Listen socket error
"); ERR_MSG(errno); exit(EXIT_FAILURE); }

    struct sockaddr cli_sock_addr;
    memset(&cli_sock_addr, 0, sizeof(cli_sock_addr));
    int cli_sockaddr_len = sizeof(cli_sock_addr);
    int cli_socket_fd;
    char buff[BUF_LEN] = {0};

    while (1) {
        cli_socket_fd = accept(srv_socket_fd, (struct sockaddr *)&cli_sock_addr, (socklen_t *)&cli_sockaddr_len);
        if (cli_socket_fd == -1) { printf("Accept connection error
"); ERR_MSG(errno); exit(EXIT_FAILURE); }

        int recv_len = recv(cli_socket_fd, buff, BUF_LEN, 0);
        if (recv_len < 0) { printf("Receive error
"); ERR_MSG(errno); exit(EXIT_FAILURE); }
        printf("Received from client: %s
", buff);
        send(cli_socket_fd, buff, recv_len, 0);
        printf("Sent to client: %s
", buff);
        close(cli_socket_fd);
        memset(buff, 0, BUF_LEN);
    }
    close(srv_socket_fd);
    return EXIT_SUCCESS;
}

Client

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define ERR_MSG(err_code) do { \
    err_code = errno; \
    fprintf(stderr, "ERROR code: %d 
", err_code); \
    perror("PERROR message"); \
} while (0)

const int BUF_LEN = 100;

int main(void) {
    struct sockaddr_in srv_sock_addr;
    memset(&srv_sock_addr, 0, sizeof(srv_sock_addr));
    srv_sock_addr.sin_family = AF_INET;
    srv_sock_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    srv_sock_addr.sin_port = htons(6666);

    int cli_socket_fd;
    char send_buff[BUF_LEN];
    char recv_buff[BUF_LEN];

    while (1) {
        cli_socket_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
        if (cli_socket_fd == -1) { printf("Create socket error
"); ERR_MSG(errno); exit(EXIT_FAILURE); }

        if (connect(cli_socket_fd, (struct sockaddr *)&srv_sock_addr, sizeof(srv_sock_addr)) == -1) {
            printf("Connect error
"); ERR_MSG(errno); exit(EXIT_FAILURE);
        }

        fputs("Send to server> ", stdout);
        fgets(send_buff, BUF_LEN, stdin);
        send(cli_socket_fd, send_buff, BUF_LEN, 0);
        memset(send_buff, 0, BUF_LEN);

        recv(cli_socket_fd, recv_buff, BUF_LEN, 0);
        printf("Received from server: %s
", recv_buff);
        memset(recv_buff, 0, BUF_LEN);
        close(cli_socket_fd);
    }
    return EXIT_SUCCESS;
}

Testing

Compile:

$ gcc -g -std=c99 -Wall tcp_server.c -o tcp_server
$ gcc -g -std=c99 -Wall tcp_client.c -o tcp_client

Run the server, verify it is listening on port 6666, then run the client and exchange messages.

UDP Socket Programming Example

Server

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

#define BUF_LEN 100

int main(void) {
    int ServerFd;
    char Buf[BUF_LEN] = {0};
    struct sockaddr ClientAddr;
    struct sockaddr_in ServerSockAddr;
    int addr_size = 0;
    int optval = 1;

    ServerFd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (ServerFd == -1) { perror("socket error"); exit(1); }

    memset(&ServerSockAddr, 0, sizeof(ServerSockAddr));
    ServerSockAddr.sin_family = AF_INET;
    ServerSockAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    ServerSockAddr.sin_port = htons(1314);

    if (setsockopt(ServerFd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0) { perror("setsockopt error"); exit(1); }
    if (bind(ServerFd, (struct sockaddr *)&ServerSockAddr, sizeof(ServerSockAddr)) == -1) { perror("bind error"); exit(1); }

    addr_size = sizeof(ClientAddr);
    while (1) {
        int str_len = recvfrom(ServerFd, Buf, BUF_LEN, 0, &ClientAddr, &addr_size);
        printf("Received from client: %s
", Buf);
        sendto(ServerFd, Buf, str_len, 0, &ClientAddr, addr_size);
        memset(Buf, 0, BUF_LEN);
    }
    close(ServerFd);
    return 0;
}

Client

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

#define BUF_LEN 100

int main(void) {
    int ClientFd;
    char Buf[BUF_LEN] = {0};
    struct sockaddr ServerAddr;
    int addr_size = 0;
    struct sockaddr_in ServerSockAddr;

    ClientFd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (ClientFd == -1) { perror("socket error"); exit(1); }

    memset(&ServerSockAddr, 0, sizeof(ServerSockAddr));
    ServerSockAddr.sin_family = PF_INET;
    ServerSockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    ServerSockAddr.sin_port = htons(1314);

    addr_size = sizeof(ServerAddr);
    while (1) {
        printf("Enter a string to send to server: ");
        fgets(Buf, BUF_LEN, stdin);
        sendto(ClientFd, Buf, strlen(Buf), 0, (struct sockaddr *)&ServerSockAddr, sizeof(ServerSockAddr));
        recvfrom(ClientFd, Buf, BUF_LEN, 0, &ServerAddr, &addr_size);
        printf("Received from server: %s
", Buf);
        memset(Buf, 0, BUF_LEN);
    }
    close(ClientFd);
    return 0;
}

Testing

Verify the UDP server is listening:

$ netstat -lpntu | grep 1314
udp        0      0 0.0.0.0:1314           0.0.0.0:*                           29729/udp_server
NetworkCTCPLinuxSocket ProgrammingUDP
AI Cyberspace
Written by

AI Cyberspace

AI, big data, cloud computing, and networking.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.