C Sockets Deep Dive 🎯

beginner
25 min

C Sockets Deep Dive 🎯

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.

What are Sockets? 💡

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.

Types of Sockets 📝

There are two main types of sockets:

  1. 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.

  2. Datagram Sockets (SOCK_DGRAM): These provide a connectionless, unreliable, and best-effort datagram service. They are typically used for UDP (User Datagram Protocol) connections.

Creating Sockets 🎯

To create a socket, we use the socket() function. This function takes three arguments:

  • Domain: The network domain. For IPv4, we use AF_INET.
  • Type: The socket type (either SOCK_STREAM or SOCK_DGRAM).
  • Protocol: The communication protocol (for TCP, use IPPROTO_TCP).

Here's a simple example of creating a TCP socket:

c
#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; }

Bind and Listen 🎯

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.

c
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); } }

Accepting Connections 🎯

To accept an incoming connection, we use the accept() function. This function blocks until a new connection is received.

c
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; }

Sending and Receiving Data 🎯

To send and receive data over a socket, we use the send() and recv() functions, respectively.

c
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; }

Closing Sockets 🎯

Finally, to close a socket, we use the close() function.

c
void close_socket(int sockfd) { if (close(sockfd) < 0) { perror("ERROR: close"); exit(EXIT_FAILURE); } }

Putting It All Together 🎯

Now that we've covered the basics of socket programming in C, let's put it all together in a simple server-client example.

Quick Quiz
Question 1 of 1

What is the purpose of the `socket()` function in C socket programming?

Quick Quiz
Question 1 of 1

In C socket programming, what does the `bind()` function do?

Quick Quiz
Question 1 of 1

What does the `accept()` function do in C socket programming?

Here's a simple server-client example:

Server

c
#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

c
#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.