C++ NETWORK PROGRAMMING

B.R.O.L.Y
9 min readMay 14, 2024

--

1 — Introduction to Network Programming:

What is network programming?

Network programming is the practice of creating software applications that communicate with each other over a network. This communication can occur within a local network (LAN) or across the internet, enabling devices and systems to exchange data and share resources. Network programming plays a crucial role in enabling various modern technologies and services, from web applications to IoT devices.

Why is it important?

Understanding network programming is essential for developers who want to build scalable, distributed, and interconnected systems. It enables the creation of applications that can leverage the power of the internet and networked environments to provide real-time communication, data sharing, and collaboration. Whether developing web servers, chat applications, or enterprise systems, knowledge of network programming principles is indispensable.

Overview of different network protocols (TCP/IP, UDP, etc.)

Introduction

Network protocols are sets of rules and conventions that govern how data is transmitted and received over a network. They define the format of data packets, error-handling mechanisms, and other aspects of communication. Understanding different network protocols is crucial for developing networked applications that can effectively communicate and interact with other devices and systems.

TCP/IP Protocol Suite

The TCP/IP (Transmission Control Protocol/Internet Protocol) protocol suite is the foundation of the modern internet. It consists of a set of protocols that facilitate communication between devices connected to the internet. The key protocols in the TCP/IP suite include:

  • TCP (Transmission Control Protocol): TCP is a connection-oriented protocol that provides reliable, ordered, and error-checked delivery of data packets. It ensures that data sent from one device to another is received in the correct order and without errors.
  • IP (Internet Protocol): IP is a connectionless protocol responsible for routing data packets between devices on a network. It provides the addressing and routing mechanisms necessary for data transmission across different networks.
  • UDP (User Datagram Protocol): UDP is a connectionless protocol that provides a lightweight and unreliable means of data transmission. Unlike TCP, UDP does not guarantee delivery or order of packets, making it suitable for applications where speed is more critical than reliability, such as real-time communication and streaming media.

Other Network Protocols

In addition to TCP/IP, there are various other network protocols used for specific purposes or in specialized environments. Some notable examples include:

  • HTTP (Hypertext Transfer Protocol): HTTP is a protocol used for transferring hypertext documents on the World Wide Web. It defines how web browsers and servers communicate, enabling the retrieval and display of web pages.
  • FTP (File Transfer Protocol): FTP is a protocol used for transferring files between a client and a server on a network. It provides commands for uploading, downloading, and managing files on remote servers.
  • SMTP (Simple Mail Transfer Protocol): SMTP is a protocol used for sending and receiving email messages over the internet. It defines the rules for email transmission and delivery between mail servers.

2 — Socket Programming Basics:[just another file manipulation in Linux ]

Introduction to sockets in C++:

sockets provide a means for communication between processes running on different devices over a network. In C++, sockets are typically manipulated using system calls provided by the operating system’s networking API, such as the Berkeley sockets API on Unix-like systems or the Winsock API on Windows.

Creating and using sockets in C++ involves several steps, including socket creation, binding to a specific address and port, listening for incoming connections (for server sockets), establishing connections (for client sockets), and sending/receiving data.

One of the key advantages of using sockets in C++ is the flexibility they offer. Developers have fine-grained control over various aspects of network communication, such as the choice of transport protocol (e.g., TCP or UDP), socket options, and error handling. This flexibility allows for the implementation of a wide range of networked applications, from simple client-server applications to complex distributed systems.

C++ provides several libraries and frameworks for working with sockets, including:

  • The BSD sockets API: This is the standard API for socket programming on Unix-like systems. It provides low-level access to sockets and is widely used for network programming in C++.
  • Boost.Asio: Boost.Asio is a cross-platform C++ library that provides a high-level abstraction over sockets and other asynchronous I/O operations. It simplifies the process of writing networked applications and is particularly well-suited for implementing scalable and efficient servers.
  • C++ Networking Technical Specification (TS): The C++ Networking TS, introduced in C++17, provides a modern, standardized API for network programming in C++. It offers a higher-level interface than the BSD sockets API and is designed to be portable across different platforms.

Regardless of the specific library or framework used, understanding the fundamentals of socket programming in C++ is essential for developing robust and efficient networked applications. In the following sections, we will explore the basics of socket programming in C++, including socket creation, binding, connection establishment, and data transmission.

Creating sockets (socket(), bind(), listen(), etc.):

1. Socket Creation:

  • Explanation: The socket() system call is used to create a new socket. It takes three arguments: the address family (such as AF_INET for IPv4 or AF_INET6 for IPv6), the socket type (such as SOCK_STREAM for TCP or SOCK_DGRAM for UDP), and the protocol (usually set to 0 to let the system choose the default protocol for the specified address family and socket type ).
  • Prototype: int socket(int domain, int type, int protocol);
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
perror("socket");
exit(EXIT_FAILURE);
}

2. Binding:

  • Explanation: The bind() system call is used to bind a socket to a specific address and port. It takes the socket file descriptor, a pointer to a structure containing the local address and port information, and the size of the address structure as arguments.
  • Prototype: int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY; // Bind to any available interface[0.0.0.0]
addr.sin_port = htons(4000); // Bind to port 4000

if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
perror("bind");
exit(EXIT_FAILURE);
}

3. Listening (for Server Sockets):

  • Explanation: The listen() system call is used to put a server socket into a listening state, allowing it to accept incoming connections from clients. It takes the socket file descriptor and the maximum number of pending connections that the socket's listen queue can hold as arguments.
  • Prototype: int listen(int sockfd, int backlog);
if (listen(sockfd, 5) == -1) { // Allow up to 5 pending connections
perror("listen");
exit(EXIT_FAILURE);
}

4. Accepting Connections (for Server Sockets):

  • Explanation: The accept() system call is used by server sockets to accept incoming connections from clients. It blocks until a connection arrives and returns a new socket file descriptor representing the connection. The original server socket remains in the listening state and can continue to accept new connections.
  • Prototype: int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
sockaddr_in client_addr;
socklen_t client_addrlen = sizeof(client_addr);
int client_sockfd = accept(sockfd, (struct sockaddr *)&client_addr, &client_addrlen);
if (client_sockfd == -1) {
perror("accept");
exit(EXIT_FAILURE);
}

5. Connecting (for Client Sockets):

  • Explanation: The connect() system call is used by client sockets to establish a connection with a server. It takes the socket file descriptor, a pointer to the server's address structure containing the address and port, and the size of the address structure as arguments.
  • Prototype: int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); // Server's IP address
server_addr.sin_port = htons(4000); // Server's port

if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
perror("connect");
exit(EXIT_FAILURE);
}

3 — Simple tcp client-server :

1-Server :

#include <sys/socket.h>
#include <iostream>
#include <cstring>
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>

int main()
{
std::cout << "Starting the server ...." << std::endl;
int server_socket = socket(AF_INET, SOCK_STREAM, 0);
if (server_socket == -1)
{
std::cerr << "Error creating the socket !" << std::endl;
return 1;
}

sockaddr_in serverAddr;
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = INADDR_ANY;
serverAddr.sin_port = htons(4000);

if (bind(server_socket, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) == -1)
{
std::cerr << "Error binding the socket !" << std::endl;
close(server_socket);
return 1;
}

listen (server_socket, 1);

int client_socket = accept(server_socket, NULL, NULL);
char buff[1024];
while (true)
{
memset(buff, 0, sizeof(buff));
recv(client_socket, buff, sizeof(buff), 0);
std::cout << "Message from client: " << buff;
if (strcmp(buff, "exit\n") == 0)
break ;
}
close(server_socket);
return (0);
}

2-Client :

#include <iostream>
#include <cstring>
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>

int main()
{
std::cout << "Starting the Client ..." << std::endl;
int client_socket = socket(AF_INET,SOCK_STREAM, 0);
if (client_socket == -1)
{
std::cerr << "Error creating the socket ..." << std::endl;
}
sockaddr_in clientAddr;
clientAddr.sin_family = AF_INET;
clientAddr.sin_port = htons(4000);
clientAddr.sin_addr.s_addr = INADDR_ANY;

connect(client_socket, (struct sockaddr*)&clientAddr, sizeof(clientAddr));

char mess[1024];
while (true)
{
std::cout << "Enter the message to send : ";
fgets(mess, sizeof(mess), stdin);
send(client_socket, &mess, strlen(mess), 0);
if (strcmp(mess, "exit\n") == 0)
break ;
memset(mess, 0, sizeof(mess));
}
close(client_socket);
return (0);
}

3-Run it :

now to run the server-client application just compile it using these commands

g++ server.cpp -o server
./server
g++ client.cpp -o client
./client

4 — blocking vs non-blocking sockets:

Blocking and non-blocking sockets are two different approaches to handling I/O operations in network programming. Understanding the differences between them is crucial for designing efficient and responsive networked applications.

1 — Blocking Sockets:

In blocking sockets, I/O operations (such as reading from or writing to a socket) block the calling thread until the operation completes or an error occurs. This means that if there’s no data available to read, a recv() call will block until data arrives, and if the send buffer is full, a send() call will block until space becomes available in the buffer.

#include <iostream>
#include <cstring>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>

int main() {
int server_socket = socket(AF_INET, SOCK_STREAM, 0);
// Socket creation, binding, and listening...

int client_socket = accept(server_socket, NULL, NULL);
char buffer[1024];
ssize_t bytes_received = recv(client_socket, buffer, sizeof(buffer), 0);
if (bytes_received == -1) {
perror("recv");
close(client_socket);
close(server_socket);
return 1;
}
std::cout << "Received message: " << buffer << std::endl;

close(client_socket);
close(server_socket);
return 0;
}

2 — Non-blocking Sockets:

In non-blocking sockets, I/O operations return immediately, even if they cannot be completed right away. This allows the calling thread to continue executing other tasks without waiting for the I/O operation to complete. Non-blocking sockets are often used with polling mechanisms like select() or poll() to determine when I/O operations can be performed without blocking.

#include <iostream>
#include <cstring>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <fcntl.h>

int main() {
int server_socket = socket(AF_INET, SOCK_STREAM, 0);
// Socket creation, binding, and listening...

int client_socket = accept(server_socket, NULL, NULL);
// Set client socket to non-blocking
fcntl(client_socket, F_SETFL, O_NONBLOCK);

char buffer[1024];
ssize_t bytes_received = recv(client_socket, buffer, sizeof(buffer), 0);
if (bytes_received == -1) {
if (errno == EWOULDBLOCK) {
std::cerr << "No data available to read" << std::endl;
} else {
perror("recv");
}
} else {
std::cout << "Received message: " << buffer << std::endl;
}

close(client_socket);
close(server_socket);
return 0;
}

5 — Select and Poll:

1 — Select:

Overview: Select is a system call that monitors multiple file descriptors for I/O readiness. It allows the process to wait until one or more file descriptors become ready for reading, writing, or exceptional conditions.

Prototype: int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

  • nfds: The highest-numbered file descriptor in any of the sets plus one.
  • readfds: Set of file descriptors to monitor for readability.
  • writefds: Set of file descriptors to monitor for writability.
  • exceptfds: Set of file descriptors to monitor for exceptional conditions.
  • timeout: Maximum time to wait for an event to occur (or NULL to block indefinitely).

How it Works:

  • The select() function blocks until at least one file descriptor in one of the specified sets becomes ready or until the timeout expires.
  • When select() returns, the application can iterate through the file descriptor sets to determine which file descriptors are ready and perform appropriate I/O operations.
fd_set read_fds;
FD_ZERO(&read_fds);
FD_SET(server_socket, &read_fds);

int max_fd = server_socket + 1;
struct timeval timeout = {10, 0}; // 10 seconds timeout
int activity = select(max_fd, &read_fds, NULL, NULL, &timeout);
if (activity == -1) {
perror("select");
exit(EXIT_FAILURE);
}

if (FD_ISSET(server_socket, &read_fds)) {
// Server socket is ready for new connection
int client_socket = accept(server_socket, NULL, NULL);
// Handle new connection...
}

2 — Poll:

Overview: Poll is a system call that performs similar functionality to select() but with some differences, such as a more efficient design and lack of limitations on the maximum number of file descriptors.

Prototype: int poll(struct pollfd *fds, nfds_t nfds, int timeout);

  • fds: Array of pollfd structures containing file descriptor information and events to monitor.
  • nfds: Number of elements in the fds array.
  • timeout: Maximum time to wait for an event to occur (or -1 to block indefinitely).

How it Works:

  • The poll() function blocks until an event occurs on one of the file descriptors in the fds array or until the timeout expires.
  • Each element in the fds array specifies a file descriptor to monitor and the types of events to monitor for (e.g., readability, writability, or errors).
struct pollfd fds[1];
fds[0].fd = server_socket;
fds[0].events = POLLIN;

int timeout = 10000; // 10 seconds timeout
int activity = poll(fds, 1, timeout);
if (activity == -1) {
perror("poll");
exit(EXIT_FAILURE);
}

if (fds[0].revents & POLLIN) {
// Server socket is ready for new connection
int client_socket = accept(server_socket, NULL, NULL);
// Handle new connection...
}

That’s it for now, till next time inshallah, have a good day, and don’t forget to follow me on Medium and GitHub.

--

--

B.R.O.L.Y

My name is RIDWANE EL FILALI but you can call me B.R.O.L.Y