C Network Sockets — TCP/IP Socket Programming with Berkeley Sockets
In this tutorial, you will learn about C Network Sockets. We cover key concepts, practical examples, and best practices to help you master this topic.
C network socket programming using the Berkeley sockets API enables TCP/IP communication between processes over a network, with system calls for creating sockets (socket), binding addresses (bind), listening for connections (listen), accepting clients (accept), and connecting to servers (connect).
What You Will Learn
- Creating TCP sockets with socket()
- Binding and listening with bind() and listen()
- Accepting client connections with accept()
- Connecting to servers with connect()
- Sending and receiving data with send() and recv()
- Handling multiple connections sequentially
- IPv4 and IPv6 address structures
Why It Matters
Network programming is the backbone of the internet. Every web server, database client, chat application, and IoT device communicates over sockets. Understanding the socket API lets you build network services, custom protocols, and Distributed Systems. Durga Antivirus Pro uses sockets to download signature updates from the update server and to communicate between the scan daemon and the GUI over localhost.
Real-World Use
A system monitoring agent runs on 100 servers. It opens a TCP connection to the central monitoring server every 60 seconds, sends CPU/memory/disk metrics as a JSON string, and closes the connection. The central server runs a socket server that accepts all 100 agents, processes their data, and stores it.
Learning Path
flowchart LR A[Multithreading] --> B[Network Sockets\nYou are here] B --> C[I/O Multiplexing] style B fill:#f90,color:#fff
TCP Echo Server
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define PORT 8080
#define BUFFER_SIZE 1024
int main() {
int server_fd, client_fd;
struct sockaddr_in address;
int opt = 1;
socklen_t addrlen = sizeof(address);
char buffer[BUFFER_SIZE] = {0};
// 1. Create socket
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == -1) {
perror("socket failed");
exit(1);
}
// 2. Allow port reuse (avoids "Address already in use")
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) {
perror("setsockopt");
exit(1);
}
// 3. Bind to address and port
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);
if (bind(server_fd, (struct sockaddr*)&address, sizeof(address)) < 0) {
perror("bind failed");
exit(1);
}
// 4. Listen for connections
if (listen(server_fd, 3) < 0) {
perror("listen");
exit(1);
}
printf("Echo server listening on port %d\n", PORT);
// 5. Accept and handle connections
while (1) {
client_fd = accept(server_fd, (struct sockaddr*)&address, &addrlen);
if (client_fd < 0) {
perror("accept");
continue;
}
printf("Client connected\n");
// Receive and echo back
int valread = read(client_fd, buffer, BUFFER_SIZE - 1);
if (valread > 0) {
buffer[valread] = '\0';
printf("Received: %s\n", buffer);
send(client_fd, buffer, valread, 0);
printf("Echoed back\n");
}
close(client_fd);
}
close(server_fd);
return 0;
}
TCP Echo Client
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define PORT 8080
#define BUFFER_SIZE 1024
int main() {
int sock;
struct sockaddr_in server_addr;
char buffer[BUFFER_SIZE] = {0};
// 1. Create socket
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
perror("socket");
return 1;
}
// 2. Configure server address
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
if (inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr) <= 0) {
perror("Invalid address");
return 1;
}
// 3. Connect to server
if (connect(sock, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
perror("connect failed");
return 1;
}
// 4. Send and receive
const char *message = "Hello, server!";
send(sock, message, strlen(message), 0);
printf("Sent: %s\n", message);
int valread = read(sock, buffer, BUFFER_SIZE - 1);
buffer[valread] = '\0';
printf("Received: %s\n", buffer);
close(sock);
return 0;
}
Compile and run:
gcc -o echo_server echo_server.c
gcc -o echo_client echo_client.c
./echo_server &
./echo_client
Output (client):
Sent: Hello, server!
Received: Hello, server!
HTTP Request Client
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#define BUFFER_SIZE 4096
int http_get(const char *host, const char *path) {
int sock;
struct hostent *server;
struct sockaddr_in addr;
char request[1024];
char response[BUFFER_SIZE];
// Resolve hostname
server = gethostbyname(host);
if (server == NULL) {
fprintf(stderr, "No such host: %s\n", host);
return -1;
}
// Create socket
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
perror("socket");
return -1;
}
addr.sin_family = AF_INET;
addr.sin_port = htons(80);
memcpy(&addr.sin_addr, server->h_addr_list[0], server->h_length);
// Connect
if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
perror("connect");
close(sock);
return -1;
}
// Send HTTP GET request
snprintf(request, sizeof(request),
"GET %s HTTP/1.1\r\nHost: %s\r\nConnection: close\r\n\r\n",
path, host);
send(sock, request, strlen(request), 0);
// Read response
int total = 0;
int n;
while ((n = read(sock, response + total, sizeof(response) - total - 1)) > 0) {
total += n;
}
response[total] = '\0';
printf("%s\n", response);
close(sock);
return 0;
}
int main() {
http_get("example.com", "/");
return 0;
}
Non-blocking Sockets
Set a socket to non-blocking mode for async operations:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>
int set_nonblocking(int fd) {
int flags = fcntl(fd, F_GETFL, 0);
if (flags == -1) return -1;
return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}
int main() {
int server_fd;
struct sockaddr_in addr;
server_fd = socket(AF_INET, SOCK_STREAM, 0);
set_nonblocking(server_fd);
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(8080);
bind(server_fd, (struct sockaddr*)&addr, sizeof(addr));
listen(server_fd, 5);
printf("Non-blocking server on port 8080\n");
while (1) {
struct sockaddr_in client;
socklen_t client_len = sizeof(client);
int client_fd = accept(server_fd, (struct sockaddr*)&client, &client_len);
if (client_fd >= 0) {
printf("Client accepted\n");
close(client_fd);
} else if (errno == EWOULDBLOCK || errno == EAGAIN) {
// No pending connections -- do other work
printf("No client yet, doing other work...\n");
usleep(500000);
} else {
perror("accept error");
}
}
close(server_fd);
return 0;
}
Address Resolution with getaddrinfo
The modern way to resolve hostnames and create sockets (IPv4/IPv6 agnostic):
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
int connect_to_host(const char *host, const char *port) {
struct addrinfo hints, *result, *rp;
int sock;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC; // IPv4 or IPv6
hints.ai_socktype = SOCK_STREAM; // TCP
int s = getaddrinfo(host, port, &hints, &result);
if (s != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
return -1;
}
// Try each address until one works
for (rp = result; rp != NULL; rp = rp->ai_next) {
sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
if (sock == -1) continue;
if (connect(sock, rp->ai_addr, rp->ai_addrlen) != -1) {
break; // Success
}
close(sock);
}
if (rp == NULL) {
fprintf(stderr, "Could not connect\n");
freeaddrinfo(result);
return -1;
}
freeaddrinfo(result);
return sock;
}
int main() {
int sock = connect_to_host("example.com", "80");
if (sock >= 0) {
printf("Connected to example.com:80\n");
close(sock);
}
return 0;
}
Common Mistakes
Forgetting htons/htonl: Network byte order is big-endian. Port numbers and IP addresses must be converted with htons() and htonl(). Forgetting this causes connections to the wrong port.
Not checking all return values: socket(), bind(), listen(), accept(), connect(), send(), recv() can all fail. Every call must be checked for errors.
Ignoring partial sends/receives: send() and recv() may not send/receive all bytes in one call. Always loop until all data is transferred.
Not setting SO_REUSEADDR: Without this, restarting the server after a crash gives "Address already in use" for several minutes while the port is in TIME_WAIT state.
Assuming getaddrinfo succeeds: DNS can fail (network down, host not found). Always check the return value of getaddrinfo and use gai_strerror for diagnostics.
Blocking on accept with no clients: The accept() call blocks until a client connects. Use non-blocking sockets or I/O multiplexing (select, poll, epoll) for servers that must do other work.
Not closing sockets: Every open socket consumes a file descriptor. There is a system-wide limit (typically 1024). Close sockets when done.
Practice Questions
- What is the purpose of htons() and ntohs()?
- What does SO_REUSEADDR do and why is it important for servers?
- How does the three-way TCP handshake relate to connect() and accept()?
- Why might recv() return fewer bytes than requested?
- Challenge: Write a simple HTTP server that serves static files from a directory. Support GET requests for .html, .css, and .js files. Return proper Content-Type headers and 404 for missing files. Use non-blocking sockets to handle multiple connections.
Mini Project
Build a chat room server and client:
- Server: Accepts multiple clients (using pthreads or fork), maintains a list of connected users, broadcasts messages to all clients, handles disconnect gracefully
- Client: Connects to the server, sends messages from stdin, receives and displays messages from other users in real time
- Protocol: Each message starts with a 4-byte length prefix (network byte order), followed by the message text
- Commands:
/nick <name>to set nickname,/listto list users,/quitto disconnect - Handle: client disconnect detection (recv returns 0), message fragmentation, and long messages (up to 64 KB)
- Test with 3+ clients simultaneously
FAQ
What is Next
Proceed to I/O Multiplexing to learn how to handle multiple sockets efficiently with select, poll, and epoll. Then build project applications starting with Project Calculator.