Network TCP IP Programming For Linux - GitMasterNikanjam/C_WiKi GitHub Wiki

TCP/IP programming in C++ for Raspberry Pi involves creating applications that communicate over a network using the TCP/IP protocol suite. The TCP/IP stack is a set of protocols that govern how data is transmitted and received over a network. In the context of C++ programming on Raspberry Pi, you might be developing applications to send and receive data between devices, or even create a server-client architecture.

Here's a basic overview of how you can approach TCP/IP programming in C++ for Raspberry Pi:

Socket Programming

Sockets are the basic building blocks for network communication. In C++, you can use the <sys/socket.h> and <netinet/in.h> headers to work with sockets.

Include Necessary Headers

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

Create a Socket

Use the socket() function to create a socket. This can be done for both the server and client sides.

int serverSocket = socket(AF_INET, SOCK_STREAM, 0);

1- socket:
socket is a system call used to create a new socket. It takes three arguments:
AF_INET: Address Family, specifying that the socket will be used for IPv4 communication. AF_INET stands for Address Family IPv4.
SOCK_STREAM: Socket Type, specifying that the socket will be a stream socket. Stream sockets provide a reliable, connection-oriented, byte-stream communication. This is commonly used for TCP (Transmission Control Protocol) communication.
0: Protocol, indicating that the system should choose the default protocol for the specified address family and socket type. For IPv4 and SOCK_STREAM, this is usually TCP.

2- Return Value:
The socket function returns a file descriptor, which is an integer representing the newly created socket. In the code, this file descriptor is stored in the variable serverSocket.

Here's a simplified example for creating a server socket:

#include <iostream>
#include <sys/socket.h>

int main() {
    // Create a server socket
    int serverSocket = socket(AF_INET, SOCK_STREAM, 0);

    if (serverSocket == -1) {
        std::cerr << "Error creating socket." << std::endl;
        return 1;
    }

    // Further code for binding, listening, accepting connections, etc.

    // Close the socket when done
    close(serverSocket);

    return 0;
}

Define Server and Client Structures

You need to define structures to store server and client information.

struct sockaddr_in serverAddress, clientAddress;

The struct sockaddr_in is a data structure commonly used in network programming in C and C++ to represent an Internet socket address, particularly for IPv4 addresses. This structure is part of the <netinet/in.h> header file. Let's break down the declaration you mentioned:
This line declares two instances of the struct sockaddr_in structure: serverAddress and clientAddress. These structures are typically used to store information about the local and remote addresses when working with sockets.

Here's a brief explanation of the members of struct sockaddr_in:

1- sin_family:

This member specifies the address family and should be set to AF_INET for IPv4.

serverAddress.sin_family = AF_INET;

2- sin_port:

This member stores the port number. It should be set to the port number you want the socket to listen on (for server) or connect to (for client). The htons function is often used to convert the port number to network byte order.

serverAddress.sin_port = htons(8080); // Example port number (convert to network byte order)

3- sin_addr:

This member represents the IP address. For a server, you typically set it to INADDR_ANY, indicating that the socket can accept connections from any available network interface. For a client, you set it to the IP address of the server you want to connect to.

serverAddress.sin_addr.s_addr = INADDR_ANY; // Allow connections from any IP

4- sin_zero:**

This member is not used in modern practice and is included for historical reasons. You can set it to zero.

memset(&(serverAddress.sin_zero), 0, sizeof(serverAddress.sin_zero));

Here's a simple example that demonstrates the use of struct sockaddr_in for a server:

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

int main() {
    struct sockaddr_in serverAddress;

    // Initialize serverAddress
    serverAddress.sin_family = AF_INET;
    serverAddress.sin_port = htons(8080); // Port number in network byte order
    serverAddress.sin_addr.s_addr = INADDR_ANY; // Allow connections from any IP
    memset(&(serverAddress.sin_zero), 0, sizeof(serverAddress.sin_zero));

    // Further code for socket creation, binding, listening, accepting connections, etc.

    return 0;
}

To set serverAddress.sin_addr.s_addr to a specific IP address, you need to use the inet_pton function from <arpa/inet.h>. This function converts an IP address from its presentation format (a string) to the network format (binary) and stores it in the in_addr structure within serverAddress.sin_addr.
Here's an example of how you can set serverAddress.sin_addr.s_addr to a specific IP address (e.g., "192.168.1.100"):

#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main() {
    struct sockaddr_in serverAddress;

    // Initialize serverAddress
    serverAddress.sin_family = AF_INET;
    serverAddress.sin_port = htons(8080); // Port number in network byte order

    // Convert the IP address from string to binary and store it in serverAddress.sin_addr
    if (inet_pton(AF_INET, "192.168.1.100", &(serverAddress.sin_addr)) <= 0) {
        std::cerr << "Invalid IP address." << std::endl;
        return 1;
    }

    // Further code for socket creation, binding, listening, accepting connections, etc.

    return 0;
}

In this example, inet_pton is used to convert the string "192.168.1.100" to the binary format expected by serverAddress.sin_addr. The result is stored in serverAddress.sin_addr.

Remember to handle the return value of inet_pton to check for errors. If inet_pton returns a value less than or equal to 0, it indicates an error in the conversion process.

Initialize Server Address

For the server, initialize the server address structure.

serverAddress.sin_family = AF_INET;
serverAddress.sin_addr.s_addr = INADDR_ANY;
serverAddress.sin_port = htons(8080); // Use the desired port

Bind the Server Socket

Bind the server socket to a specific address and port.

bind(serverSocket, (struct sockaddr*)&serverAddress, sizeof(serverAddress));

The bind function in C and C++ is used to associate a socket with a specific local address and port. In the context of network programming, it's a critical step when setting up a server.

1- serverSocket:

This is the file descriptor returned by the socket function. It represents the server socket that you want to associate with a specific address and port.

2- (struct sockaddr)&serverAddress:*

The second argument is a pointer to a struct sockaddr (or its derivatives like struct sockaddr_in for IPv4 or struct sockaddr_in6 for IPv6). In the case of struct sockaddr_in, it is cast to (struct sockaddr*) because bind expects a generic pointer to a socket address structure.

3- sizeof(serverAddress):

The third argument specifies the size of the socket address structure. It tells bind the size of the memory block it should consider when interpreting the address structure. The size is calculated using the sizeof operator.
Here's a simple example that includes error handling:

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

int main() {
    struct sockaddr_in serverAddress;

    // Initialize serverAddress
    serverAddress.sin_family = AF_INET;
    serverAddress.sin_port = htons(8080); // Port number in network byte order
    serverAddress.sin_addr.s_addr = INADDR_ANY; // Allow connections from any IP
    memset(&(serverAddress.sin_zero), 0, sizeof(serverAddress.sin_zero));

    // Create a socket
    int serverSocket = socket(AF_INET, SOCK_STREAM, 0);
    if (serverSocket == -1) {
        std::cerr << "Error creating socket." << std::endl;
        return 1;
    }

    // Bind the socket to the address and port
    if (bind(serverSocket, (struct sockaddr*)&serverAddress, sizeof(serverAddress)) == -1) {
        std::cerr << "Bind failed: " << strerror(errno) << std::endl;
        close(serverSocket);
        return 1;
    }

    // Further code for listening, accepting connections, etc.

    return 0;
}

In this example, bind is used to associate the serverSocket with the IP address INADDR_ANY and port 8080. INADDR_ANY means the server will listen on all available network interfaces. If you want to bind to a specific network interface or IP address, you should set serverAddress.sin_addr.s_addr accordingly.

After a successful bind, the server socket is ready to accept incoming connections on the specified address and port. The subsequent steps typically involve calling listen and handling client connections using accept.

Listen for Connections (for Server)

Make the server listen for incoming connections.

listen(serverSocket, 5);

1- serverSocket:

This is the file descriptor of the socket that has been created and bound to a specific local address and port using socket and bind functions, respectively.

2- 5:

The second argument to listen specifies the maximum number of pending connections that can be queued up by the operating system while the server is handling existing connections. This value is often referred to as the "backlog."

The operating system will keep a queue of pending connections, and when the server is busy handling existing connections, new incoming connections will be queued in this backlog. The 5 in the example is just an arbitrary number; you can adjust it based on your server's expected workload.

Accept Connections (for Server)

Accept incoming connections on the server side.

int clientSocket = accept(serverSocket, NULL, NULL);

The accept function is used in network programming to accept an incoming connection on a server socket that has been set up with listen. It creates a new socket for the communication with the client.

1- serverSocket:

This is the file descriptor of the server socket that has been created, bound, and listened to using the socket, bind, and listen functions, respectively.

2- NULL, NULL:

The second and third arguments are pointers to structures that can be used to store the client's address information (e.g., struct sockaddr). However, in many cases, when you're not interested in the client's address details, you can pass NULL for both arguments.

If you want to retrieve the client's address, you can provide pointers to struct sockaddr variables. For example:

struct sockaddr_in clientAddress;
socklen_t clientAddressLength = sizeof(clientAddress);

int clientSocket = accept(serverSocket, (struct sockaddr*)&clientAddress, &clientAddressLength);

This allows you to obtain information about the client's address after the connection is established.

3- Return Value (clientSocket):

The accept function returns a new file descriptor (clientSocket) representing the accepted connection. This new socket is used for communication with the specific client. You can use send and recv functions on clientSocket to exchange data with the connected client.

Connect to Server (for Client)

For the client, connect to the server.

connect(clientSocket, (struct sockaddr*)&serverAddress, sizeof(serverAddress));

The connect function in network programming is used by a client to establish a connection to a server. It is typically used after creating a client socket with the socket function and configuring the server's address using the struct sockaddr_in structure.

1- clientSocket:

This is the file descriptor of the client socket created with the socket function. It represents the endpoint of the client-side connection.

2- (struct sockaddr)&serverAddress:*

The second argument is a pointer to a struct sockaddr (or its derivatives like struct sockaddr_in for IPv4 or struct sockaddr_in6 for IPv6) representing the server's address.

3- sizeof(serverAddress):

The third argument specifies the size of the socket address structure. It tells connect the size of the memory block it should consider when interpreting the address structure.

Send and Receive Data

Use send() and recv() functions to send and receive data over the network.

send(clientSocket, "Hello, Server!", sizeof("Hello, Server!"), 0);

Close Sockets

Properly close sockets when the communication is done.

close(serverSocket);
close(clientSocket);

Example:

Below is a complete example that demonstrates a simple client-server communication using sockets in C++. In this example, the server listens for incoming connections, and the client connects to the server. Once the connection is established, the client sends a message to the server, and the server echoes the message back to the client.

This example uses the basic socket, bind, listen, accept, connect, send, and recv functions.

Server (Echo Server):

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

int main() {
    // Server configuration
    int serverSocket = socket(AF_INET, SOCK_STREAM, 0);
    if (serverSocket == -1) {
        std::cerr << "Error creating server socket." << std::endl;
        return 1;
    }

    struct sockaddr_in serverAddress;
    serverAddress.sin_family = AF_INET;
    serverAddress.sin_port = htons(8080);  // Port number in network byte order
    serverAddress.sin_addr.s_addr = INADDR_ANY;
    memset(&(serverAddress.sin_zero), 0, sizeof(serverAddress.sin_zero));

    if (bind(serverSocket, (struct sockaddr*)&serverAddress, sizeof(serverAddress)) == -1) {
        std::cerr << "Bind failed: " << strerror(errno) << std::endl;
        close(serverSocket);
        return 1;
    }

    if (listen(serverSocket, 5) == -1) {
        std::cerr << "Listen failed: " << strerror(errno) << std::endl;
        close(serverSocket);
        return 1;
    }

    std::cout << "Server is listening on port 8080..." << std::endl;

    // Accept incoming connection
    struct sockaddr_in clientAddress;
    socklen_t clientAddressLength = sizeof(clientAddress);

    int clientSocket = accept(serverSocket, (struct sockaddr*)&clientAddress, &clientAddressLength);
    if (clientSocket == -1) {
        std::cerr << "Accept failed: " << strerror(errno) << std::endl;
        close(serverSocket);
        return 1;
    }

    std::cout << "Connection established with client." << std::endl;

    // Receive and echo messages
    char buffer[1024];
    ssize_t bytesRead;

    while ((bytesRead = recv(clientSocket, buffer, sizeof(buffer), 0)) > 0) {
        buffer[bytesRead] = '\0';  // Ensure null-terminated string
        std::cout << "Received: " << buffer << std::endl;

        // Echo back to the client
        send(clientSocket, buffer, bytesRead, 0);
    }

    // Close sockets
    close(clientSocket);
    close(serverSocket);

    std::cout << "Server closed." << std::endl;

    return 0;
}

Client (Echo Client):

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

int main() {
    // Client configuration
    int clientSocket = socket(AF_INET, SOCK_STREAM, 0);
    if (clientSocket == -1) {
        std::cerr << "Error creating client socket." << std::endl;
        return 1;
    }

    struct sockaddr_in serverAddress;
    serverAddress.sin_family = AF_INET;
    serverAddress.sin_port = htons(8080);  // Port number in network byte order
    serverAddress.sin_addr.s_addr = inet_addr("127.0.0.1");  // Server's IP address

    // Connect to the server
    if (connect(clientSocket, (struct sockaddr*)&serverAddress, sizeof(serverAddress)) == -1) {
        std::cerr << "Connection failed: " << strerror(errno) << std::endl;
        close(clientSocket);
        return 1;
    }

    std::cout << "Connected to the server." << std::endl;

    // Send a message to the server
    const char* message = "Hello, Server!";
    send(clientSocket, message, strlen(message), 0);

    // Receive and display the echoed message
    char buffer[1024];
    ssize_t bytesRead = recv(clientSocket, buffer, sizeof(buffer), 0);

    if (bytesRead > 0) {
        buffer[bytesRead] = '\0';  // Ensure null-terminated string
        std::cout << "Server echoed: " << buffer << std::endl;
    } else {
        std::cerr << "Error receiving echoed message." << std::endl;
    }

    // Close the client socket
    close(clientSocket);

    std::cout << "Client closed." << std::endl;

    return 0;
}

This example assumes that the server and client are running on the same machine, and the server is listening on port 8080. You can modify the IP address and port number according to your requirements. Additionally, error handling is included to provide information in case of failures during socket creation, connection, or data exchange.

⚠️ **GitHub.com Fallback** ⚠️