What is a Socket?
A socket is an essential concept in computer networking, providing a mechanism for programs to communicate over a network. In Unix-based systems, a socket is treated like a file descriptor, an integer representing an open file. This file descriptor can correspond to various types of I/O, including network connections, which allows programs to read from and write to the network similarly to how they interact with files.
Key Concepts
- File Descriptor: A unique identifier for an open file, which in the context of sockets, represents a network connection.
- Socket Creation: Sockets are created using the
socket()
system call, which returns a socket descriptor that can be used for subsequent communication operations. - Communication Functions:
- send() and recv(): Provide fine-grained control over the communication process.
- read() and write(): Offer a simpler interface, similar to file operations.
Types of Internet Sockets
Internet sockets come in two main types: stream sockets and datagram sockets. Each type has distinct characteristics and uses.
1. Stream Sockets (SOCK_STREAM)
Characteristics:
- Stream sockets provide reliable, two-way, connection-oriented communication.
- They ensure that data arrives intact, in order, and without duplicates.
Protocol:
- Typically use TCP (Transmission Control Protocol), which establishes a connection before data is transferred and guarantees the delivery of packets.
Use Cases:
- Ideal for applications requiring reliability, such as web browsers (HTTP), remote terminal applications (telnet, ssh), and email (SMTP).
2. Datagram Sockets (SOCK_DGRAM)
Characteristics:
- Datagram sockets support connectionless communication.
- They send messages, called datagrams, without establishing a connection, making them faster but less reliable.
Protocol:
- Use UDP (User Datagram Protocol), which does not guarantee delivery, order, or error-free communication.
Use Cases:
- Suitable for applications where speed is critical, and occasional data loss is acceptable, such as online games, streaming media (audio and video), and DNS lookups.
Creating a Socket
To create a socket, use the socket()
function:
int socket(int domain, int type, int protocol);
- domain: Specifies the communication domain (e.g.,
AF_INET
for IPv4). - type: Specifies the communication type (e.g.,
SOCK_STREAM
orSOCK_DGRAM
). - protocol: Typically set to 0 to select the default protocol for the given domain and type.
Example:
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
perror("socket creation failed");
exit(EXIT_FAILURE);
}
Binding a Socket
Before a socket can accept incoming connections or send data, it must be bound to an address (IP address and port number):
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
Example:
struct sockaddr_in servaddr;
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = INADDR_ANY;
servaddr.sin_port = htons(PORT);
if (bind(sockfd, (const struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) {
perror("bind failed");
close(sockfd);
exit(EXIT_FAILURE);
}
Listening and Accepting Connections (for Stream Sockets)
For stream sockets, the server must listen for incoming connections and accept them:
int listen(int sockfd, int backlog);
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
Example:
if (listen(sockfd, 5) < 0) {
perror("listen failed");
close(sockfd);
exit(EXIT_FAILURE);
}
int newsockfd = accept(sockfd, (struct sockaddr *)&cliaddr, &clilen);
if (newsockfd < 0) {
perror("accept failed");
close(sockfd);
exit(EXIT_FAILURE);
}
Sending and Receiving Data
Data can be sent and received using send()
and recv()
for finer control:
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
Or using read()
and write()
for a simpler interface:
ssize_t read(int sockfd, void *buf, size_t count);
ssize_t write(int sockfd, const void *buf, size_t count);
Example Code
TCP Server
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#define PORT 8080
#define BUFSIZE 1024
int main() {
int sockfd, newsockfd;
struct sockaddr_in servaddr, cliaddr;
socklen_t clilen;
char buffer[BUFSIZE];
int n;
// Create socket
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
perror("socket creation failed");
exit(EXIT_FAILURE);
}
// Bind socket
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = INADDR_ANY;
servaddr.sin_port = htons(PORT);
if (bind(sockfd, (const struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) {
perror("bind failed");
close(sockfd);
exit(EXIT_FAILURE);
}
// Listen
if (listen(sockfd, 5) < 0) {
perror("listen failed");
close(sockfd);
exit(EXIT_FAILURE);
}
// Accept
clilen = sizeof(cliaddr);
newsockfd = accept(sockfd, (struct sockaddr *)&cliaddr, &clilen);
if (newsockfd < 0) {
perror("accept failed");
close(sockfd);
exit(EXIT_FAILURE);
}
// Receive and echo data
while ((n = read(newsockfd, buffer, BUFSIZE)) > 0) {
buffer[n] = '\\0';
printf("Client: %s", buffer);
write(newsockfd, buffer, n);
}
close(newsockfd);
close(sockfd);
return 0;
}
TCP Client
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#define PORT 8080
#define BUFSIZE 1024
int main() {
int sockfd;
struct sockaddr_in servaddr;
char buffer[BUFSIZE];
int n;
// Create socket
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
perror("socket creation failed");
exit(EXIT_FAILURE);
}
// Set server address
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(PORT);
if (inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr) <= 0) {
perror("invalid address");
close(sockfd);
exit(EXIT_FAILURE);
}
// Connect to server
if (connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) {
perror("connect failed");
close(sockfd);
exit(EXIT_FAILURE);
}
// Send and receive data
while (fgets(buffer, BUFSIZE, stdin) != NULL) {
write(sockfd, buffer, strlen(buffer));
n = read(sockfd, buffer, BUFSIZE);
buffer[n] = '\\0';
printf("Server: %s", buffer);
}
close(sockfd);
return 0;
}
Conclusion
Sockets are a fundamental aspect of network programming, providing a versatile interface for communication over networks. Whether you’re developing a simple chat application, a complex web server, or a real-time multiplayer game, understanding and effectively using sockets is crucial. By mastering the concepts and functions related to sockets, you can create robust and efficient networked applications.