Welcome to our deep dive into C Sockets! In this comprehensive guide, we'll explore the world of network programming in C, a powerful language for creating networked applications. By the end of this lesson, you'll have a solid understanding of sockets and how to use them to build your own networked applications. 📝 Note: This lesson is suitable for both beginners and intermediate learners.
Sockets are endpoints in a network communication system that provide a reliable, bidirectional communication channel between two machines over a network. In C, sockets are implemented using the BSD Socket API.
There are two main types of sockets:
Stream Sockets (SOCK_STREAM): These provide a full-duplex, reliable, and connection-oriented communication channel. They are typically used for TCP (Transmission Control Protocol) connections.
Datagram Sockets (SOCK_DGRAM): These provide a connectionless, unreliable, and best-effort datagram service. They are typically used for UDP (User Datagram Protocol) connections.
To create a socket, we use the socket() function. This function takes three arguments:
AF_INET.SOCK_STREAM or SOCK_DGRAM).IPPROTO_TCP).Here's a simple example of creating a TCP socket:
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
int create_socket() {
int sockfd;
struct sockaddr_in serv_addr;
// Create the socket
sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
// Check for errors
if (sockfd < 0) {
perror("ERROR: socket");
exit(EXIT_FAILURE);
}
return sockfd;
}After creating a socket, we need to bind it to a specific address and port, and then listen for incoming connections. We do this using the bind() and listen() functions.
void bind_and_listen(int sockfd, int port) {
struct sockaddr_in serv_addr;
// Fill in the socket structure
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = INADDR_ANY;
serv_addr.sin_port = htons(port);
// Bind the socket to the specified address and port
if (bind(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
perror("ERROR: bind");
exit(EXIT_FAILURE);
}
// Listen for incoming connections
if (listen(sockfd, 10) < 0) {
perror("ERROR: listen");
exit(EXIT_FAILURE);
}
}To accept an incoming connection, we use the accept() function. This function blocks until a new connection is received.
int accept_connection(int sockfd) {
int new_sockfd;
struct sockaddr_in cli_addr;
socklen_t cli_len = sizeof(cli_addr);
// Accept the incoming connection
new_sockfd = accept(sockfd, (struct sockaddr *)&cli_addr, &cli_len);
// Check for errors
if (new_sockfd < 0) {
perror("ERROR: accept");
exit(EXIT_FAILURE);
}
return new_sockfd;
}To send and receive data over a socket, we use the send() and recv() functions, respectively.
void send_data(int sockfd, char *message) {
int sent = send(sockfd, message, strlen(message), 0);
// Check for errors
if (sent < 0) {
perror("ERROR: send");
exit(EXIT_FAILURE);
}
}
char receive_data(int sockfd, int buffer_size) {
char buffer[buffer_size];
ssize_t received = recv(sockfd, buffer, buffer_size - 1, 0);
// Check for errors
if (received < 0) {
perror("ERROR: recv");
exit(EXIT_FAILURE);
}
// Null-terminate the received data
buffer[received] = '\0';
return buffer;
}Finally, to close a socket, we use the close() function.
void close_socket(int sockfd) {
if (close(sockfd) < 0) {
perror("ERROR: close");
exit(EXIT_FAILURE);
}
}Now that we've covered the basics of socket programming in C, let's put it all together in a simple server-client example.
What is the purpose of the `socket()` function in C socket programming?
In C socket programming, what does the `bind()` function do?
What does the `accept()` function do in C socket programming?
Here's a simple server-client example:
Server
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#define PORT 8080
int create_socket() {
int sockfd;
struct sockaddr_in serv_addr;
// Create the socket
sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
// Check for errors
if (sockfd < 0) {
perror("ERROR: socket");
exit(EXIT_FAILURE);
}
return sockfd;
}
void bind_and_listen(int sockfd, int port) {
struct sockaddr_in serv_addr;
// Fill in the socket structure
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = INADDR_ANY;
serv_addr.sin_port = htons(port);
// Bind the socket to the specified address and port
if (bind(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
perror("ERROR: bind");
exit(EXIT_FAILURE);
}
// Listen for incoming connections
if (listen(sockfd, 10) < 0) {
perror("ERROR: listen");
exit(EXIT_FAILURE);
}
}
int accept_connection(int sockfd) {
int new_sockfd;
struct sockaddr_in cli_addr;
socklen_t cli_len = sizeof(cli_addr);
// Accept the incoming connection
new_sockfd = accept(sockfd, (struct sockaddr *)&cli_addr, &cli_len);
// Check for errors
if (new_sockfd < 0) {
perror("ERROR: accept");
exit(EXIT_FAILURE);
}
return new_sockfd;
}
void send_data(int sockfd, char *message) {
int sent = send(sockfd, message, strlen(message), 0);
// Check for errors
if (sent < 0) {
perror("ERROR: send");
exit(EXIT_FAILURE);
}
}
char receive_data(int sockfd, int buffer_size) {
char buffer[buffer_size];
ssize_t received = recv(sockfd, buffer, buffer_size - 1, 0);
// Check for errors
if (received < 0) {
perror("ERROR: recv");
exit(EXIT_FAILURE);
}
// Null-terminate the received data
buffer[received] = '\0';
return buffer;
}
void close_socket(int sockfd) {
if (close(sockfd) < 0) {
perror("ERROR: close");
exit(EXIT_FAILURE);
}
}
int main() {
int sockfd = create_socket();
bind_and_listen(sockfd, PORT);
int new_sockfd;
char message[100];
while (1) {
new_sockfd = accept_connection(sockfd);
printf("New connection received!\n");
// Read the client's message
strcpy(message, receive_data(new_sockfd, sizeof(message)));
// Send a response to the client
send_data(new_sockfd, "Hello, Client! This is the server.\n");
// Close the socket
close_socket(new_sockfd);
}
return 0;
}Client
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#define PORT 8080
int create_socket() {
int sockfd;
struct sockaddr_in serv_addr;
// Create the socket
sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
// Check for errors
if (sockfd < 0) {
perror("ERROR: socket");
exit(EXIT_FAILURE);
}
return sockfd;
}
struct sockaddr_in init_address(char *host, int port) {
struct sockaddr_in serv_addr;
// Fill in the socket structure
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = inet_addr(host);
serv_addr.sin_port = htons(port);
return serv_addr;
}
void connect_to_server(int sockfd, struct sockaddr_in serv_addr) {
// Connect to the server
if (connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
perror("ERROR: connect");
exit(EXIT_FAILURE);
}
}
char send_data(int sockfd, char *message) {
int sent = send(sockfd, message, strlen(message), 0);
// Check for errors
if (sent < 0) {
perror("ERROR: send");
exit(EXIT_FAILURE);
}
return message;
}
char receive_data(int sockfd, int buffer_size) {
char buffer[buffer_size];
ssize_t received = recv(sockfd, buffer, buffer_size - 1, 0);
// Check for errors
if (received < 0) {
perror("ERROR: recv");
exit(EXIT_FAILURE);
}
// Null-terminate the received data
buffer[received] = '\0';
return buffer;
}
void close_socket(int sockfd) {
if (close(sockfd) < 0) {
perror("ERROR: close");
exit(EXIT_FAILURE);
}
}
int main() {
int sockfd = create_socket();
struct sockaddr_in serv_addr = init_address("127.0.0.1", PORT);
// Connect to the server
connect_to_server(sockfd, serv_addr);
// Send a message to the server
send_data(sockfd, "Hello, Server! This is the client.\n");
// Receive the server's response
char response[100];
strcpy(response, receive_data(sockfd, sizeof(response)));
// Print the server's response
printf("Server replied: %s\n", response);
// Close the socket
close_socket(sockfd);
return 0;
}In this example, the server listens for incoming connections and responds to each client with a predefined message. The client sends a message to the server and receives the server's response.