Network programming is the foundation of modern distributed systems, enabling applications to communicate across the internet and local networks. C provides the lowest-level access to networking APIs, giving developers complete control over how data flows between systems. This comprehensive guide covers everything from basic socket programming to advanced techniques for building robust network applications.
Understanding Sockets
A socket is an endpoint for communication between two programs. Think of it as a door through which data enters and exits a program. The Berkeley sockets API, used in Unix-like systems and Windows (Winsock), provides a standardized interface for network programming.
Client Server | | |------ Socket() ----------------->| | | |------ Connect() ---------------->|------ Socket() -----| | |------ Bind() -------| | |------ Listen() -----| | |------ Accept() -----| | | | |<----- Data Exchange ------------>| | | | | |------ Close() ------------------>|------ Close() -----|
Socket Types
- Stream Sockets (SOCK_STREAM): TCP - reliable, connection-oriented
- Datagram Sockets (SOCK_DGRAM): UDP - unreliable, connectionless
- Raw Sockets (SOCK_RAW): Direct access to network protocols
Basic Socket Functions
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <netdb.h> // Socket creation int socket(int domain, int type, int protocol); // domain: AF_INET (IPv4), AF_INET6 (IPv6), AF_UNIX (local) // type: SOCK_STREAM (TCP), SOCK_DGRAM (UDP) // protocol: 0 (default for given type) // Binding (server) int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); // Listening (server) int listen(int sockfd, int backlog); // Accepting connections (server) int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); // Connecting (client) int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); // Sending data ssize_t send(int sockfd, const void *buf, size_t len, int flags); ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen); // Receiving data ssize_t recv(int sockfd, void *buf, size_t len, int flags); ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen); // Closing int close(int sockfd); int shutdown(int sockfd, int how);
TCP Server Implementation
1. Basic TCP Echo Server
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#define PORT 8080
#define BUFFER_SIZE 1024
#define BACKLOG 5
volatile int server_running = 1;
void handle_signal(int sig) {
server_running = 0;
}
int main() {
int server_fd, client_fd;
struct sockaddr_in server_addr, client_addr;
socklen_t client_len = sizeof(client_addr);
char buffer[BUFFER_SIZE];
ssize_t bytes_read;
// Handle Ctrl+C gracefully
signal(SIGINT, handle_signal);
// Create socket
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd < 0) {
perror("socket creation failed");
exit(EXIT_FAILURE);
}
// Allow socket reuse (avoid "address already in use" errors)
int opt = 1;
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
perror("setsockopt failed");
close(server_fd);
exit(EXIT_FAILURE);
}
// Configure server address
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY; // Listen on all interfaces
server_addr.sin_port = htons(PORT); // Convert to network byte order
// Bind socket to address
if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
perror("bind failed");
close(server_fd);
exit(EXIT_FAILURE);
}
// Start listening
if (listen(server_fd, BACKLOG) < 0) {
perror("listen failed");
close(server_fd);
exit(EXIT_FAILURE);
}
printf("Echo server listening on port %d\n", PORT);
while (server_running) {
// Accept client connection (blocking)
client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_len);
if (client_fd < 0) {
if (server_running) {
perror("accept failed");
}
continue;
}
printf("Client connected: %s:%d\n",
inet_ntoa(client_addr.sin_addr),
ntohs(client_addr.sin_port));
// Handle client
while ((bytes_read = recv(client_fd, buffer, BUFFER_SIZE - 1, 0)) > 0) {
buffer[bytes_read] = '\0';
printf("Received: %s", buffer);
// Echo back
if (send(client_fd, buffer, bytes_read, 0) < 0) {
perror("send failed");
break;
}
}
close(client_fd);
printf("Client disconnected\n");
}
close(server_fd);
printf("Server shutdown\n");
return 0;
}
2. TCP 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>
#include <netdb.h>
#define PORT 8080
#define BUFFER_SIZE 1024
int main(int argc, char *argv[]) {
int sock_fd;
struct sockaddr_in server_addr;
char buffer[BUFFER_SIZE];
ssize_t bytes_sent, bytes_received;
if (argc != 2) {
fprintf(stderr, "Usage: %s <server_ip>\n", argv[0]);
exit(EXIT_FAILURE);
}
// Create socket
sock_fd = socket(AF_INET, SOCK_STREAM, 0);
if (sock_fd < 0) {
perror("socket creation failed");
exit(EXIT_FAILURE);
}
// Configure server address
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
// Convert IP address from text to binary
if (inet_pton(AF_INET, argv[1], &server_addr.sin_addr) <= 0) {
perror("invalid address");
close(sock_fd);
exit(EXIT_FAILURE);
}
// Connect to server
if (connect(sock_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
perror("connection failed");
close(sock_fd);
exit(EXIT_FAILURE);
}
printf("Connected to server %s:%d\n", argv[1], PORT);
// Communication loop
while (1) {
printf("Enter message (or 'quit' to exit): ");
fgets(buffer, BUFFER_SIZE, stdin);
// Remove newline
buffer[strcspn(buffer, "\n")] = 0;
if (strcmp(buffer, "quit") == 0) {
break;
}
// Send message
bytes_sent = send(sock_fd, buffer, strlen(buffer), 0);
if (bytes_sent < 0) {
perror("send failed");
break;
}
// Receive echo
bytes_received = recv(sock_fd, buffer, BUFFER_SIZE - 1, 0);
if (bytes_received < 0) {
perror("recv failed");
break;
}
buffer[bytes_received] = '\0';
printf("Server echoed: %s\n", buffer);
}
close(sock_fd);
printf("Connection closed\n");
return 0;
}
UDP Server and Client
UDP Echo Server
#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 9090
#define BUFFER_SIZE 1024
int main() {
int sock_fd;
struct sockaddr_in server_addr, client_addr;
socklen_t client_len = sizeof(client_addr);
char buffer[BUFFER_SIZE];
ssize_t bytes_received;
// Create UDP socket
sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
if (sock_fd < 0) {
perror("socket creation failed");
exit(EXIT_FAILURE);
}
// Configure server address
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(PORT);
// Bind socket
if (bind(sock_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
perror("bind failed");
close(sock_fd);
exit(EXIT_FAILURE);
}
printf("UDP echo server listening on port %d\n", PORT);
while (1) {
// Receive data (no connection needed)
bytes_received = recvfrom(sock_fd, buffer, BUFFER_SIZE - 1, 0,
(struct sockaddr*)&client_addr, &client_len);
if (bytes_received < 0) {
perror("recvfrom failed");
continue;
}
buffer[bytes_received] = '\0';
printf("Received from %s:%d: %s\n",
inet_ntoa(client_addr.sin_addr),
ntohs(client_addr.sin_port),
buffer);
// Echo back
if (sendto(sock_fd, buffer, bytes_received, 0,
(struct sockaddr*)&client_addr, client_len) < 0) {
perror("sendto failed");
}
}
close(sock_fd);
return 0;
}
UDP 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 9090
#define BUFFER_SIZE 1024
int main(int argc, char *argv[]) {
int sock_fd;
struct sockaddr_in server_addr;
char buffer[BUFFER_SIZE];
socklen_t addr_len = sizeof(server_addr);
ssize_t bytes_sent, bytes_received;
if (argc != 2) {
fprintf(stderr, "Usage: %s <server_ip>\n", argv[0]);
exit(EXIT_FAILURE);
}
// Create UDP socket
sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
if (sock_fd < 0) {
perror("socket creation failed");
exit(EXIT_FAILURE);
}
// Configure server address
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
if (inet_pton(AF_INET, argv[1], &server_addr.sin_addr) <= 0) {
perror("invalid address");
close(sock_fd);
exit(EXIT_FAILURE);
}
printf("UDP client ready (type 'quit' to exit)\n");
while (1) {
printf("Enter message: ");
fgets(buffer, BUFFER_SIZE, stdin);
buffer[strcspn(buffer, "\n")] = 0;
if (strcmp(buffer, "quit") == 0) break;
// Send message
bytes_sent = sendto(sock_fd, buffer, strlen(buffer), 0,
(struct sockaddr*)&server_addr, addr_len);
if (bytes_sent < 0) {
perror("sendto failed");
break;
}
// Receive response
bytes_received = recvfrom(sock_fd, buffer, BUFFER_SIZE - 1, 0,
NULL, NULL);
if (bytes_received < 0) {
perror("recvfrom failed");
break;
}
buffer[bytes_received] = '\0';
printf("Server echoed: %s\n", buffer);
}
close(sock_fd);
return 0;
}
Multi-Client Server with Fork
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
#define PORT 8080
#define BUFFER_SIZE 1024
#define BACKLOG 10
void handle_client(int client_fd, struct sockaddr_in client_addr) {
char buffer[BUFFER_SIZE];
ssize_t bytes_read;
printf("Child process %d handling client %s:%d\n",
getpid(),
inet_ntoa(client_addr.sin_addr),
ntohs(client_addr.sin_port));
while ((bytes_read = recv(client_fd, buffer, BUFFER_SIZE - 1, 0)) > 0) {
buffer[bytes_read] = '\0';
printf("Received from client: %s", buffer);
// Echo back
send(client_fd, buffer, bytes_read, 0);
}
close(client_fd);
printf("Child %d: client disconnected\n", getpid());
exit(0);
}
void reap_zombies(int sig) {
while (waitpid(-1, NULL, WNOHANG) > 0);
}
int main() {
int server_fd, client_fd;
struct sockaddr_in server_addr, client_addr;
socklen_t client_len = sizeof(client_addr);
// Reap zombie processes
signal(SIGCHLD, reap_zombies);
// Create socket
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd < 0) {
perror("socket creation failed");
exit(EXIT_FAILURE);
}
// Allow socket reuse
int opt = 1;
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
// Configure address
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(PORT);
// Bind
if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
perror("bind failed");
close(server_fd);
exit(EXIT_FAILURE);
}
// Listen
if (listen(server_fd, BACKLOG) < 0) {
perror("listen failed");
close(server_fd);
exit(EXIT_FAILURE);
}
printf("Multi-client server listening on port %d\n", PORT);
while (1) {
// Accept client
client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_len);
if (client_fd < 0) {
perror("accept failed");
continue;
}
// Fork new process for client
pid_t pid = fork();
if (pid < 0) {
perror("fork failed");
close(client_fd);
} else if (pid == 0) {
// Child process
close(server_fd);
handle_client(client_fd, client_addr);
} else {
// Parent process
close(client_fd);
printf("Parent: forked child %d for client\n", pid);
}
}
close(server_fd);
return 0;
}
Multi-Client Server with Threads
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define PORT 8080
#define BUFFER_SIZE 1024
#define BACKLOG 10
#define MAX_THREADS 100
typedef struct {
int client_fd;
struct sockaddr_in client_addr;
} ClientInfo;
void* handle_client_thread(void *arg) {
ClientInfo *info = (ClientInfo*)arg;
char buffer[BUFFER_SIZE];
ssize_t bytes_read;
printf("Thread %lu handling client %s:%d\n",
pthread_self(),
inet_ntoa(info->client_addr.sin_addr),
ntohs(info->client_addr.sin_port));
while ((bytes_read = recv(info->client_fd, buffer, BUFFER_SIZE - 1, 0)) > 0) {
buffer[bytes_read] = '\0';
printf("Received: %s", buffer);
send(info->client_fd, buffer, bytes_read, 0);
}
close(info->client_fd);
free(info);
return NULL;
}
int main() {
int server_fd;
struct sockaddr_in server_addr;
// Create socket
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd < 0) {
perror("socket creation failed");
exit(EXIT_FAILURE);
}
// Allow socket reuse
int opt = 1;
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
// Configure address
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(PORT);
// Bind
if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
perror("bind failed");
close(server_fd);
exit(EXIT_FAILURE);
}
// Listen
if (listen(server_fd, BACKLOG) < 0) {
perror("listen failed");
close(server_fd);
exit(EXIT_FAILURE);
}
printf("Threaded server listening on port %d\n", PORT);
while (1) {
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
int client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_len);
if (client_fd < 0) {
perror("accept failed");
continue;
}
// Create client info
ClientInfo *info = (ClientInfo*)malloc(sizeof(ClientInfo));
info->client_fd = client_fd;
info->client_addr = client_addr;
// Create thread for client
pthread_t thread;
if (pthread_create(&thread, NULL, handle_client_thread, info) != 0) {
perror("pthread_create failed");
close(client_fd);
free(info);
} else {
pthread_detach(thread); // Auto-cleanup when done
}
}
close(server_fd);
return 0;
}
Non-Blocking I/O with select()
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#define PORT 8080
#define BUFFER_SIZE 1024
#define MAX_CLIENTS 30
int main() {
int server_fd, client_fds[MAX_CLIENTS];
struct sockaddr_in server_addr, client_addr;
socklen_t client_len = sizeof(client_addr);
fd_set read_fds;
int max_fd;
char buffer[BUFFER_SIZE];
// Initialize client array
for (int i = 0; i < MAX_CLIENTS; i++) {
client_fds[i] = -1;
}
// Create socket
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd < 0) {
perror("socket creation failed");
exit(EXIT_FAILURE);
}
// Allow socket reuse
int opt = 1;
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
// Configure address
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(PORT);
// Bind
if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
perror("bind failed");
close(server_fd);
exit(EXIT_FAILURE);
}
// Listen
if (listen(server_fd, 10) < 0) {
perror("listen failed");
close(server_fd);
exit(EXIT_FAILURE);
}
printf("Select-based server listening on port %d\n", PORT);
while (1) {
// Clear and set file descriptor set
FD_ZERO(&read_fds);
FD_SET(server_fd, &read_fds);
max_fd = server_fd;
// Add client sockets
for (int i = 0; i < MAX_CLIENTS; i++) {
int fd = client_fds[i];
if (fd > 0) {
FD_SET(fd, &read_fds);
if (fd > max_fd) max_fd = fd;
}
}
// Wait for activity
int activity = select(max_fd + 1, &read_fds, NULL, NULL, NULL);
if (activity < 0) {
perror("select failed");
continue;
}
// New connection
if (FD_ISSET(server_fd, &read_fds)) {
int new_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_len);
if (new_fd < 0) {
perror("accept failed");
continue;
}
// Add to client array
int i;
for (i = 0; i < MAX_CLIENTS; i++) {
if (client_fds[i] == -1) {
client_fds[i] = new_fd;
break;
}
}
if (i == MAX_CLIENTS) {
printf("Max clients reached, rejecting connection\n");
close(new_fd);
} else {
printf("New client connected: %s:%d (fd=%d)\n",
inet_ntoa(client_addr.sin_addr),
ntohs(client_addr.sin_port),
new_fd);
}
}
// Handle data from clients
for (int i = 0; i < MAX_CLIENTS; i++) {
int fd = client_fds[i];
if (fd > 0 && FD_ISSET(fd, &read_fds)) {
ssize_t bytes = recv(fd, buffer, BUFFER_SIZE - 1, 0);
if (bytes <= 0) {
// Client disconnected
close(fd);
client_fds[i] = -1;
printf("Client %d disconnected\n", fd);
} else {
buffer[bytes] = '\0';
printf("Received from %d: %s", fd, buffer);
// Echo back
send(fd, buffer, bytes, 0);
}
}
}
}
close(server_fd);
return 0;
}
HTTP Server Example
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#include <fcntl.h>
#define PORT 8080
#define BUFFER_SIZE 4096
const char* get_mime_type(const char *path) {
const char *ext = strrchr(path, '.');
if (!ext) return "text/plain";
if (strcmp(ext, ".html") == 0) return "text/html";
if (strcmp(ext, ".css") == 0) return "text/css";
if (strcmp(ext, ".js") == 0) return "application/javascript";
if (strcmp(ext, ".jpg") == 0) return "image/jpeg";
if (strcmp(ext, ".png") == 0) return "image/png";
if (strcmp(ext, ".txt") == 0) return "text/plain";
return "application/octet-stream";
}
void serve_file(int client_fd, const char *path) {
char buffer[BUFFER_SIZE];
struct stat st;
// Check if file exists
if (stat(path, &st) != 0) {
// 404 Not Found
const char *response = "HTTP/1.1 404 Not Found\r\n"
"Content-Type: text/html\r\n"
"\r\n"
"<h1>404 Not Found</h1>";
send(client_fd, response, strlen(response), 0);
return;
}
// Open file
int file_fd = open(path, O_RDONLY);
if (file_fd < 0) {
const char *response = "HTTP/1.1 500 Internal Server Error\r\n"
"\r\n";
send(client_fd, response, strlen(response), 0);
return;
}
// Send HTTP headers
const char *mime = get_mime_type(path);
char headers[256];
snprintf(headers, sizeof(headers),
"HTTP/1.1 200 OK\r\n"
"Content-Type: %s\r\n"
"Content-Length: %ld\r\n"
"Connection: close\r\n"
"\r\n",
mime, st.st_size);
send(client_fd, headers, strlen(headers), 0);
// Send file content
ssize_t bytes;
while ((bytes = read(file_fd, buffer, BUFFER_SIZE)) > 0) {
send(client_fd, buffer, bytes, 0);
}
close(file_fd);
}
int main() {
int server_fd, client_fd;
struct sockaddr_in server_addr, client_addr;
socklen_t client_len = sizeof(client_addr);
char buffer[BUFFER_SIZE];
// Create socket
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd < 0) {
perror("socket creation failed");
exit(EXIT_FAILURE);
}
// Allow socket reuse
int opt = 1;
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
// Configure address
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(PORT);
// Bind
if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
perror("bind failed");
close(server_fd);
exit(EXIT_FAILURE);
}
// Listen
if (listen(server_fd, 10) < 0) {
perror("listen failed");
close(server_fd);
exit(EXIT_FAILURE);
}
printf("HTTP server listening on port %d\n", PORT);
while (1) {
client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_len);
if (client_fd < 0) {
perror("accept failed");
continue;
}
printf("HTTP request from %s:%d\n",
inet_ntoa(client_addr.sin_addr),
ntohs(client_addr.sin_port));
// Read request
ssize_t bytes = recv(client_fd, buffer, BUFFER_SIZE - 1, 0);
if (bytes > 0) {
buffer[bytes] = '\0';
// Parse request line
char method[16], path[256], version[16];
sscanf(buffer, "%s %s %s", method, path, version);
// Default to index.html
if (strcmp(path, "/") == 0) {
path[0] = '\0';
strcat(path, "index.html");
} else {
// Remove leading slash
memmove(path, path + 1, strlen(path));
}
printf(" %s %s\n", method, path);
// Serve file
serve_file(client_fd, path);
}
close(client_fd);
}
close(server_fd);
return 0;
}
DNS Resolution
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netdb.h>
#include <arpa/inet.h>
void resolve_hostname(const char *hostname) {
struct addrinfo hints, *res, *p;
char ipstr[INET6_ADDRSTRLEN];
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC; // IPv4 or IPv6
hints.ai_socktype = SOCK_STREAM;
int status = getaddrinfo(hostname, NULL, &hints, &res);
if (status != 0) {
fprintf(stderr, "getaddrinfo error: %s\n", gai_strerror(status));
return;
}
printf("Resolved addresses for %s:\n", hostname);
for (p = res; p != NULL; p = p->ai_next) {
void *addr;
char *ipver;
if (p->ai_family == AF_INET) {
struct sockaddr_in *ipv4 = (struct sockaddr_in*)p->ai_addr;
addr = &(ipv4->sin_addr);
ipver = "IPv4";
} else {
struct sockaddr_in6 *ipv6 = (struct sockaddr_in6*)p->ai_addr;
addr = &(ipv6->sin6_addr);
ipver = "IPv6";
}
inet_ntop(p->ai_family, addr, ipstr, sizeof(ipstr));
printf(" %s: %s\n", ipver, ipstr);
}
freeaddrinfo(res);
}
int main(int argc, char *argv[]) {
if (argc != 2) {
fprintf(stderr, "Usage: %s <hostname>\n", argv[0]);
return 1;
}
resolve_hostname(argv[1]);
return 0;
}
Error Handling and Utilities
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
// Wrapper functions with error checking
int Socket(int domain, int type, int protocol) {
int fd = socket(domain, type, protocol);
if (fd < 0) {
perror("socket");
exit(EXIT_FAILURE);
}
return fd;
}
void Bind(int fd, const struct sockaddr *addr, socklen_t len) {
if (bind(fd, addr, len) < 0) {
perror("bind");
exit(EXIT_FAILURE);
}
}
void Listen(int fd, int backlog) {
if (listen(fd, backlog) < 0) {
perror("listen");
exit(EXIT_FAILURE);
}
}
int Accept(int fd, struct sockaddr *addr, socklen_t *len) {
int client_fd = accept(fd, addr, len);
if (client_fd < 0) {
perror("accept");
exit(EXIT_FAILURE);
}
return client_fd;
}
void Connect(int fd, const struct sockaddr *addr, socklen_t len) {
if (connect(fd, addr, len) < 0) {
perror("connect");
exit(EXIT_FAILURE);
}
}
// Set socket to non-blocking mode
int set_nonblocking(int fd) {
int flags = fcntl(fd, F_GETFL, 0);
if (flags < 0) return -1;
return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}
// Set socket timeout
int set_socket_timeout(int fd, int seconds) {
struct timeval tv;
tv.tv_sec = seconds;
tv.tv_usec = 0;
if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0) {
return -1;
}
if (setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)) < 0) {
return -1;
}
return 0;
}
// Get local address
void get_local_address(int fd, char *buffer, size_t size) {
struct sockaddr_in addr;
socklen_t len = sizeof(addr);
if (getsockname(fd, (struct sockaddr*)&addr, &len) == 0) {
snprintf(buffer, size, "%s:%d",
inet_ntoa(addr.sin_addr),
ntohs(addr.sin_port));
} else {
strcpy(buffer, "unknown");
}
}
Best Practices
- Always check return values: Every socket function can fail
- Set SO_REUSEADDR: Prevents "address already in use" errors
- Handle signals: Properly handle SIGPIPE and SIGCHLD
- Use appropriate buffer sizes: Not too small, not too large
- Close sockets properly: Use shutdown() before close() when needed
- Convert to network byte order: Use htons(), htonl(), etc.
- Handle partial sends/receives: Loop until all data is sent/received
- Use non-blocking I/O for high concurrency: select(), poll(), epoll()
- Validate input: Check IP addresses, ports, etc.
- Implement timeouts: Don't block indefinitely
Common Pitfalls
// DON'T: Ignoring return values
send(sock, buffer, len, 0); // May not send all data
// DO: Loop until all data is sent
ssize_t total = 0;
while (total < len) {
ssize_t sent = send(sock, buffer + total, len - total, 0);
if (sent <= 0) break;
total += sent;
}
// DON'T: Not using htons()
addr.sin_port = 8080; // Wrong byte order on big-endian systems
// DO: Convert to network byte order
addr.sin_port = htons(8080);
// DON'T: Not handling SIGPIPE
// Writing to a closed connection causes SIGPIPE and process termination
// DO: Ignore SIGPIPE
signal(SIGPIPE, SIG_IGN);
Conclusion
Network programming in C provides the foundation for building distributed applications. By mastering sockets, you gain direct control over network communication, enabling you to create everything from simple chat servers to complex distributed systems.
Key takeaways:
- Understand the difference between TCP (reliable, connection-oriented) and UDP (unreliable, connectionless)
- Use
select(),poll(), orepoll()for handling multiple connections - Implement proper error handling and resource cleanup
- Consider concurrency models: fork, threads, or event-driven
- Always use network byte order for portability
- Test thoroughly with multiple clients and edge cases
With these skills, you can build robust network applications that communicate efficiently across any distance.