Network Programming Fundamentals
Core Elements of Network Programming
The essence of network programming is inter-process communication via network protocols. The core elements include:
• IP Address: Identifies a host in the network (e.g., 192.168.1.1).
• Port Number: Identifies a process on the host (e.g., HTTP default port 80).
• Socket: The endpoint of network communication, a network interface provided by the operating system.
• Protocol: Communication rules (e.g., TCP, UDP, HTTP).
Core Differences Between TCP and UDP
| Feature | TCP | UDP |
|---|---|---|
| Connection Type | Connection-oriented (three-way handshake) | Connectionless (send directly) |
| Reliability | Reliable (ordered, no loss) | Unreliable (possible loss, disorder) |
| Transmission Unit | Byte stream (no message boundaries) | Datagram (with message boundaries) |
| Use Cases | File transfer, email, web browsing | Video streaming, games, real-time communication |
Byte Order (Endianness)
In network communication, different hosts may use different byte orders (big-endian: high byte first; little-endian: low byte first). Use the following functions for conversion:
• htons(): Host to network byte order (short integer).
• htonl(): Host to network byte order (long integer).
• ntohs(): Network to host byte order (short integer).
• ntohl(): Network to host byte order (long integer).
Socket: The Endpoint of Network Communication
A socket is the network interface provided by the operating system, created via the socket function, and is the core object in network programming.
Creating a Socket (socket Function)
The socket function creates a socket descriptor (similar to a file descriptor). Its prototype is:
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
• Parameter Explanation:
• domain: Protocol family (e.g., AF_INET for IPv4, AF_INET6 for IPv6).
• type: Socket type (e.g., SOCK_STREAM for TCP, SOCK_DGRAM for UDP).
• protocol: Specific protocol (usually 0, determined by type).
• Return Value: Returns a socket descriptor (non-negative integer) on success; -1 on failure.
Example: Creating a TCP Socket
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
perror("socket creation failed");
exit(EXIT_FAILURE);
}
Binding an Address (bind Function)
The bind function binds a socket to a local IP address and port number. Its prototype is:
int bind(int sockfd, const struct sockaddr* addr, socklen_t addrlen);
• Parameter Explanation:
• sockfd: Socket descriptor.
• addr: Pointer to a sockaddr structure (must fill in IP and port).
• addrlen: Size of the addr structure.
• Return Value: Returns 0 on success; -1 on failure.
Example: Binding a TCP Socket to Port 8080
#include <netinet/in.h> // Includes sockaddr_in structure
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr)); // Clear structure
server_addr.sin_family = AF_INET; // IPv4
server_addr.sin_port = htons(8080); // Port 8080 (network byte order)
server_addr.sin_addr.s_addr = INADDR_ANY; // Listen on all local IPs
if (bind(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
perror("bind failed");
close(sockfd);
exit(EXIT_FAILURE);
}
Listening for Connections (listen Function, TCP Only)
The listen function puts the socket into listening state to wait for client connections. Its prototype is:
int listen(int sockfd, int backlog);
• Parameter Explanation:
• sockfd: Bound socket descriptor.
• backlog: Maximum number of pending connections (length of the incomplete connection queue).
• Return Value: Returns 0 on success; -1 on failure.
Example: TCP Socket Listening
if (listen(sockfd, 5) == -1) { // Allow up to 5 pending connections
perror("listen failed");
close(sockfd);
exit(EXIT_FAILURE);
}
Accepting Connections (accept Function, TCP Only)
The accept function accepts a client connection request and returns a new socket descriptor (used for communication with the client). Its prototype is:
int accept(int sockfd, struct sockaddr* addr, socklen_t* addrlen);
• Parameter Explanation:
• sockfd: Listening socket descriptor.
• addr: Stores client address information (optional).
• addrlen: Size of the addr structure (input/output parameter).
• Return Value: Returns a new socket descriptor on success; -1 on failure.
Example: TCP Server Accepting Client Connection
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
int client_fd = accept(sockfd, (struct sockaddr*)&client_addr, &client_len);
if (client_fd == -1) {
perror("accept failed");
close(sockfd);
exit(EXIT_FAILURE);
}
printf("Client connected successfully, IP: %s, Port: %d\n",
inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
Connecting to a Server (connect Function, Client Only)
The connect function is used by the client to connect to a server. Its prototype is:
int connect(int sockfd, const struct sockaddr* addr, socklen_t addrlen);
• Parameter Explanation:
• sockfd: Created socket descriptor.
• addr: Server address information (IP and port).
• addrlen: Size of the addr structure.
• Return Value: Returns 0 on success; -1 on failure.
Example: TCP Client Connecting to Server
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8080);
inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr); // Convert IP address
if (connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
perror("connect failed");
close(sockfd);
exit(EXIT_FAILURE);
}
printf("Connected to server successfully\n");
Sending and Receiving Data
Sending and receiving data are the core operations of network communication. The functions differ slightly between TCP and UDP.
TCP Data Send/Receive (send/recv)
// Send data (TCP)
ssize_t send(int sockfd, const void* buf, size_t len, int flags);
// Receive data (TCP)
ssize_t recv(int sockfd, void* buf, size_t len, int flags);
• Parameter Explanation:
• sockfd: Socket descriptor (TCP uses the new descriptor returned by accept).
• buf: Buffer for sending/receiving data.
• len: Data length.
• flags: Control flags (usually 0).
• Return Value: Returns the number of bytes sent/received on success; -1 on failure (TCP) or -1 with errno=EAGAIN (non-blocking mode).
Example: TCP Client Sending Data
char* msg = "Hello, Server!";
ssize_t sent = send(client_fd, msg, strlen(msg), 0);
if (sent == -1) {
perror("send failed");
close(client_fd);
exit(EXIT_FAILURE);
}
printf("Sent %zd bytes\n", sent);
UDP Data Send/Receive (sendto/recvfrom)
// Send data (UDP)
ssize_t sendto(int sockfd, const void* buf, size_t len, int flags,
const struct sockaddr* dest_addr, socklen_t addrlen);
// Receive data (UDP)
ssize_t recvfrom(int sockfd, void* buf, size_t len, int flags,
struct sockaddr* src_addr, socklen_t* addrlen);
• Parameter Explanation:
• dest_addr (send): Target server address.
• src_addr (receive): Stores sender address information.
Example: UDP Server Receiving Data
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
char buffer[1024];
ssize_t received = recvfrom(sockfd, buffer, sizeof(buffer), 0,
(struct sockaddr*)&client_addr, &client_len);
if (received == -1) {
perror("recvfrom failed");
close(sockfd);
exit(EXIT_FAILURE);
}
buffer[received] = '\0'; // String terminator
printf("Received data: %s, from IP: %s, Port: %d\n", buffer,
inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
TCP Server and Client Practice
TCP Server Implementation Steps
- Create socket (
socket). - Bind address (
bind). - Listen for connections (
listen). - Loop to accept client connections (
accept). - Communicate with client (
recv/send). - Close connection (
close).
Example: 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 sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) { perror("socket failed"); exit(EXIT_FAILURE); }
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
server_addr.sin_addr.s_addr = INADDR_ANY;
if (bind(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
perror("bind failed"); close(sockfd); exit(EXIT_FAILURE);
}
if (listen(sockfd, 5) == -1) {
perror("listen failed"); close(sockfd); exit(EXIT_FAILURE);
}
printf("TCP server started, listening on port %d...\n", PORT);
while (1) {
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
int client_fd = accept(sockfd, (struct sockaddr*)&client_addr, &client_len);
if (client_fd == -1) {
perror("accept failed"); continue;
}
char buffer[BUFFER_SIZE];
ssize_t received = recv(client_fd, buffer, sizeof(buffer), 0);
if (received == -1) {
perror("recv failed"); close(client_fd); continue;
}
buffer[received] = '\0';
printf("Received data: %s\n", buffer);
send(client_fd, buffer, received, 0); // Echo data
close(client_fd); // Close client connection
}
close(sockfd);
return 0;
}
TCP Client Implementation Steps
- Create socket (
socket). - Connect to server (
connect). - Send data (
send). - Receive response (
recv). - Close connection (
close).
Example: 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 sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) { perror("socket failed"); exit(EXIT_FAILURE); }
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr);
if (connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
perror("connect failed"); close(sockfd); exit(EXIT_FAILURE);
}
char* msg = "Hello, TCP Server!";
send(sockfd, msg, strlen(msg), 0);
printf("Sent: %s\n", msg);
char buffer[BUFFER_SIZE];
ssize_t received = recv(sockfd, buffer, sizeof(buffer), 0);
if (received == -1) { perror("recv failed"); close(sockfd); exit(EXIT_FAILURE); }
buffer[received] = '\0';
printf("Received response: %s\n", buffer);
close(sockfd);
return 0;
}
UDP Server and Client Practice
UDP Server Implementation Steps
- Create socket (
socket). - Bind address (
bind). - Loop to receive data (
recvfrom). - Send response (
sendto, optional).
Example: UDP Time Server
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <time.h>
#define PORT 8080
#define BUFFER_SIZE 1024
int main() {
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd == -1) { perror("socket failed"); exit(EXIT_FAILURE); }
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
server_addr.sin_addr.s_addr = INADDR_ANY;
if (bind(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
perror("bind failed"); close(sockfd); exit(EXIT_FAILURE);
}
printf("UDP time server started, listening on port %d...\n", PORT);
while (1) {
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
char buffer[BUFFER_SIZE];
ssize_t received = recvfrom(sockfd, buffer, sizeof(buffer), 0,
(struct sockaddr*)&client_addr, &client_len);
if (received == -1) {
perror("recvfrom failed"); continue;
}
buffer[received] = '\0';
time_t now = time(NULL);
char* time_str = ctime(&now);
sendto(sockfd, time_str, strlen(time_str), 0,
(struct sockaddr*)&client_addr, client_len);
}
close(sockfd);
return 0;
}
UDP Client Implementation Steps
- Create socket (
socket). - Send data (
sendto). - Receive response (
recvfrom). - Close socket (
close).
Example: UDP Time 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 sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd == -1) { perror("socket failed"); exit(EXIT_FAILURE); }
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr);
char* msg = "Request time";
sendto(sockfd, msg, strlen(msg), 0, (struct sockaddr*)&server_addr, sizeof(server_addr));
printf("Sent time request\n");
struct sockaddr_in server_resp;
socklen_t server_len = sizeof(server_resp);
char buffer[BUFFER_SIZE];
ssize_t received = recvfrom(sockfd, buffer, sizeof(buffer), 0,
(struct sockaddr*)&server_resp, &server_len);
if (received == -1) { perror("recvfrom failed"); close(sockfd); exit(EXIT_FAILURE); }
buffer[received] = '\0';
printf("Server time: %s", buffer);
close(sockfd);
return 0;
}
Advanced Network Programming
Multithreaded TCP Server
A single-threaded TCP server can only handle one client connection at a time. A multithreaded server creates a separate thread for each client to achieve concurrency. A multithreaded server can handle multiple client connections simultaneously, improving server concurrency.
Example: Multithreaded TCP Server
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <pthread.h>
#define PORT 8080
#define MAX_CLIENTS 5
#define BUFFER_SIZE 1024
void *handle_client(void *arg) {
int client_socket = *(int *)arg;
char buffer[BUFFER_SIZE];
ssize_t bytes_received = recv(client_socket, buffer, BUFFER_SIZE - 1, 0);
if (bytes_received <= 0) {
perror("Receive failed");
goto cleanup;
}
buffer[bytes_received] = '\0';
printf("Received: %s\n", buffer);
// Respond to client
const char *response = "Hello from server";
send(client_socket, response, strlen(response), 0);
cleanup:
close(client_socket);
free(arg);
pthread_exit(NULL);
}
int main() {
int server_socket, client_socket;
struct sockaddr_in server_addr, client_addr;
socklen_t addr_len = sizeof(struct sockaddr_in);
// Create socket
if ((server_socket = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("Socket creation failed");
exit(EXIT_FAILURE);
}
// Set address structure
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(PORT);
// Bind socket
if (bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
perror("Bind failed");
exit(EXIT_FAILURE);
}
// Start listening
if (listen(server_socket, MAX_CLIENTS) == -1) {
perror("Listen failed");
exit(EXIT_FAILURE);
}
printf("Server listening on port %d\n", PORT);
while (1) {
// Accept client connection
if ((client_socket = accept(server_socket, (struct sockaddr *)&client_addr, &addr_len)) == -1) {
perror("Accept failed");
continue;
}
int *client_socket_ptr = malloc(sizeof(int));
*client_socket_ptr = client_socket;
pthread_t thread_id;
if (pthread_create(&thread_id, NULL, handle_client, client_socket_ptr) != 0) {
perror("Thread creation failed");
free(client_socket_ptr);
close(client_socket);
continue;
}
pthread_detach(thread_id);
}
close(server_socket);
return 0;
}
Pros and Cons:
• Advantages: Simple to implement, utilizes multi-core CPUs.
• Disadvantages: High overhead for thread creation/destruction, complex inter-thread synchronization (e.g., shared data requires locking).
Non-blocking I/O
Non-blocking I/O allows the server to continue performing other tasks while waiting for client connections or data. Non-blocking I/O sets the socket to non-blocking mode, causing functions like recv/send to return immediately (returning EAGAIN or EWOULDBLOCK if no data is available), avoiding process blocking.
Example: Setting a Socket to Non-blocking Mode
#include <fcntl.h>
int set_non_blocking(int sockfd) {
int flags = fcntl(sockfd, F_GETFL, 0);
if (flags == -1) return -1;
flags |= O_NONBLOCK; // Add non-blocking flag
return fcntl(sockfd, F_SETFL, flags);
}
Use Cases:
• Polling multiple sockets (e.g., game servers).
• Avoiding long blocking (e.g., real-time communication).
Example: Non-blocking TCP Server
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <fcntl.h>
#define PORT 8080
#define BUFFER_SIZE 1024
int main() {
int server_socket, client_socket;
struct sockaddr_in server_addr, client_addr;
socklen_t addr_len = sizeof(struct sockaddr_in);
// Create socket
if ((server_socket = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("Socket creation failed");
exit(EXIT_FAILURE);
}
// Set address structure
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(PORT);
// Bind socket
if (bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
perror("Bind failed");
exit(EXIT_FAILURE);
}
// Set socket to non-blocking mode
if (fcntl(server_socket, F_SETFL, O_NONBLOCK) == -1) {
perror("Set non-blocking mode failed");
exit(EXIT_FAILURE);
}
// Start listening
if (listen(server_socket, 5) == -1) {
perror("Listen failed");
exit(EXIT_FAILURE);
}
printf("Server listening on port %d\n", PORT);
while (1) {
// Accept client connection
client_socket = accept(server_socket, (struct sockaddr *)&client_addr, &addr_len);
if (client_socket == -1 && errno == EAGAIN) {
continue; // No connection request
} else if (client_socket == -1) {
perror("Accept failed");
continue;
}
char buffer[BUFFER_SIZE];
ssize_t bytes_received = recv(client_socket, buffer, BUFFER_SIZE - 1, 0);
if (bytes_received <= 0) {
perror("Receive failed");
close(client_socket);
continue;
}
buffer[bytes_received] = '\0';
printf("Received: %s\n", buffer);
// Respond to client
const char *response = "Hello from server";
send(client_socket, response, strlen(response), 0);
close(client_socket);
}
close(server_socket);
return 0;
}
I/O Multiplexing
I/O multiplexing allows a server to monitor multiple sockets for activity simultaneously, improving efficiency.
I/O multiplexing uses a single system call to monitor multiple sockets, notifying the application when a socket becomes readable/writable, avoiding the need to create a thread per socket.
select System Call
select can monitor multiple file descriptors (sockets) for readability, writability, and exceptional conditions.
#include <sys/select.h>
int select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, struct timeval* timeout);
• Parameter Explanation:
• nfds: Maximum file descriptor value + 1.
• readfds: Set of file descriptors to monitor for readability.
• writefds: Set of file descriptors to monitor for writability.
• exceptfds: Set of file descriptors to monitor for exceptions.
• timeout: Timeout duration (NULL for blocking).
epoll (Linux-specific)
epoll is a high-performance I/O multiplexing mechanism provided by Linux, supporting event-driven operation (only notifying active sockets), outperforming select and poll.
#include <sys/epoll.h>
// Create epoll instance
int epoll_fd = epoll_create1(0);
// Add event to monitor
struct epoll_event event;
event.data.fd = sockfd;
event.events = EPOLLIN | EPOLLOUT; // Monitor readable and writable
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sockfd, &event);
// Wait for events
struct epoll_event events[MAX_EVENTS];
int n = epoll_wait(epoll_fd, events, MAX_EVENTS, -1); // Block waiting
for (int i = 0; i < n; i++) {
if (events[i].events & EPOLLIN) {
// Handle readable event
}
if (events[i].events & EPOLLOUT) {
// Handle writable event
}
}
Advantages:
• Efficient: Event-driven, only processes active sockets.
• Scalable: Supports a large number of connections (e.g., 100,000+).
Example: I/O Multiplexing Server Using select
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/select.h>
#define PORT 8080
#define BUFFER_SIZE 1024
#define MAX_CLIENTS 5
int main() {
int server_socket, client_socket;
struct sockaddr_in server_addr, client_addr;
socklen_t addr_len = sizeof(struct sockaddr_in);
// Create socket
if ((server_socket = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("Socket creation failed");
exit(EXIT_FAILURE);
}
// Set address structure
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(PORT);
// Bind socket
if (bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
perror("Bind failed");
exit(EXIT_FAILURE);
}
// Start listening
if (listen(server_socket, MAX_CLIENTS) == -1) {
perror("Listen failed");
exit(EXIT_FAILURE);
}
printf("Server listening on port %d\n", PORT);
fd_set readfds;
FD_ZERO(&readfds);
FD_SET(server_socket, &readfds);
int max_fd = server_socket;
while (1) {
fd_set tmp_fds = readfds;
int ret = select(max_fd + 1, &tmp_fds, NULL, NULL, NULL);
if (ret == -1) {
perror("Select failed");
continue;
}
if (FD_ISSET(server_socket, &tmp_fds)) {
// Accept client connection
if ((client_socket = accept(server_socket, (struct sockaddr *)&client_addr, &addr_len)) == -1) {
perror("Accept failed");
continue;
}
FD_SET(client_socket, &readfds);
if (client_socket > max_fd) {
max_fd = client_socket;
}
}
for (int i = 0; i <= max_fd; i++) {
if (FD_ISSET(i, &tmp_fds)) {
if (i == server_socket) {
continue;
}
char buffer[BUFFER_SIZE];
ssize_t bytes_received = recv(i, buffer, BUFFER_SIZE - 1, 0);
if (bytes_received <= 0) {
perror("Receive failed");
FD_CLR(i, &readfds);
close(i);
continue;
}
buffer[bytes_received] = '\0';
printf("Received: %s\n", buffer);
// Respond to client
const char *response = "Hello from server";
send(i, response, strlen(response), 0);
}
}
}
close(server_socket);
return 0;
}
I/O Multiplexing Using epoll
epoll is a high-performance I/O multiplexing mechanism in Linux, especially suitable for handling a large number of concurrent connections.
Example: I/O Multiplexing Server Using epoll
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#define PORT 8080
#define BUFFER_SIZE 1024
#define MAX_EVENTS 10
int main() {
int server_socket, client_socket;
struct sockaddr_in server_addr, client_addr;
socklen_t addr_len = sizeof(struct sockaddr_in);
// Create socket
if ((server_socket = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("Socket creation failed");
exit(EXIT_FAILURE);
}
// Set address structure
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(PORT);
// Bind socket
if (bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
perror("Bind failed");
exit(EXIT_FAILURE);
}
// Start listening
if (listen(server_socket, 5) == -1) {
perror("Listen failed");
exit(EXIT_FAILURE);
}
printf("Server listening on port %d\n", PORT);
// Create epoll instance
int epoll_fd = epoll_create1(0);
if (epoll_fd == -1) {
perror("Epoll create failed");
exit(EXIT_FAILURE);
}
// Add server socket to epoll instance
struct epoll_event event;
event.events = EPOLLIN | EPOLLET;
event.data.fd = server_socket;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_socket, &event) == -1) {
perror("Epoll add failed");
exit(EXIT_FAILURE);
}
while (1) {
struct epoll_event events[MAX_EVENTS];
int num_events = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
if (num_events == -1) {
perror("Epoll wait failed");
continue;
}
for (int i = 0; i < num_events; i++) {
if (events[i].data.fd == server_socket) {
// Accept client connection
if ((client_socket = accept(server_socket, (struct sockaddr *)&client_addr, &addr_len)) == -1) {
perror("Accept failed");
continue;
}
// Add client socket to epoll instance
event.events = EPOLLIN | EPOLLET;
event.data.fd = client_socket;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_socket, &event) == -1) {
perror("Epoll add failed");
close(client_socket);
continue;
}
} else {
char buffer[BUFFER_SIZE];
ssize_t bytes_received = recv(events[i].data.fd, buffer, BUFFER_SIZE - 1, 0);
if (bytes_received <= 0) {
perror("Receive failed");
// Remove client socket from epoll instance
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, events[i].data.fd, NULL);
close(events[i].data.fd);
continue;
}
buffer[bytes_received] = '\0';
printf("Received: %s\n", buffer);
// Respond to client
const char *response = "Hello from server";
send(events[i].data.fd, response, strlen(response), 0);
}
}
}
// Clean up resources
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, server_socket, NULL);
close(epoll_fd);
close(server_socket);
return 0;
}
Summary
- Multithreaded Server: Suitable for handling a small number of high-load connections, each in a separate thread.
- Non-blocking I/O: Allows the server to continue other tasks while waiting for I/O operations.
- I/O Multiplexing:
select: Simple to use, but inefficient with many connections.epoll: High performance and suitable for handling a large number of concurrent connections.



