Socket Programming in C: Complete Guide
Socket Programming in C: A Comprehensive Tutorial
Socket programming is a method of enabling communication between two nodes over a network using sockets. One socket acts as a server, listening on a specific IP address and port, while another socket acts as a client, initiating a connection to the server. Socket programming follows a client-server architecture for data exchange.
This tutorial provides an in-depth exploration of socket programming in C: sockets, client-server model, server-side and client-side implementation, TCP/UDP sockets, common pitfalls, and best practices.
Conceptual Introduction & Fundamentals
What is a Socket?
A socket is an endpoint for communication between two processes over a network. It is a combination of an IP address and a port number.
Example: 192.168.1.1:8080
- IP address: Identifies the machine
- Port number: Identifies the specific application/process
Socket Types
| Socket Type | Protocol | Characteristics |
|---|---|---|
Stream Socket (SOCK_STREAM) | TCP | Reliable, connection-oriented |
Datagram Socket (SOCK_DGRAM) | UDP | Unreliable, connectionless |
Client-Server Model
The client-server model is the architecture used in socket programming:
- Server: Listens for incoming connections, processes requests, sends responses.
- Client: Initiates connection to server, sends requests, receives responses.
Why Socket Programming Matters
- Network communication: Enable data exchange between systems.
- Distributed applications: Web servers, chat systems, streaming.
- Real-time collaboration: Multiplayer games, collaborative tools.
- Microservices: Inter-service communication.
- IoT: Device-to-server communication.
Required Headers
c
#include <sys/socket.h> // Core socket functions #include <netinet/in.h> // sockaddr_in structure #include <arpa/inet.h> // inet_pton, inet_ntop #include <unistd.h> // close, read, write #include <string.h> // memset #include <stdio.h> // printf, perror #include <stdlib.h> // exit
Syntax & Core Mechanics
Server-Side Steps
1. Socket Creation
c
int sockfd = socket(domain, type, protocol);
Parameters:
domain:AF_INET(IPv4),AF_INET6(IPv6),AF_LOCAL(same host)type:SOCK_STREAM(TCP),SOCK_DGRAM(UDP)protocol: Usually 0 (auto-selected)
2. Setsockopt (Optional)
Allows address/port reuse.
c
setsockopt(sockfd, level, optname, optval, optlen);
3. Bind
Binds socket to address and port.
c
bind(sockfd, (struct sockaddr*)&address, sizeof(address));
4. Listen
Puts socket in passive mode, waiting for connections.
c
listen(sockfd, backlog);
5. Accept
Accepts incoming connection, creates new socket.
c
int new_socket = accept(sockfd, (struct sockaddr*)&address, &addrlen);
6. Send/Receive
c
send(sockfd, buffer, length, flags); recv(sockfd, buffer, length, flags);
7. Close
c
close(sockfd);
Client-Side Steps
1. Socket Creation (same as server)
c
int client_fd = socket(AF_INET, SOCK_STREAM, 0);
2. Connect
c
connect(sockfd, (struct sockaddr*)&address, sizeof(address));
3. Send/Receive
c
send(sockfd, buffer, length, flags); recv(sockfd, buffer, length, flags);
4. Close
c
close(sockfd);
Full Practical Code Example with Explanation
This section provides complete server and client implementations demonstrating TCP socket communication.
Server Program: server.c
c
/** * server.c * TCP server that listens on port 8080. * Accepts client connections and exchanges messages. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.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(void) { int server_fd, client_fd; struct sockaddr_in address; int opt = 1; socklen_t addrlen = sizeof(address); char buffer[BUFFER_SIZE] = {0}; const char *response = "Hello from server"; /* Step 1: Create socket */ server_fd = socket(AF_INET, SOCK_STREAM, 0); if (server_fd < 0) { perror("Socket creation failed"); return EXIT_FAILURE; } printf("Socket created successfully.\n"); /* Step 2: Set socket options (reuse address/port) */ if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt)) < 0) { perror("setsockopt failed"); close(server_fd); return EXIT_FAILURE; } printf("Socket options set.\n"); /* Configure server address */ address.sin_family = AF_INET; address.sin_addr.s_addr = INADDR_ANY; // Bind to all interfaces address.sin_port = htons(PORT); /* Step 3: Bind socket to address */ if (bind(server_fd, (struct sockaddr*)&address, sizeof(address)) < 0) { perror("Bind failed"); close(server_fd); return EXIT_FAILURE; } printf("Bound to port %d.\n", PORT); /* Step 4: Listen for connections */ if (listen(server_fd, 3) < 0) { perror("Listen failed"); close(server_fd); return EXIT_FAILURE; } printf("Listening for connections...\n"); /* Step 5: Accept client connection */ client_fd = accept(server_fd, (struct sockaddr*)&address, &addrlen); if (client_fd < 0) { perror("Accept failed"); close(server_fd); return EXIT_FAILURE; } printf("Client connected.\n"); /* Step 6: Receive data from client */ ssize_t bytesRead = recv(client_fd, buffer, BUFFER_SIZE - 1, 0); if (bytesRead < 0) { perror("Receive failed"); close(client_fd); close(server_fd); return EXIT_FAILURE; } buffer[bytesRead] = '\0'; printf("Received from client: %s\n", buffer); /* Step 7: Send response to client */ ssize_t bytesSent = send(client_fd, response, strlen(response), 0); if (bytesSent < 0) { perror("Send failed"); close(client_fd); close(server_fd); return EXIT_FAILURE; } printf("Response sent: '%s'\n", response); /* Step 8: Close sockets */ close(client_fd); close(server_fd); printf("Connection closed.\n"); return EXIT_SUCCESS; } Client Program: client.c
c
/** * client.c * TCP client that connects to server on port 8080. * Sends a message and receives a response. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.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(void) { int client_fd; struct sockaddr_in server_addr; char buffer[BUFFER_SIZE] = {0}; const char *message = "Hello from client"; /* Step 1: Create socket */ client_fd = socket(AF_INET, SOCK_STREAM, 0); if (client_fd < 0) { perror("Socket creation failed"); return EXIT_FAILURE; } printf("Socket created successfully.\n"); /* Configure server address */ server_addr.sin_family = AF_INET; server_addr.sin_port = htons(PORT); /* Convert IPv4 address from text to binary */ if (inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr) <= 0) { perror("Invalid address"); close(client_fd); return EXIT_FAILURE; } /* Step 2: Connect to server */ if (connect(client_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) { perror("Connection failed"); close(client_fd); return EXIT_FAILURE; } printf("Connected to server.\n"); /* Step 3: Send message to server */ ssize_t bytesSent = send(client_fd, message, strlen(message), 0); if (bytesSent < 0) { perror("Send failed"); close(client_fd); return EXIT_FAILURE; } printf("Message sent: '%s'\n", message); /* Step 4: Receive response from server */ ssize_t bytesRead = recv(client_fd, buffer, BUFFER_SIZE - 1, 0); if (bytesRead < 0) { perror("Receive failed"); close(client_fd); return EXIT_FAILURE; } buffer[bytesRead] = '\0'; printf("Received from server: %s\n", buffer); /* Step 5: Close socket */ close(client_fd); printf("Connection closed.\n"); return EXIT_SUCCESS; } Compilation
bash
gcc -Wall -Wextra -std=c11 server.c -o server gcc -Wall -Wextra -std=c11 client.c -o client
Running the Programs
Terminal 1 (Server):
bash
./server
Terminal 2 (Client):
bash
./client
Expected Output
Server:
text
Socket created successfully. Socket options set. Bound to port 8080. Listening for connections... Client connected. Received from client: Hello from client Response sent: 'Hello from server' Connection closed.
Client:
text
Socket created successfully. Connected to server. Message sent: 'Hello from client' Received from server: Hello from server Connection closed.
Common Pitfalls, Edge Cases & Memory Considerations
1. Not Checking Return Values
Socket functions can fail; always check return values.
c
socket(AF_INET, SOCK_STREAM, 0); // No check
Solution: Check if (sockfd < 0) after creation.
2. Port Already in Use
Binding to a port that’s already in use fails.
Solution: Use SO_REUSEADDR or choose a different port.
3. Blocking Calls
By default, accept(), recv(), and connect() block indefinitely.
Solution: Use non-blocking mode with fcntl or select/poll.
4. Forgetting to Close Sockets
Leaving sockets open leaks resources.
Solution: Always close() sockets after use.
5. Byte Order Issues
Network byte order is big-endian; host may be little-endian.
Solution: Use htons, htonl, ntohs, ntohl for conversions.
6. Buffer Overflow
Using buffers without checking sizes.
c
char buffer[10]; recv(sockfd, buffer, 1024, 0); // BUG: overflow
Solution: Use sizeof(buffer) - 1 for receive size.
7. Not Handling Partial Sends/Receives
send and recv may transfer fewer bytes than requested.
Solution: Loop until all data is sent/received.
8. Incorrect Address Conversion
Using inet_addr (deprecated) instead of inet_pton.
Solution: Use inet_pton and inet_ntop.
9. Connection Refused
Client cannot connect if server is not running.
Solution: Check server status, IP address, and port.
10. Memory Considerations
- Socket buffers consume kernel memory.
- Large data transfers require adequate buffer sizes.
- Use
mallocfor dynamic buffers when needed. - Close sockets to release resources.
Summary & Best Practices
Socket programming enables network communication between processes. Understanding the client-server model and proper socket management is essential for building distributed applications.
Key Takeaways
- Sockets combine IP address and port number.
- TCP (
SOCK_STREAM) is reliable; UDP (SOCK_DGRAM) is faster. - Server: socket → bind → listen → accept → send/recv → close.
- Client: socket → connect → send/recv → close.
- Use
htons/htonlfor network byte order. - Always check return values and close sockets.
Best Practices
- Always check return values of socket functions.
- Use
SO_REUSEADDRto avoid port binding issues. - Use
htons/htonlfor network byte order conversions. - Handle partial sends/receives with loops.
- Use adequate buffer sizes to prevent overflow.
- Set timeouts using
setsockoptfor non-blocking behavior. - Close all sockets when done.
- Use
inet_pton/inet_ntopfor address conversion. - Document protocol (message format) for clarity.
- Test with different scenarios: connection refused, timeout, large data.
By mastering socket programming, you’ll build robust networked applications that communicate efficiently across systems.



