A socket is a fundamental building block for network communication, serving as an endpoint for process-to-process communication across a computer network. This guide explains the core concepts and implementation details of socket programming.
Think of a socket like a phone system:
- Socket β Phone Line
- IP Address β Phone Number
- Port β Extension Number
- Connection β Phone Call between two points
A socket provides an interface for network communication enabling:
- Process-to-process communication
- Data exchange between different computers
- Communication using various protocols (TCP/IP, UDP)
- Uses TCP (Transmission Control Protocol)
- Provides reliable, ordered data delivery
- Connection-oriented (like a phone call)
- Common applications:
- Web browsers
- SSH
- FTP
- Uses UDP (User Datagram Protocol)
- No guaranteed delivery
- Connectionless (like sending letters)
- Common applications:
- DNS
- Streaming media
- Online gaming
int socket_fd = socket(AF_INET, SOCK_STREAM, 0);AF_INET: Uses IPv4SOCK_STREAM: TCP protocol- Returns a file descriptor for the socket
bind(socket_fd, address, address_length);- Assigns an address to the socket
- Similar to registering a phone number
listen(socket_fd, backlog);- Marks socket as passive
- Prepares to accept connections
backlogdefines maximum connection queue length
accept(socket_fd, client_address, address_length);- Waits for incoming connections
- Creates new socket for each connected client
connect(socket_fd, server_address, address_length);- Initiates connection to server
- Used by clients to connect to listening servers
| State | Description | Triggered By |
|---|---|---|
| Created | Initial socket state | socket() |
| Bound | Address assigned | bind() |
| Listening | Ready for connections | listen() |
| Connected | Active connection | connect()/accept() |
| Closed | Socket termin |
Binding in socket programming is like registering an official address for your application. Just as a business needs a physical address to receive mail, a socket needs an IP address and port number to receive network communications.
The address structure used in binding contains all necessary addressing information:
struct sockaddr_in {
short sin_family; // Address family (AF_INET for IPv4)
unsigned short sin_port; // Port number
struct in_addr sin_addr; // IP address
char sin_zero[8]; // Padding
};// Create address structure
sockaddr_in server_addr;
server_addr.sin_family = AF_INET; // IPv4
server_addr.sin_port = htons(port); // Set port (in network byte order)
server_addr.sin_addr.s_addr = INADDR_ANY; // Listen on all interfaces
// Bind socket to address
if (bind(socket_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
throw std::runtime_error("Binding failed");
}- Socket exists but isn't associated with any address
- Like a phone that's not yet activated
- No way for clients to reach the socket
- OS registers the socket with specific IP and port
- Creates entry in routing table
- Reserves the port for your application
- Sets up necessary system resources
- Socket is now "findable" on the network
- Can receive connections on specified IP:port
- Port is exclusively reserved for your process
- Other processes cannot use the same IP:port combination
server_addr.sin_addr.s_addr = INADDR_ANY;When you use INADDR_ANY, your socket will:
- Listen on ALL available network interfaces
- Accept connections coming to any of the host's IP addresses
- Provide maximum flexibility for incoming connections
Example with multiple network interfaces:
eth0: 192.168.1.100
wlan0: 192.168.2.200
localhost: 127.0.0.1
INADDR_ANY will listen on all of these!
// Solution: Use SO_REUSEADDR option
int opt = 1;
setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));- Ports below 1024 require root privileges
- Best practice: Use ports > 1024 for user applications
- Common ports to avoid:
- 80 (HTTP)
- 443 (HTTPS)
- 21 (FTP)
- 22 (SSH)
[Client] [Server]
| |
| 1. Create Socket
| |
| 2. Bind(IP:Port)
| |
| 3. Listen()
| |
|------ Connect() -------->|
| |
|<----- Accept() ----------|
Think of binding like setting up a new business location:
- Your Program = Business
- IP Address = Street Address
- Port Number = Suite/Unit Number
- Binding = Registering this location officially
| Business Analogy | Socket Programming |
|---|---|
| Building | Program |
| Street Address | IP Address |
| Suite Number | Port Number |
| Location Registration | Binding |
| Mail Reception | Data Reception |
- Error Handling
if (bind(socket_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
std::cerr << "Binding failed: " << strerror(errno) << std::endl;
close(socket_fd);
exit(EXIT_FAILURE);
}- Port Selection
- Use ports > 1024 (non-privileged ports)
- Check if port is available before binding
- Consider making port configurable
- Address Reuse
- Always set SO_REUSEADDR for development
- Handle cleanup properly
- Close sockets when done
Simple test to verify binding:
# Check if port is in use
netstat -an | grep <port_number>
# Try connecting to bound port
nc -zv localhost <port_number>The listen() function is more than just making a socket available for incoming connections. Let's explore what happens under the hood when you call listen().
if (listen(socket_fd, backlog) < 0) {
// Handle error
perror("listen failed");
exit(EXIT_FAILURE);
}[Before listen()]
Socket State: CLOSED
Queue: Not initialized
[After listen()]
Socket State: LISTENING
Queue: Two queues created
- SYN Queue (Incomplete connections)
- Accept Queue (Complete connections)
Client Server
| |
|----- SYN --------->| [Goes to SYN Queue]
|<--- SYN+ACK -------|
|----- ACK --------->| [Moves to Accept Queue]
- Holds connections in
SYN_RCVDstate - First SYN packet received
- Handshake not complete
- Protects against SYN flood attacks
- Holds fully established connections
- Complete three-way handshake finished
- Ready for
accept() - Size limited by backlog parameter
[Client] [SYN Queue] [Accept Queue] [Server]
| | | |
|--SYN---------->| | |
| |(SYN_RCVD) | |
|<--SYN+ACK------| | |
| | | |
|--ACK---------->| | |
| |----Moves to------->|(ESTABLISHED) |
| | | |
| | |<--accept()--------|
When listen() is called, the kernel:
- Changes socket state to
LISTENING - Allocates memory for both queues
- Sets up TCP state machine
- Prepares to handle incoming SYN packets
// Kernel's internal process
if (listen(socket_fd, backlog)) {
// 1. Change socket state
socket->state = LISTEN;
// 2. Initialize queues
create_syn_queue(socket);
create_accept_queue(socket, backlog);
// 3. Set up TCP state machine
setup_tcp_state_machine(socket);
}// Too small
listen(socket_fd, 5); // Might reject connections under heavy load
// Better for busy servers
listen(socket_fd, SOMAXCONN); // Use system maximumSYN Queue Full: New SYN packets dropped
Accept Queue Full: New connections rejected
# View current system limits
sysctl net.ipv4.tcp_max_syn_backlog
sysctl net.core.somaxconn- Trying to listen before bind:
// Wrong
listen(socket_fd, 5); // Will fail
bind(socket_fd, ...);
// Correct
bind(socket_fd, ...);
listen(socket_fd, 5);- Wrong socket type:
// Wrong - UDP socket can't listen
socket_fd = socket(AF_INET, SOCK_DGRAM, 0);
// Correct - TCP socket can listen
socket_fd = socket(AF_INET, SOCK_STREAM, 0);- Backlog too small:
// Risk of dropping connections
listen(socket_fd, 5);
// Better for production
listen(socket_fd, SOMAXCONN);- Always check return value:
if (listen(socket_fd, SOMAXCONN) < 0) {
perror("listen failed");
close(socket_fd);
exit(EXIT_FAILURE);
}- Set appropriate backlog:
// Development
#define BACKLOG 5
// Production
#define BACKLOG SOMAXCONN- Handle errors gracefully:
void setup_server(int port) {
// ... socket creation and binding ...
if (listen(socket_fd, BACKLOG) < 0) {
close(socket_fd);
throw std::runtime_error("Listen failed: " +
std::string(strerror(errno)));
}
}- Monitor queue sizes:
- Watch for connection drops
- Adjust backlog if needed
- Consider load balancing for high traffic
- System tuning:
# Increase system-wide limits if needed
sudo sysctl -w net.ipv4.tcp_max_syn_backlog=2048
sudo sysctl -w net.core.somaxconn=1024- Error handling:
- Log connection failures
- Monitor queue overflow
- Implement retry mechanisms
The accept() function is a crucial part of socket programming that handles incoming client connections. It extracts the first connection from the listening socket's queue, creates a new socket specifically for this connection, and returns a new file descriptor.
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);[Listening Socket] -----> [accept()] -----> [New Client Socket]
| | |
Port 8080 Creates new Unique connection
Multiple clients socket & returns with one client
file descriptor
void Server::acceptClient() {
struct sockaddr_in client_addr;
socklen_t addr_len = sizeof(client_addr);
// Accept new connection
int client_fd = accept(_serverSocket,
(struct sockaddr*)&client_addr,
&addr_len);
if (client_fd < 0) {
if (errno != EWOULDBLOCK) { // Real error, not just no connections
std::cerr << "Accept failed: " << strerror(errno) << std::endl;
}
return;
}
// Get client information
char client_ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &(client_addr.sin_addr), client_ip, INET_ADDRSTRLEN);
std::cout << "New connection from " << client_ip
<< ":" << ntohs(client_addr.sin_port)
<< " (socket: " << client_fd << ")" << std::endl;
// Store client socket
_clientSockets.push_back(client_fd);
}struct sockaddr_in client_addr;
socklen_t addr_len = sizeof(client_addr);client_addr: Stores connecting client's information- Contains IP address and port number
addr_len: Size of address structure
int client_fd = accept(_serverSocket,
(struct sockaddr*)&client_addr,
&addr_len);- Returns new socket for client communication
- Original server socket continues listening
- New socket inherits server properties
if (client_fd < 0) {
if (errno != EWOULDBLOCK) {
std::cerr << "Accept failed: " << strerror(errno) << std::endl;
}
return;
}EWOULDBLOCK: Normal for non-blocking sockets- Other errors require proper handling
char client_ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &(client_addr.sin_addr), client_ip, INET_ADDRSTRLEN);- Converts binary IP to string format
- Useful for logging and debugging
// Set non-blocking mode
int flags = fcntl(client_fd, F_GETFL, 0);
fcntl(client_fd, F_SETFL, flags | O_NONBLOCK);// Proper cleanup on error
if (client_fd < 0) {
close(client_fd);
return;
}// Check connection limit
if (_clientSockets.size() >= MAX_CLIENTS) {
close(client_fd);
return;
}void setSocketOptions(int socket_fd) {
// Set TCP keepalive
int keepalive = 1;
setsockopt(socket_fd, SOL_SOCKET, SO_KEEPALIVE,
&keepalive, sizeof(keepalive));
// Set non-blocking
int flags = fcntl(socket_fd, F_GETFL, 0);
fcntl(socket_fd, F_SETFL, flags | O_NONBLOCK);
}void handleAcceptError(int error) {
switch (error) {
case EWOULDBLOCK:
// No connections available
break;
case EMFILE:
// Too many open files
std::cerr << "Too many open files" << std::endl;
break;
default:
std::cerr << "Accept error: " << strerror(error) << std::endl;
}
}void logClientConnection(const struct sockaddr_in& addr) {
char ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &(addr.sin_addr), ip, INET_ADDRSTRLEN);
std::cout << "New client connected from "
<< ip << ":" << ntohs(addr.sin_port) << std::endl;
}-
Connection Queue Management
- Monitor accept queue size
- Handle backlog appropriately
- Implement connection rate limiting
-
Resource Management
- Track open connections
- Implement timeout mechanism
- Clean up unused connections
-
Error Recovery
- Implement reconnection logic
- Handle temporary failures
- Log connection statistics
- Connection Issues
void debugConnection(const struct sockaddr_in& addr) {
std::cout << "Connection details:" << std::endl;
std::cout << "Family: " << addr.sin_family << std::endl;
std::cout << "Port: " << ntohs(addr.sin_port) << std::endl;
std::cout << "Address: " << inet_ntoa(addr.sin_addr) << std::endl;
}- Socket Status
void checkSocketStatus(int socket_fd) {
int error = 0;
socklen_t len = sizeof(error);
getsockopt(socket_fd, SOL_SOCKET, SO_ERROR, &error, &len);
if (error) {
std::cerr << "Socket error: " << strerror(error) << std::endl;
}
}