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.
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_clientRun 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_serverHow this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
