Logs
Consultez les logs.
OK
Liste des données
Consultez la liste des données.
OK
Loading...
Formulaire
Saisissez vos données.
Enregistrer
Annuler

Apprendre C++

Vues
292

Introduction


C++ est un langage de programmation orientée objet. Il offre la possibilité d'avoir le contrôle total sur la gestion de la mémoire.


### Travailler avec les fichiers


C++ est un langage de programmation orientée objet prenant en charge la manipulation de fichier. Un fichier est un moyen de stocker des données de manière permanente sur un disque dur sous une forme textuelle ou binaire


Manipuler des fichiers textuels ou binaires en C++ sous Windows


C++ est un langage de programmation orientée objet prenant en charge la manipulation de fichiers au format textuel ou binaire. Dans ce tutoriel, nous utiliserons le module (fstream), pour vérifier l'existence d'un fichier et écrire ou lire des données textuelles ou binaires à partir d'un fichier. Nous utiliserons le module (filesystem), pour récupérer le nom, l'extension et le répertoire parent d'un fichier et pour créer un chemin de répertoires.


Gestion du programme principal


// main.cpp (Editer le programme principal)
...
#include "cFile.h"
#include <iostream>

static const std::string DEF_INPUT_PATH = "../../data/input";
static const std::string DEF_OUTPUT_PATH = "../../data/output";
static const std::string DEF_INPUT_IMAGE_FILE = "../../data/input/cpp.png";
static const std::string DEF_INPUT_CV_FILE = "../../data/input/cv.txt";
static const std::string DEF_INPUT_NO_IMAGE_FILE = "../../data/input/no-file.img";
static const std::string DEF_INPUT_NO_PATH = "../../data/input/no-path";
static const std::string DEF_OUTPUT_NEW_PATH = "../../data/output/new/path";
static const std::string DEF_OUTPUT_IMAGE_FILE = "../../data/output/image/cpp.png";
static const std::string DEF_OUTPUT_CV_FILE = "../../data/output/text/cv.txt";

int main(int, char **)
{
    {
        cFile oFile(DEF_INPUT_IMAGE_FILE);
        std::cout << DEF_INPUT_IMAGE_FILE << std::endl
                  << "|oFile.exitsFile()=" << oFile.exitsFile() << std::endl
                  << "|oFile.isFile()=" << oFile.isFile() << std::endl
                  << "|oFile.isDir()=" << oFile.isDir() << std::endl
                  << "|oFile.getFilename()=" << oFile.getFilename() << std::endl
                  << "|oFile.getBasename()=" << oFile.getBasename() << std::endl
                  << "|oFile.getExtension()=" << oFile.getExtension() << std::endl
                  << "|oFile.getPath()=" << oFile.getPath() << std::endl
                  << "|oFile.getAbsolutePath()=" << oFile.getAbsolutePath() << std::endl
                  << std::endl;
    }
    {
        cFile oFile(DEF_INPUT_NO_IMAGE_FILE);
        std::cout << DEF_INPUT_NO_IMAGE_FILE << std::endl
                  << "|oFile.exitsFile()=" << oFile.exitsFile() << std::endl
                  << "|oFile.isFile()=" << oFile.isFile() << std::endl
                  << "|oFile.isDir()=" << oFile.isDir() << std::endl
                  << "|oFile.getFilename()=" << oFile.getFilename() << std::endl
                  << "|oFile.getBasename()=" << oFile.getBasename() << std::endl
                  << "|oFile.getExtension()=" << oFile.getExtension() << std::endl
                  << "|oFile.getPath()=" << oFile.getPath() << std::endl
                  << "|oFile.getAbsolutePath()=" << oFile.getAbsolutePath() << std::endl
                  << std::endl;
    }
    {
        cFile oFile(DEF_INPUT_PATH);
        std::cout << DEF_INPUT_PATH << std::endl
                  << "|oFile.exitsFile()=" << oFile.exitsFile() << std::endl
                  << "|oFile.isFile()=" << oFile.isFile() << std::endl
                  << "|oFile.isDir()=" << oFile.isDir() << std::endl
                  << "|oFile.getFilename()=" << oFile.getFilename() << std::endl
                  << "|oFile.getBasename()=" << oFile.getBasename() << std::endl
                  << "|oFile.getExtension()=" << oFile.getExtension() << std::endl
                  << "|oFile.getPath()=" << oFile.getPath() << std::endl
                  << "|oFile.getAbsolutePath()=" << oFile.getAbsolutePath() << std::endl
                  << std::endl;
    }
    {
        cFile oFile(DEF_INPUT_NO_PATH);
        std::cout << DEF_INPUT_NO_PATH << std::endl
                  << "|oFile.exitsFile()=" << oFile.exitsFile() << std::endl
                  << "|oFile.isFile()=" << oFile.isFile() << std::endl
                  << "|oFile.isDir()=" << oFile.isDir() << std::endl
                  << "|oFile.getFilename()=" << oFile.getFilename() << std::endl
                  << "|oFile.getBasename()=" << oFile.getBasename() << std::endl
                  << "|oFile.getExtension()=" << oFile.getExtension() << std::endl
                  << "|oFile.getPath()=" << oFile.getPath() << std::endl
                  << "|oFile.getAbsolutePath()=" << oFile.getAbsolutePath() << std::endl
                  << std::endl;
    }
    {
        cFile oFile(DEF_OUTPUT_NEW_PATH);
        std::cout << DEF_OUTPUT_NEW_PATH << std::endl
                  << "|oFile.createDirectory()=" << oFile.createDirectory() << std::endl
                  << std::endl;
        oFile.createDirectory();
    }
    {
        cFile oInput(DEF_INPUT_CV_FILE);
        std::string oCV = oInput.readText();
        cFile oOutput(DEF_OUTPUT_CV_FILE);
        if (cFile(oOutput.getPath()).createDirectory())
        {
            oOutput.writeText(oCV);
        }

        std::cout << DEF_INPUT_CV_FILE << std::endl
                  << DEF_OUTPUT_CV_FILE << std::endl
                  << "|oInput.readText()=" << "readText" << std::endl
                  << "|oOutput.writeText()=" << "writeBin" << std::endl
                  << "|oCV.length()=" << oCV.length() << std::endl
                  << "|oCV.size()=" << oCV.size() << std::endl
                  << std::endl;
    }
    {
        cFile oInput(DEF_INPUT_IMAGE_FILE);
        std::string oImage = oInput.readBin();
        cFile oOutput(DEF_OUTPUT_IMAGE_FILE);
        if (cFile(oOutput.getPath()).createDirectory())
        {
            oOutput.writeBin(oImage);
        }

        std::cout << DEF_INPUT_IMAGE_FILE << std::endl
                  << DEF_OUTPUT_IMAGE_FILE << std::endl
                  << "|oInput.readBin()=" << "readBin" << std::endl
                  << "|oOutput.writeBin()=" << "writeBin" << std::endl
                  << "|oImage.length()=" << oImage.length() << std::endl
                  << "|oImage.size()=" << oImage.size() << std::endl
                  << std::endl;
    }
}
...

Gestion d'un fichier textuel ou binaire


// cFile.h (Editer la gestion d'un fichier textuel ou binaire)
...
#pragma once

#include <string>

class cFile
{
public:
    explicit cFile(const std::string &_filename);
    ~cFile();
    bool exitsFile() const;
    bool isFile() const;
    bool isDir() const;

    std::string getFilename() const;
    std::string getBasename() const;
    std::string getExtension() const;
    std::string getPath() const;
    std::string getAbsolutePath() const;

    bool createDirectory() const;

    std::string readText() const;
    void writeText(const std::string &_data) const;
    std::string readBin() const;
    void writeBin(const std::string &_data) const;

private:
    std::string m_filename;
};
...

// cFile.cpp (Editer la gestion d'un fichier textuel ou binaire)
...
#include "cFile.h"
#include <fstream>
#include <sstream>
#include <filesystem>
#include <vector>

cFile::cFile(const std::string &_filename)
    : m_filename(_filename)
{
}

cFile::~cFile()
{
}

bool cFile::exitsFile() const
{
    std::ifstream oFile(m_filename);
    return oFile.good();
}

bool cFile::isFile() const
{
    std::filesystem::path oPath(m_filename);
    return std::filesystem::is_regular_file(oPath);
}

bool cFile::isDir() const
{
    std::filesystem::path oPath(m_filename);
    return std::filesystem::is_directory(oPath);
}

std::string cFile::getFilename() const
{
    std::filesystem::path oPath(m_filename);
    return oPath.filename().string();
}

std::string cFile::getBasename() const
{
    std::filesystem::path oPath(m_filename);
    return oPath.stem().string();
}

std::string cFile::getExtension() const
{
    std::filesystem::path oPath(m_filename);
    return oPath.extension().string();
}

std::string cFile::getPath() const
{
    std::filesystem::path oPath(m_filename);
    return oPath.parent_path().string();
}

std::string cFile::getAbsolutePath() const
{
    try
    {
        std::filesystem::path oPath(m_filename);
        return std::filesystem::canonical(oPath).string();
    }
    catch (...)
    {
    }
    return "";
}

bool cFile::createDirectory() const
{
    try
    {
        std::filesystem::create_directories(m_filename);
        return true;
    }
    catch (...)
    {
    }
    return false;
}

std::string cFile::readText() const
{
    std::ifstream oFile(m_filename);
    std::stringstream oBuffer;
    oBuffer << oFile.rdbuf();
    return oBuffer.str();
}

void cFile::writeText(const std::string &_data) const
{
    std::ofstream oFile(m_filename);
    oFile << _data.c_str();
}

std::string cFile::readBin() const
{
    std::ifstream oFile(m_filename, std::ios::in | std::ios::binary);
    std::vector<char> oData = std::vector<char>(std::istreambuf_iterator<char>(oFile), std::istreambuf_iterator<char>());
    return std::string(&oData[0], oData.size());
}

void cFile::writeBin(const std::string &_data) const
{
    std::ofstream oFile(m_filename, std::ios::out | std::ios::binary);
    oFile.write(&_data[0], _data.size());
}
...

Gestion du fichier CMake


// CMakeLists.txt (Editer le fichier CMake)
...
cmake_minimum_required(VERSION 3.10.0)
project(rdvcpp VERSION 0.1.0 LANGUAGES C CXX)

set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_STANDARD 17)

add_executable(${PROJECT_NAME}
    main.cpp
    cFile.cpp
)
...

Exécution du projet


// Terminal (Exécuter le projet)
...
rdvcpp.exe
...

// Application (Opérations sur un fichier existant)
image.png

// Application (Opérations sur un fichier non existant)
image.png

// Application (Opérations sur un répertoire existant)
image.png

// Application (Opérations sur un répertoire non existant)
image.png

// Application (Opérations sur la création sur d'un nouveau répertoire)
image.png

// Application (Opérations sur un fichier texte)
image.png

// Application (Opérations sur un fichier binaire) 
image.png

// Application (Capture sur les fichiers manipulés)
image.png


### Travailler avec les sockets


Un socket est un point de terminaison utilisé pour établir une communication entre un client et un serveur. Les sockets sont supportés par le langage C++.


Créer une application client-serveur TCP/IP en C++ sous Windows


C++ est un langage de programmation orientée objet prenant en charge la création d'applications client/serveur. Dans ce tutoriel, nous utiliserons le module (Winsock2), pour établir une communication TCP/IP entre un client et un serveur. 


Gestion du serveur



Gestion du programme principal


// main.cpp (Editer le programme principal)
...
#include "cServer.h"

int main(int _argc, char **_argv)
{
    cServer oServer(_argc, _argv);
    oServer.run();
    return 0;
}
...

Gestion du serveur


// cServer.h (Editer le serveur)
...
#pragma once

#include <string>

class cServer
{
public:
    explicit cServer(int _argc, char **_argv);
    ~cServer();
    void run();

private:
    std::string getLastError(int _error) const;

private:
    int m_argc;
    char **m_argv;
};
...

// cServer.cpp (Editer le serveur)
...
#include "cServer.h"
#include "cServerInit.h"
#include <winsock2.h>
#include <iostream>

static const int DEF_WINSOCK_MAJOR_VERSION = 2;
static const int DEF_WINSOCK_MINOR_VERSION = 2;
static const int DEF_WINSOCK_ERROR_MSG_LENGTH = 512;

cServer::cServer(int _argc, char **_argv)
    : m_argc(_argc),
      m_argv(_argv)
{
}

cServer::~cServer()
{
}

void cServer::run()
{
    WSADATA oWSADATA;
    int oWSAStartup = WSAStartup(MAKEWORD(DEF_WINSOCK_MAJOR_VERSION, DEF_WINSOCK_MINOR_VERSION),
                                 &oWSADATA);

    if (oWSAStartup != 0)
    {
        std::cout << "L'initialisation du point de terminaison a echoue."
                  << "|errorCode=" << oWSAStartup
                  << "|errorMsg=" << getLastError(oWSAStartup)
                  << "|WINSOCK_MAJOR_VERSION=" << DEF_WINSOCK_MAJOR_VERSION
                  << "|WINSOCK_MINOR_VERSION=" << DEF_WINSOCK_MINOR_VERSION
                  << std::endl;
        return;
    }

    cServerInit oServerRun;
    oServerRun.run();
}

std::string cServer::getLastError(int _error) const
{
    char oErrorMsg[DEF_WINSOCK_ERROR_MSG_LENGTH] = {0};
    int oLength = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
                                NULL,
                                _error,
                                MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                                oErrorMsg,
                                sizeof(oErrorMsg),
                                NULL);
    if (oLength > 0)
    {
        oErrorMsg[oLength - 1] = 0;
    }
    std::string oMessage = oErrorMsg;
    return oMessage;
}
...

Gestion de l'initialisation du point de terminaison


// cServerInit.h (Editer l'initialisation du point de terminaison)
...
#pragma once

#include <string>

class cServerInit
{
    friend class cServer;

private:
    explicit cServerInit();
    ~cServerInit();
    void run();

private:
    std::string getLastError(int _error) const;
};
...

// cServerInit.cpp (Editer l'initialisation du point de terminaison)
...
#include "cServerInit.h"
#include "cServerSocket.h"
#include <winsock2.h>
#include <iostream>

static const int DEF_WINSOCK_ERROR_MSG_LENGTH = 512;

cServerInit::cServerInit()
{
}

cServerInit::~cServerInit()
{
    WSACleanup();
}

void cServerInit::run()
{
    SOCKET oSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    if (oSocket == INVALID_SOCKET)
    {
        std::cout << "La creation du point de terminaison a echoue."
                  << "|errorCode=" << GetLastError()
                  << "|errorMsg=" << getLastError(GetLastError())
                  << std::endl;
        return;
    }

    cServerSocket oServerSocket(oSocket);
    oServerSocket.run();
}

std::string cServerInit::getLastError(int _error) const
{
    char oErrorMsg[DEF_WINSOCK_ERROR_MSG_LENGTH] = {0};
    int oLength = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
                                NULL,
                                _error,
                                MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                                oErrorMsg,
                                sizeof(oErrorMsg),
                                NULL);
    if (oLength > 0)
    {
        oErrorMsg[oLength - 1] = 0;
    }
    std::string oMessage = oErrorMsg;
    return oMessage;
}
...

Gestion de la création du point de terminaison


// cServerSocket.h (Editer la création du point de terminaison)
...
#pragma once

#include <string>
#include <winsock2.h>

class cServerSocket
{
    friend class cServerInit;

private:
    explicit cServerSocket(SOCKET _socket);
    ~cServerSocket();
    void run();

private:
    static void onAccept(SOCKET _client);
    std::string getLastError(int _error) const;

private:
    SOCKET m_socket;
};
...

// cServerSocket.cpp (Editer la création du point de terminaison)
...
#include "cServerSocket.h"
#include "cServerClient.h"
#include <future>
#include <iostream>

static const int DEF_WINSOCK_ERROR_MSG_LENGTH = 512;
static const int DEF_WINSOCK_SERVER_PORT = 5555;
static const int DEF_WINSOCK_SERVER_BACKLOG = 10;

cServerSocket::cServerSocket(SOCKET _socket)
    : m_socket(_socket)
{
}

cServerSocket::~cServerSocket()
{
    closesocket(m_socket);
}

void cServerSocket::run()
{
    SOCKADDR_IN oServerAddr;
    oServerAddr.sin_addr.s_addr = INADDR_ANY;
    oServerAddr.sin_family = AF_INET;
    oServerAddr.sin_port = htons(DEF_WINSOCK_SERVER_PORT);

    if ((bind(m_socket, reinterpret_cast<SOCKADDR *>(&oServerAddr),
              sizeof(oServerAddr))) == SOCKET_ERROR)
    {
        std::cout << "La liaison de l'adresse du point de terminaison a echoue."
                  << "|errorCode=" << GetLastError()
                  << "|errorMsg=" << getLastError(GetLastError())
                  << std::endl;
        return;
    }

    if ((listen(m_socket, DEF_WINSOCK_SERVER_BACKLOG)) == SOCKET_ERROR)
    {
        std::cout << "L'initialisation du nombre de connexions au point de terminaison a echoue."
                  << "|errorCode=" << GetLastError()
                  << "|errorMsg=" << getLastError(GetLastError())
                  << std::endl;
        return;
    }

    std::cout << "Le serveur a demarre..."
              << "|port=" << DEF_WINSOCK_SERVER_PORT
              << std::endl;

    while (true)
    {
        SOCKADDR_IN oAddress;
        int oSize = sizeof(oAddress);
        SOCKET oClient = accept(m_socket, reinterpret_cast<SOCKADDR *>(&oAddress), &oSize);

        if (oClient == INVALID_SOCKET)
        {
            std::cout << "La connexion d'un client au point de terminaison a echoue."
                      << "|errorCode=" << GetLastError()
                      << "|errorMsg=" << getLastError(GetLastError())
                      << std::endl;
            continue;
        }

        auto fut = std::async(std::launch::async, onAccept, oClient);
    }
}

void cServerSocket::onAccept(SOCKET _client)
{
    cServerClient oServerClient(_client);
    oServerClient.run();
}

std::string cServerSocket::getLastError(int _error) const
{
    char oErrorMsg[DEF_WINSOCK_ERROR_MSG_LENGTH] = {0};
    int oLength = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
                                NULL,
                                _error,
                                MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                                oErrorMsg,
                                sizeof(oErrorMsg),
                                NULL);
    if (oLength > 0)
    {
        oErrorMsg[oLength - 1] = 0;
    }
    std::string oMessage = oErrorMsg;
    return oMessage;
}
...

Gestion de la communication avec un client


// cServerClient.h (Editer la communication avec un client)
...
#pragma once

#include <string>
#include <winsock2.h>

class cServerClient
{
    friend class cServerSocket;

private:
    explicit cServerClient(SOCKET _socket);
    ~cServerClient();
    void run();

private:
    bool recvData(std::string &_request);
    bool sendData(const std::string &_response);
    std::string getLastError(int _error) const;

private:
    SOCKET m_socket;
};
...

// cServerClient.cpp (Editer la communication avec un client)
...
#include "cServerClient.h"
#include <iostream>

static const int DEF_WINSOCK_ERROR_MSG_LENGTH = 512;
static const int DEF_WINSOCK_BUFFER_SIZE = 1024;

cServerClient::cServerClient(SOCKET _socket)
    : m_socket(_socket)
{
}

cServerClient::~cServerClient()
{
    closesocket(m_socket);
}

void cServerClient::run()
{
    std::string oRequest;
    std::string oResponse = "Un probleme a ete rencontre.";
    if (recvData(oRequest))
    {
        oResponse = "Votre requete a ete traite avec succes.";
    }
    sendData(oResponse);

    std::cout << "[Client] : " << oRequest << std::endl;
    std::cout << "[Server] : " << oResponse << std::endl;
}

bool cServerClient::recvData(std::string &_request)
{
    char oBuffer[DEF_WINSOCK_BUFFER_SIZE];
    int oBytes = recv(m_socket, oBuffer, DEF_WINSOCK_BUFFER_SIZE, 0);
    if (oBytes == SOCKET_ERROR)
    {
        std::cout << "La reception des donnees du point de terminaison a echoue."
                  << "|errorCode=" << GetLastError()
                  << "|errorMsg=" << getLastError(GetLastError())
                  << std::endl;
        return false;
    }
    oBuffer[oBytes] = '\0';
    _request = oBuffer;
    return true;
}

bool cServerClient::sendData(const std::string &_response)
{
    int oBytes = send(m_socket, _response.c_str(), (int)_response.length(), 0);
    if (oBytes == SOCKET_ERROR)
    {
        std::cout << "La reception des donnees du point de terminaison a echoue."
                  << "|errorCode=" << GetLastError()
                  << "|errorMsg=" << getLastError(GetLastError())
                  << std::endl;
        return false;
    }
    return true;
}

std::string cServerClient::getLastError(int _error) const
{
    char oErrorMsg[DEF_WINSOCK_ERROR_MSG_LENGTH] = {0};
    int oLength = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
                                NULL,
                                _error,
                                MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                                oErrorMsg,
                                sizeof(oErrorMsg),
                                NULL);
    if (oLength > 0)
    {
        oErrorMsg[oLength - 1] = 0;
    }
    std::string oMessage = oErrorMsg;
    return oMessage;
}
...

Gestion du fichier CMake


// CMakeLists.txt (Editer le fichier CMake)
...
cmake_minimum_required(VERSION 3.10.0)
project(rdvcpp VERSION 0.1.0 LANGUAGES C CXX)

add_executable(${PROJECT_NAME}
    main.cpp
    cServer.cpp
    cServerInit.cpp
    cServerSocket.cpp
    cServerClient.cpp
)

target_link_libraries(${PROJECT_NAME}
    ws2_32
)
...

Exécution du projet


// Terminal (Exécuter le projet)
...
rdvcpp.exe
...

// Application (Fiche de démarrage du serveur)

image.png

// Application (Fiche de traitement d'une requête du client)

image.png

Gestion du client



Gestion du programme principal


// main.cpp (Editer le programme principal)
...
#include "cClient.h"
#include <iostream>

int main(int _argc, char **_argv)
{
    cClient oClient(_argc, _argv);

    std::string oRequest("Bonjour tout le monde");
    std::string oResponse;

    if (oClient.run(oRequest, oResponse))
    {
        std::cout << "[Client] : " << oRequest << std::endl;
        std::cout << "[Server] : " << oResponse << std::endl;
    }

    return 0;
}
...

Gestion du client


// cClient.h (Editer le client)
...
#pragma once

#include <string>

class cClient
{
public:
    explicit cClient(int _argc, char **_argv);
    ~cClient();
    bool run(const std::string &_request, std::string &_response) const;

private:
    std::string getLastError(int _error) const;

private:
    int m_argc;
    char **m_argv;
};
...

// cClient.cpp (Editer le client)
...
#include "cClient.h"
#include "cClientInit.h"
#include <winsock2.h>
#include <iostream>

static const int DEF_WINSOCK_MAJOR_VERSION = 2;
static const int DEF_WINSOCK_MINOR_VERSION = 2;
static const int DEF_WINSOCK_ERROR_MSG_LENGTH = 512;

cClient::cClient(int _argc, char **_argv)
    : m_argc(_argc),
      m_argv(_argv)
{
}

cClient::~cClient()
{
}

bool cClient::run(const std::string &_request, std::string &_response) const
{
    WSADATA oWSADATA;
    int oWSAStartup = WSAStartup(MAKEWORD(DEF_WINSOCK_MAJOR_VERSION, DEF_WINSOCK_MINOR_VERSION),
                                 &oWSADATA);

    if (oWSAStartup != 0)
    {

        std::cout << "L'initialisation du point de terminaison a echoue."
                  << "|errorCode=" << oWSAStartup
                  << "|errorMsg=" << getLastError(oWSAStartup)
                  << "|WINSOCK_MAJOR_VERSION=" << DEF_WINSOCK_MAJOR_VERSION
                  << "|WINSOCK_MINOR_VERSION=" << DEF_WINSOCK_MINOR_VERSION
                  << std::endl;
        return false;
    }

    cClientInit oClientRun;
    return oClientRun.run(_request, _response);
}

std::string cClient::getLastError(int _error) const
{
    char oErrorMsg[DEF_WINSOCK_ERROR_MSG_LENGTH] = {0};
    int oLength = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
                                NULL,
                                _error,
                                MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                                oErrorMsg,
                                sizeof(oErrorMsg),
                                NULL);
    if (oLength > 0)
    {
        oErrorMsg[oLength - 1] = 0;
    }
    std::string oMessage = oErrorMsg;
    return oMessage;
}
...

Gestion de l'initialisation du point de terminaison


// cClientInit.h (Editer l'initialisation du point de terminaison)
...
#pragma once

#include <string>

class cClientInit
{
    friend class cClient;

private:
    explicit cClientInit();
    ~cClientInit();
    bool run(const std::string &_request, std::string &_response) const;

private:
    std::string getLastError(int _error) const;
};
...

// cClientInit.cpp (Editer l'initialisation du point de terminaison)
...
#include "cClientInit.h"
#include "cClientSocket.h"
#include <winsock2.h>
#include <iostream>

static const int DEF_WINSOCK_ERROR_MSG_LENGTH = 512;

cClientInit::cClientInit()
{
}

cClientInit::~cClientInit()
{
    WSACleanup();
}

bool cClientInit::run(const std::string &_request, std::string &_response) const
{
    SOCKET oSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    if (oSocket == INVALID_SOCKET)
    {
        std::cout << "La creation du point de terminaison a echoue."
                  << "|errorCode=" << GetLastError()
                  << "|errorMsg=" << getLastError(GetLastError())
                  << std::endl;
        return false;
    }

    cClientSocket oClientSocket(oSocket);
    return oClientSocket.run(_request, _response);
}

std::string cClientInit::getLastError(int _error) const
{
    char oErrorMsg[DEF_WINSOCK_ERROR_MSG_LENGTH] = {0};
    int oLength = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
                                NULL,
                                _error,
                                MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                                oErrorMsg,
                                sizeof(oErrorMsg),
                                NULL);
    if (oLength > 0)
    {
        oErrorMsg[oLength - 1] = 0;
    }
    std::string oMessage = oErrorMsg;
    return oMessage;
}
...

Gestion de la création du point de terminaison


// cClientSocket.h (Editer la création du point de terminaison)
...
#pragma once

#include <string>
#include <winsock2.h>

class cClientSocket
{
    friend class cClientInit;

private:
    explicit cClientSocket(SOCKET _socket);
    ~cClientSocket();
    bool run(const std::string &_request, std::string &_response);

private:
    bool recvData(std::string &_response);
    bool sendData(const std::string &_request);
    std::string getLastError(int _error) const;

private:
    SOCKET m_socket;
};
...

// cClientSocket.cpp (Editer la création du point de terminaison)
...
#include "cClientSocket.h"
#include <ws2tcpip.h>
#include <iostream>

static const int DEF_WINSOCK_ERROR_MSG_LENGTH = 512;
static const char *DEF_WINSOCK_SERVER_ADDRESS = "127.0.0.1";
static const int DEF_WINSOCK_SERVER_PORT = 5555;
static const int DEF_WINSOCK_BUFFER_SIZE = 1024;

cClientSocket::cClientSocket(SOCKET _socket)
    : m_socket(_socket)
{
}

cClientSocket::~cClientSocket()
{
    closesocket(m_socket);
}

bool cClientSocket::run(const std::string &_request, std::string &_response)
{
    SOCKADDR_IN oServerAddress;

    InetPton(AF_INET, DEF_WINSOCK_SERVER_ADDRESS, &oServerAddress.sin_addr.s_addr);
    oServerAddress.sin_family = AF_INET;
    oServerAddress.sin_port = htons(DEF_WINSOCK_SERVER_PORT);

    if (connect(m_socket, reinterpret_cast<SOCKADDR *>(&oServerAddress), sizeof(oServerAddress)) == SOCKET_ERROR)
    {
        std::cout << "La connexion au point de terminaison a echoue."
                  << "|errorCode=" << GetLastError()
                  << "|errorMsg=" << getLastError(GetLastError())
                  << std::endl;
        return false;
    }

    if (sendData(_request))
    {
        recvData(_response);
    }

    return true;
}

bool cClientSocket::recvData(std::string &_response)
{
    char oBuffer[DEF_WINSOCK_BUFFER_SIZE];
    int oBytes = recv(m_socket, oBuffer, DEF_WINSOCK_BUFFER_SIZE, 0);
    if (oBytes == SOCKET_ERROR)
    {
        std::cout << "La reception des donnees du point de terminaison a echoue."
                  << "|errorCode=" << GetLastError()
                  << "|errorMsg=" << getLastError(GetLastError())
                  << std::endl;
        return false;
    }
    oBuffer[oBytes] = '\0';
    _response = oBuffer;
    return true;
}

bool cClientSocket::sendData(const std::string &_request)
{
    int oBytes = send(m_socket, _request.c_str(), (int)_request.length(), 0);
    if (oBytes == SOCKET_ERROR)
    {
        std::cout << "La reception des donnees du point de terminaison a echoue."
                  << "|errorCode=" << GetLastError()
                  << "|errorMsg=" << getLastError(GetLastError())
                  << std::endl;
        return false;
    }
    return true;
}

std::string cClientSocket::getLastError(int _error) const
{
    char oErrorMsg[DEF_WINSOCK_ERROR_MSG_LENGTH] = {0};
    int oLength = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
                                NULL,
                                _error,
                                MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                                oErrorMsg,
                                sizeof(oErrorMsg),
                                NULL);
    if (oLength > 0)
    {
        oErrorMsg[oLength - 1] = 0;
    }
    std::string oMessage = oErrorMsg;
    return oMessage;
}
...

Gestion du fichier CMake


// CMakeLists.txt (Editer le fichier CMake)
...
cmake_minimum_required(VERSION 3.10.0)
project(rdvcpp VERSION 0.1.0 LANGUAGES C CXX)

add_executable(rdvcpp
    main.cpp
    cClient.cpp
    cClientInit.cpp
    cClientSocket.cpp
)

target_link_libraries(${PROJECT_NAME}
    ws2_32
)
...

Exécution du projet


// Terminal (Exécuter le projet)
...
rdvcpp.exe
...

// Application (Fiche d'envoi d'une requête au serveur)
image.png


Créer une application client-serveur TCP/IP de transfert de fichiers en C++ sous Windows


C++ est un langage de programmation orientée objet prenant en charge la création d'applications client/serveur. Dans ce tutoriel, nous utiliserons le module (Winsock2), pour établir une communication TCP/IP entre un client et un serveur. Nous utiliserons le module (Winsock2) pour réaliser un transfert de fichiers entre un client et un serveur. Nous limiterons la taille des données échangées à 1 Mo. Nous utiliserons la fonction (std::async) pour traiter plusieurs clients de manière asynchrone dans des threads séparés.


Gestion du serveur



Gestion du programme principal


// main.cpp (Editer le programme principal)
...
#include "cServer.h"

int main(int _argc, char **_argv)
{
    cServer oServer;
    oServer.run();
    return 0;
}
...

Gestion du serveur


// cServer.h (Editer le serveur)
...
#pragma once

#include <string>

class cServer
{
public:
    explicit cServer();
    ~cServer();
    void run();
};
...

// cServer.cpp (Editer le serveur)
...
#include "cServer.h"
#include "cServerInit.h"
#include "cErrorMsg.h"
#include <winsock2.h>
#include <iostream>

// winsock2
static const int DEF_WINSOCK_MAJOR_VERSION = 2;
static const int DEF_WINSOCK_MINOR_VERSION = 2;

cServer::cServer()
{
}

cServer::~cServer()
{
}

void cServer::run()
{
    WSADATA oWSADATA;
    int oWSAStartup = WSAStartup(MAKEWORD(DEF_WINSOCK_MAJOR_VERSION, DEF_WINSOCK_MINOR_VERSION),
                                 &oWSADATA);

    if (oWSAStartup != 0)
    {
        std::cout << "L'initialisation du point de terminaison a echoue."
                  << "|errorCode=" << oWSAStartup
                  << "|errorMsg=" << getLastErrorMsg(oWSAStartup)
                  << "|WINSOCK_MAJOR_VERSION=" << DEF_WINSOCK_MAJOR_VERSION
                  << "|WINSOCK_MINOR_VERSION=" << DEF_WINSOCK_MINOR_VERSION
                  << std::endl;
        return;
    }

    cServerInit oServerRun;
    oServerRun.run();
}
...

Gestion de l'initialisation du point de terminaison


// cServerInit.h (Editer l'initialisation du point de terminaison)
...
#pragma once

#include <string>

class cServerInit
{
    friend class cServer;

private:
    explicit cServerInit();
    ~cServerInit();
    void run();
};
...

// cServerInit.cpp (Editer l'initialisation du point de terminaison)
...
#include "cServerInit.h"
#include "cServerSocket.h"
#include "cErrorMsg.h"
#include <winsock2.h>
#include <iostream>

cServerInit::cServerInit()
{
}

cServerInit::~cServerInit()
{
    WSACleanup();
}

void cServerInit::run()
{
    SOCKET oSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    if (oSocket == INVALID_SOCKET)
    {
        std::cout << "La creation du point de terminaison a echoue."
                  << "|errorCode=" << GetLastError()
                  << "|errorMsg=" << getLastErrorMsg(GetLastError())
                  << std::endl;
        return;
    }

    cServerSocket oServerSocket(oSocket);
    oServerSocket.run();
}
...

Gestion de la création du point de terminaison


// cServerSocket.h (Editer la création du point de terminaison)
...
#pragma once

#include <string>
#include <winsock2.h>

class cServerSocket
{
    friend class cServerInit;

private:
    explicit cServerSocket(SOCKET _socket);
    ~cServerSocket();
    void run();

private:
    static void onAccept(SOCKET _client);

private:
    SOCKET m_socket;
};
...

// cServerSocket.cpp (Editer la création du point de terminaison)
...
#include "cServerSocket.h"
#include "cServerClient.h"
#include "cErrorMsg.h"
#include <future>
#include <iostream>

// winsock2
static const int DEF_WINSOCK_SERVER_PORT = 5555;
static const int DEF_WINSOCK_SERVER_BACKLOG = 10;

cServerSocket::cServerSocket(SOCKET _socket)
    : m_socket(_socket)
{
}

cServerSocket::~cServerSocket()
{
    closesocket(m_socket);
}

void cServerSocket::run()
{
    SOCKADDR_IN oServerAddr;
    oServerAddr.sin_addr.s_addr = INADDR_ANY;
    oServerAddr.sin_family = AF_INET;
    oServerAddr.sin_port = htons(DEF_WINSOCK_SERVER_PORT);

    if ((bind(m_socket, reinterpret_cast<SOCKADDR *>(&oServerAddr),
              sizeof(oServerAddr))) == SOCKET_ERROR)
    {
        std::cout << "La liaison de l'adresse du point de terminaison a echoue."
                  << "|errorCode=" << GetLastError()
                  << "|errorMsg=" << getLastErrorMsg(GetLastError())
                  << std::endl;
        return;
    }

    if ((listen(m_socket, DEF_WINSOCK_SERVER_BACKLOG)) == SOCKET_ERROR)
    {
        std::cout << "L'initialisation du nombre de connexions au point de terminaison a echoue."
                  << "|errorCode=" << GetLastError()
                  << "|errorMsg=" << getLastErrorMsg(GetLastError())
                  << std::endl;
        return;
    }

    std::cout << "Le serveur a demarre..."
              << "|port=" << DEF_WINSOCK_SERVER_PORT
              << std::endl;

    while (true)
    {
        SOCKADDR_IN oAddress;
        int oSize = sizeof(oAddress);
        SOCKET oClient = accept(m_socket, reinterpret_cast<SOCKADDR *>(&oAddress), &oSize);

        if (oClient == INVALID_SOCKET)
        {
            std::cout << "La connexion d'un client au point de terminaison a echoue."
                      << "|errorCode=" << GetLastError()
                      << "|errorMsg=" << getLastErrorMsg(GetLastError())
                      << std::endl;
            continue;
        }

        auto oFuture = std::async(std::launch::async, onAccept, oClient);
    }
}

void cServerSocket::onAccept(SOCKET _client)
{
    cServerClient oServerClient(_client);
    oServerClient.run();
}
...

Gestion de la communication avec un client


// cServerClient.h (Editer la communication avec un client)
...
#pragma once

#include <string>
#include <winsock2.h>

class cServerClient
{
    friend class cServerSocket;

private:
    explicit cServerClient(SOCKET _socket);
    ~cServerClient();
    void run();

private:
    bool recvData(std::string &_request);
    bool sendData(const std::string &_response);

private:
    SOCKET m_socket;
};
...

// cServerClient.cpp (Editer la communication avec un client)
...
#include "cServerClient.h"
#include "cFTP.h"
#include "cString.h"
#include "cErrorMsg.h"
#include <iostream>

// winsock2
static const int DEF_WINSOCK_BUFFER_SIZE = 1024;
static const int DEF_WINSOCK_BUFFER_MAX = 1 * 1024 * 1024; // 1 Mo

// limit
static const int DEF_REQUEST_LIMIT_SIZE = 100;

cServerClient::cServerClient(SOCKET _socket)
    : m_socket(_socket)
{
}

cServerClient::~cServerClient()
{
    closesocket(m_socket);
}

void cServerClient::run()
{
    std::string oRequest;
    std::string oResponse;
    cFTP::sFTP aFTP;

    if (recvData(oRequest))
    {
        cFTP oFTP;
        if (oFTP.saveFile(oRequest, aFTP))
        {
            oResponse = "Votre requete a ete traite avec succes.";
        }
        else
        {
            oResponse = "Le chargement du fichier a echoue.";
        }
    }
    else
    {
        oResponse = "Un probleme a ete rencontre.";
    }

    sendData(oResponse);

    std::cout << "[Client] : ================================" << std::endl;
    std::cout << "[Client] : sFTP.path=" << aFTP.path << std::endl;
    std::cout << "[Client] : sFTP.filename=" << aFTP.filename << std::endl;
    std::cout << "[Client] : sFTP.size=" << aFTP.size << std::endl;
    std::cout << "[Client] : sFTP.data.size()=" << aFTP.data.size() << std::endl;
    std::cout << "[Client] : sFTP.data=" << cString(aFTP.data).limit(DEF_REQUEST_LIMIT_SIZE).oneLine() << std::endl;
    std::cout << "[Server] : " << oResponse << std::endl;
}

bool cServerClient::recvData(std::string &_request)
{
    std::string oRequest;
    char oBuffer[DEF_WINSOCK_BUFFER_SIZE];
    while (true)
    {
        int oBytes = recv(m_socket, oBuffer, DEF_WINSOCK_BUFFER_SIZE - 1, 0);
        if (oBytes == SOCKET_ERROR)
        {
            std::cout << "La reception des donnees du point de terminaison a echoue."
                      << "|errorCode=" << GetLastError()
                      << "|errorMsg=" << getLastErrorMsg(GetLastError())
                      << std::endl;
            return false;
        }

        oRequest.append(oBuffer, oBytes);

        if (oRequest.size() >= DEF_WINSOCK_BUFFER_MAX)
        {
            std::cout << "Le nombre maximal de donnees sur le point de terminaison a ete atteint."
                      << "|DEF_WINSOCK_BUFFER_MAX=" << DEF_WINSOCK_BUFFER_MAX
                      << std::endl;
            return false;
        }

        u_long oBytesIO;
        int isOK = ioctlsocket(m_socket, FIONREAD, &oBytesIO);

        if (isOK == SOCKET_ERROR)
        {
            std::cout << "La lecture du nombre de donnees restantes sur le point de terminaison a echoue."
                      << "|errorCode=" << GetLastError()
                      << "|errorMsg=" << getLastErrorMsg(GetLastError())
                      << std::endl;
            return false;
        }

        if (oBytesIO == 0)
        {
            break;
        }
    }
    _request = oRequest;
    return true;
}

bool cServerClient::sendData(const std::string &_response)
{
    int oBytes = send(m_socket, _response.c_str(), (int)_response.length(), 0);
    if (oBytes == SOCKET_ERROR)
    {
        std::cout << "La reception des donnees du point de terminaison a echoue."
                  << "|errorCode=" << GetLastError()
                  << "|errorMsg=" << getLastErrorMsg(GetLastError())
                  << std::endl;
        return false;
    }
    return true;
}
...

Gestion de la réception de fichier


// cFTP.h (Editer la réception de fichier)
...
#pragma once

#include <string>

class cFTP
{
public:
    struct sFTP
    {
        std::string path;
        std::string filename;
        size_t size;
        std::string data;
    };

public:
    explicit cFTP();
    ~cFTP();
    bool saveFile(const std::string &_request, sFTP &_sFTP);

private:
    bool deserialize(const std::string &_request, sFTP &_sFTP);
};
...

// cFTP.cpp (Editer la réception de fichier)
...
#include "cFTP.h"
#include "cFile.h"
#include "cString.h"
#include <iostream>

// output
static const std::string DEF_OUTPUT_PATH = "../../data/output";

// request
static const int DEF_REQUEST_LIMIT_SIZE = 100;
static const std::string DEF_REQUEST_DATA_SEP = ";";

cFTP::cFTP()
{
}

cFTP::~cFTP()
{
}

bool cFTP::saveFile(const std::string &_request, sFTP &_sFTP)
{
    if (!deserialize(_request, _sFTP))
    {
        std::cout << "La deserialisation des donnees du fichier a echoue."
                  << "|request=" << cString(_request).limit(DEF_REQUEST_LIMIT_SIZE).oneLine()
                  << std::endl;
        return false;
    }

    if (!cFile(DEF_OUTPUT_PATH).createDirectory())
    {
        std::cout << "La creation du fichier de sortie a echoue."
                  << "|request=" << cString(_request).limit(DEF_REQUEST_LIMIT_SIZE).oneLine()
                  << std::endl;
        return false;
    }

    std::string oFilename = DEF_OUTPUT_PATH + "/" + _sFTP.filename;
    cFile oFile(oFilename);
    oFile.writeBin(_sFTP.data);

    return true;
}

bool cFTP::deserialize(const std::string &_request, sFTP &_sFTP)
{
    // path
    size_t oPathStart = 0;
    size_t oPathEnd = _request.find(DEF_REQUEST_DATA_SEP, oPathStart);
    if (oPathEnd == std::string::npos)
    {
        std::cout << "Le chemin du fichier est inconnu."
                  << "|request=" << cString(_request).limit(DEF_REQUEST_LIMIT_SIZE).oneLine()
                  << std::endl;
        return false;
    }
    _sFTP.path = _request.substr(oPathStart, oPathEnd - oPathStart);

    // filename
    size_t oFilenameStart = oPathEnd + DEF_REQUEST_DATA_SEP.size();
    size_t oFilenameEnd = _request.find(DEF_REQUEST_DATA_SEP, oFilenameStart);
    if (oFilenameEnd == std::string::npos)
    {
        std::cout << "Le nom du fichier est inconnu."
                  << "|request=" << cString(_request).limit(DEF_REQUEST_LIMIT_SIZE).oneLine()
                  << std::endl;
        return false;
    }
    _sFTP.filename = _request.substr(oFilenameStart, oFilenameEnd - oFilenameStart);

    // size
    size_t oSizeStart = oFilenameEnd + DEF_REQUEST_DATA_SEP.size();
    size_t oSizeEnd = _request.find(DEF_REQUEST_DATA_SEP, oSizeStart);
    if (oSizeEnd == std::string::npos)
    {
        std::cout << "La taille du fichier est inconnu."
                  << "|request=" << cString(_request).limit(DEF_REQUEST_LIMIT_SIZE).oneLine()
                  << std::endl;
        return false;
    }
    std::string oSize = _request.substr(oSizeStart, oSizeEnd - oSizeStart);
    if (!cString(oSize).toInt(_sFTP.size))
    {
        std::cout << "Le format de la taille du fichier est incorrect."
                  << "|size=" << cString(oSize).limit(DEF_REQUEST_LIMIT_SIZE).oneLine()
                  << std::endl;
        return false;
    }

    // data
    size_t oDataStart = oSizeEnd + DEF_REQUEST_DATA_SEP.size();
    _sFTP.data = _request.substr(oDataStart);

    return true;
}
...

Gestion des messages d'erreurs


// cErrorMsg.h (Editer la lecture des messages d'erreurs)
...
#pragma once

#include <string>

std::string getLastErrorMsg(int _error);
...

// cErrorMsg.cpp (Editer la lecture des messages d'erreurs)
...
#include "cErrorMsg.h"
#include <Windows.h>

static const int DEF_WINSOCK_ERROR_MSG_LENGTH = 512;

std::string getLastErrorMsg(int _error)
{
    char oErrorMsg[DEF_WINSOCK_ERROR_MSG_LENGTH] = {0};
    int oLength = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
                                NULL,
                                _error,
                                MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                                oErrorMsg,
                                DEF_WINSOCK_ERROR_MSG_LENGTH - 1,
                                NULL);
    if (oLength > 0)
    {
        oErrorMsg[oLength - 1] = 0;
    }
    std::string oMessage = oErrorMsg;
    return oMessage;
}
...

Gestion du fichier CMake


// CMakeLists.txt (Editer le fichier CMake)
...
cmake_minimum_required(VERSION 3.10.0)
project(rdvcpp VERSION 0.1.0 LANGUAGES C CXX)

set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_STANDARD 17)

add_executable(${PROJECT_NAME}
    main.cpp
    cServer.cpp
    cServerInit.cpp
    cServerSocket.cpp
    cServerClient.cpp
    cFile.cpp
    cFTP.cpp
    cErrorMsg.cpp
    cString.cpp
)

target_link_libraries(${PROJECT_NAME}
    ws2_32
)
...

Exécution du projet


// Terminal (Exécuter le projet)
...
rdvcpp.exe
...

// Application (Opérations sur le démarrage du serveur)
// Application (Opérations sur la réception du fichier textuel)
// Application (Opérations sur la réception du fichier binaire)
image.png

// Application (Capture sur la réception du fichier textuel)
image.png

// Application (Capture sur la réception du fichier binaire)
image.png

Gestion du client



Gestion du programme principal


// main.cpp (Editer le programme principal)
...
#include "cFTP.h"

static const std::string DEF_INPUT_CV_FILE = "../../data/input/cv.txt";
static const std::string DEF_INPUT_IMAGE_FILE = "../../data/input/cpp.png";

int main(int _argc, char **_argv)
{
    cFTP oFTP;
    oFTP.sendFile(DEF_INPUT_CV_FILE);
    oFTP.sendFile(DEF_INPUT_IMAGE_FILE);
    return 0;
}
...

Gestion du transfert de fichier


// cFTP.h (Editer le transfert de fichier)
...
#pragma once

#include <string>

class cFTP
{
public:
    struct sFTP
    {
        std::string path;
        std::string filename;
        size_t size;
        std::string data;
    };

public:
    explicit cFTP();
    ~cFTP();
    void sendFile(const std::string &_filename);

private:
    bool loadFile(const std::string &_filename, sFTP &_sFTP);
    std::string serialize(const sFTP &_sFTP);
};
...

// cFTP.cpp (Editer le transfert de fichier)
...
#include "cFTP.h"
#include "cFile.h"
#include "cClient.h"
#include "cString.h"
#include <iostream>

// request
static const int DEF_REQUEST_LIMIT_SIZE = 100;
static const std::string DEF_REQUEST_DATA_SEP = ";";

cFTP::cFTP()
{
}

cFTP::~cFTP()
{
}

void cFTP::sendFile(const std::string &_filename)
{
    sFTP oFTP;
    std::string oRequest;

    if (loadFile(_filename, oFTP))
    {
        oRequest = serialize(oFTP);
    }
    else
    {
        oRequest = "Erreur lors du chargement du fichier";
    }

    cClient oClient;
    std::string oResponse;

    if (oClient.run(oRequest, oResponse))
    {
        std::cout << "[Client] : ================================" << std::endl;
        std::cout << "[Client] : oRequest=" << cString(oRequest).limit(DEF_REQUEST_LIMIT_SIZE).oneLine() << std::endl;
        std::cout << "[Server] : " << oResponse << std::endl;
    }
}

bool cFTP::loadFile(const std::string &_filename, sFTP &_sFTP)
{
    cFile oFile(_filename);

    if (!oFile.exitsFile())
    {
        std::cout << "Le fichier n'existe pas."
                  << "|filename=" << _filename
                  << std::endl;
        return false;
    }

    _sFTP.path = _filename;
    _sFTP.filename = oFile.getFilename();
    _sFTP.data = oFile.readBin();
    _sFTP.size = _sFTP.data.size();
    return true;
}

std::string cFTP::serialize(const sFTP &_sFTP)
{
    std::string oData;
    oData += _sFTP.path + DEF_REQUEST_DATA_SEP;
    oData += _sFTP.filename + DEF_REQUEST_DATA_SEP;
    oData += std::to_string(_sFTP.size) + DEF_REQUEST_DATA_SEP;
    oData.append(&_sFTP.data[0], _sFTP.size);
    return oData;
}
...

Gestion du client


// cClient.h (Editer le client)
...
#pragma once

#include <string>

class cClient
{
public:
    explicit cClient();
    ~cClient();
    bool run(const std::string &_request, std::string &_response) const;
};
...

// cClient.cpp (Editer le client)
...
#include "cClient.h"
#include "cClientInit.h"
#include "cErrorMsg.h"
#include <winsock2.h>
#include <iostream>

// winsock2
static const int DEF_WINSOCK_MAJOR_VERSION = 2;
static const int DEF_WINSOCK_MINOR_VERSION = 2;

cClient::cClient()
{
}

cClient::~cClient()
{
}

bool cClient::run(const std::string &_request, std::string &_response) const
{
    WSADATA oWSADATA;
    int oWSAStartup = WSAStartup(MAKEWORD(DEF_WINSOCK_MAJOR_VERSION, DEF_WINSOCK_MINOR_VERSION),
                                 &oWSADATA);

    if (oWSAStartup != 0)
    {

        std::cout << "L'initialisation du point de terminaison a echoue."
                  << "|errorCode=" << oWSAStartup
                  << "|errorMsg=" << getLastErrorMsg(oWSAStartup)
                  << "|WINSOCK_MAJOR_VERSION=" << DEF_WINSOCK_MAJOR_VERSION
                  << "|WINSOCK_MINOR_VERSION=" << DEF_WINSOCK_MINOR_VERSION
                  << std::endl;
        return false;
    }

    cClientInit oClientRun;
    return oClientRun.run(_request, _response);
}
...

Gestion de l'initialisation du point de terminaison


// cClientInit.h (Editer l'initialisation du point de terminaison)
...
#pragma once

#include <string>

class cClientInit
{
    friend class cClient;

private:
    explicit cClientInit();
    ~cClientInit();
    bool run(const std::string &_request, std::string &_response) const;
};
...

// cClientInit.cpp (Editer l'initialisation du point de terminaison)
...
#include "cClientInit.h"
#include "cClientSocket.h"
#include "cErrorMsg.h"
#include <winsock2.h>
#include <iostream>

cClientInit::cClientInit()
{
}

cClientInit::~cClientInit()
{
    WSACleanup();
}

bool cClientInit::run(const std::string &_request, std::string &_response) const
{
    SOCKET oSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    if (oSocket == INVALID_SOCKET)
    {
        std::cout << "La creation du point de terminaison a echoue."
                  << "|errorCode=" << GetLastError()
                  << "|errorMsg=" << getLastErrorMsg(GetLastError())
                  << std::endl;
        return false;
    }

    cClientSocket oClientSocket(oSocket);
    return oClientSocket.run(_request, _response);
}
...

Gestion de la création du point de terminaison


// cClientSocket.h (Editer la création du point de terminaison)
...
#pragma once

#include <string>
#include <winsock2.h>

class cClientSocket
{
    friend class cClientInit;

private:
    explicit cClientSocket(SOCKET _socket);
    ~cClientSocket();
    bool run(const std::string &_request, std::string &_response);

private:
    bool recvData(std::string &_response);
    bool sendData(const std::string &_request);

private:
    SOCKET m_socket;
};
...

// cClientSocket.cpp (Editer la création du point de terminaison)
...
#include "cClientSocket.h"
#include "cErrorMsg.h"
#include <ws2tcpip.h>
#include <iostream>

// winsock2
static const char *DEF_WINSOCK_SERVER_ADDRESS = "127.0.0.1";
static const int DEF_WINSOCK_SERVER_PORT = 5555;
static const int DEF_WINSOCK_BUFFER_SIZE = 1024;

cClientSocket::cClientSocket(SOCKET _socket)
    : m_socket(_socket)
{
}

cClientSocket::~cClientSocket()
{
    closesocket(m_socket);
}

bool cClientSocket::run(const std::string &_request, std::string &_response)
{
    SOCKADDR_IN oServerAddress;

    InetPton(AF_INET, DEF_WINSOCK_SERVER_ADDRESS, &oServerAddress.sin_addr.s_addr);
    oServerAddress.sin_family = AF_INET;
    oServerAddress.sin_port = htons(DEF_WINSOCK_SERVER_PORT);

    if (connect(m_socket, reinterpret_cast<SOCKADDR *>(&oServerAddress), sizeof(oServerAddress)) == SOCKET_ERROR)
    {
        std::cout << "La connexion au point de terminaison a echoue."
                  << "|errorCode=" << GetLastError()
                  << "|errorMsg=" << getLastErrorMsg(GetLastError())
                  << std::endl;
        return false;
    }

    if (sendData(_request))
    {
        recvData(_response);
    }

    return true;
}

bool cClientSocket::recvData(std::string &_response)
{
    char oBuffer[DEF_WINSOCK_BUFFER_SIZE];
    int oBytes = recv(m_socket, oBuffer, DEF_WINSOCK_BUFFER_SIZE - 1, 0);
    if (oBytes == SOCKET_ERROR)
    {
        std::cout << "La reception des donnees du point de terminaison a echoue."
                  << "|errorCode=" << GetLastError()
                  << "|errorMsg=" << getLastErrorMsg(GetLastError())
                  << std::endl;
        return false;
    }
    oBuffer[oBytes] = '\0';
    _response = oBuffer;
    return true;
}

bool cClientSocket::sendData(const std::string &_request)
{
    int oBytes = send(m_socket, &_request[0], (int)_request.length(), 0);
    if (oBytes == SOCKET_ERROR)
    {
        std::cout << "La reception des donnees du point de terminaison a echoue."
                  << "|errorCode=" << GetLastError()
                  << "|errorMsg=" << getLastErrorMsg(GetLastError())
                  << std::endl;
        return false;
    }
    return true;
}
...

Gestion des messages d'erreurs


// cErrorMsg.h (Editer la lecture des messages d'erreurs)
...
#pragma once

#include <string>

std::string getLastErrorMsg(int _error);
...

// cErrorMsg.cpp (Editer la lecture des messages d'erreurs)
...
#include "cErrorMsg.h"
#include <Windows.h>

static const int DEF_WINSOCK_ERROR_MSG_LENGTH = 512;

std::string getLastErrorMsg(int _error)
{
    char oErrorMsg[DEF_WINSOCK_ERROR_MSG_LENGTH] = {0};
    int oLength = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
                                NULL,
                                _error,
                                MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                                oErrorMsg,
                                DEF_WINSOCK_ERROR_MSG_LENGTH - 1,
                                NULL);
    if (oLength > 0)
    {
        oErrorMsg[oLength - 1] = 0;
    }
    std::string oMessage = oErrorMsg;
    return oMessage;
}
...

Gestion du fichier CMake


// CMakeLists.txt (Editer le fichier CMake)
...
cmake_minimum_required(VERSION 3.10.0)
project(rdvcpp VERSION 0.1.0 LANGUAGES C CXX)

set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_STANDARD 17)

add_executable(rdvcpp
    main.cpp
    cClient.cpp
    cClientInit.cpp
    cClientSocket.cpp
    cFile.cpp
    cFTP.cpp
    cErrorMsg.cpp
)

target_link_libraries(${PROJECT_NAME}
    ws2_32
)
...

Exécution du projet


// Terminal (Exécuter le projet)
...
rdvcpp.exe
...

// Application (Opérations sur le transfert du fichier textuel)
// Application (Opérations sur le transfert du fichier binaire)
image.png

// Application (Capture sur le transfert du fichier textuel)
image.png

// Application (Capture sur le transfert du fichier binaire)
image.png


### Travailler avec les requêtes HTTP


C++ est un langage de programmation orientée objet prenant en charge la création de serveur HTTP. Un serveur HTTP est un serveur basé sur le protocole TCP/IP et chargé de traiter des requêtes HTTP reçues à partir d'un client HTTP. Un client HTTP est une application capable d'émettre des requêtes HTTP. Une requête HTTP commence par le nom de la méthode HTTP.


Créer un serveur HTTP en C++ sous Windows


C++ est un langage de programmation orientée objet prenant en charge la création de serveur HTTP. Un serveur HTTP est un serveur basé sur le protocole TCP/IP et chargé de traiter des requêtes HTTP reçues à partir d'un client HTTP. Un client HTTP est une application capable d'émettre des requêtes HTTP. Une requête HTTP commence par le nom de la méthode HTTP. La méthode GET permet de demander la lecture d'une ressource présente sur le serveur HTTP. Le serveur HTTP renvoie au client une réponse HTTP. Une réponse HTTP commence par la version HTTP. Dans ce tutoriel, nous utiliserons le navigateur web comme client HTTP pour émettre une requête HTTP. Nous utiliserons le module (Winsock2), pour implémenter un serveur HTTP capable de renvoyer une réponse HTTP imprimable dans le navigateur web. Pour des raisons de sécurité, nous limiterons la taille des données échangées entre le client vers le serveur à 1 Mo. Pour des raisons de réactivité, nous utiliserons la fonction (std::async) pour traiter en parallèle plusieurs demandes de clients de manière asynchrone dans des threads séparés. Pour des raisons de flexibilité, nous utiliserons la fonction (std::format) pour construire la réponse HTTP à retourner au navigateur web. Pour des raisons d'agilité, nous utiliserons les principes de développement SOLID, RAII, OBJET VALIDE.


Gestion du programme principal


// main.cpp (Editer le programme principal)
...
#include "cServer.h"

int main(int _argc, char **_argv)
{
    cServer oServer;
    oServer.run();
    return 0;
}
...

Gestion du serveur


// cServer.h (Editer le serveur)
...
#pragma once

#include <string>

class cServer
{
public:
    explicit cServer();
    ~cServer();
    void run();
};
...

// cServer.cpp (Editer le serveur)
...
#include "cServer.h"
#include "cServerInit.h"
#include "cErrorMsg.h"
#include <winsock2.h>
#include <iostream>

// winsock2
static const int DEF_WINSOCK_MAJOR_VERSION = 2;
static const int DEF_WINSOCK_MINOR_VERSION = 2;

cServer::cServer()
{
}

cServer::~cServer()
{
}

void cServer::run()
{
    WSADATA oWSADATA;
    int oWSAStartup = WSAStartup(MAKEWORD(DEF_WINSOCK_MAJOR_VERSION, DEF_WINSOCK_MINOR_VERSION),
                                 &oWSADATA);

    if (oWSAStartup != 0)
    {
        std::cout << "L'initialisation du point de terminaison a echoue."
                  << "|errorCode=" << oWSAStartup
                  << "|errorMsg=" << getLastErrorMsg(oWSAStartup)
                  << "|WINSOCK_MAJOR_VERSION=" << DEF_WINSOCK_MAJOR_VERSION
                  << "|WINSOCK_MINOR_VERSION=" << DEF_WINSOCK_MINOR_VERSION
                  << std::endl;
        return;
    }

    cServerInit oServerRun;
    oServerRun.run();
}
...

Gestion de l'initialisation du point de terminaison


// cServerInit.h (Editer l'initialisation du point de terminaison)
...
#pragma once

#include <string>

class cServerInit
{
    friend class cServer;

private:
    explicit cServerInit();
    ~cServerInit();
    void run();
};
...

// cServerInit.cpp (Editer l'initialisation du point de terminaison)
...
#include "cServerInit.h"
#include "cServerSocket.h"
#include "cErrorMsg.h"
#include <winsock2.h>
#include <iostream>

cServerInit::cServerInit()
{
}

cServerInit::~cServerInit()
{
    WSACleanup();
}

void cServerInit::run()
{
    SOCKET oSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    if (oSocket == INVALID_SOCKET)
    {
        std::cout << "La creation du point de terminaison a echoue."
                  << "|errorCode=" << GetLastError()
                  << "|errorMsg=" << getLastErrorMsg(GetLastError())
                  << std::endl;
        return;
    }

    cServerSocket oServerSocket(oSocket);
    oServerSocket.run();
}
...

Gestion de la création du point de terminaison


// cServerSocket.h (Editer la création du point de terminaison)
...
#pragma once

#include <string>
#include <winsock2.h>

class cServerSocket
{
    friend class cServerInit;

private:
    explicit cServerSocket(SOCKET _socket);
    ~cServerSocket();
    void run();

private:
    static void onAccept(SOCKET _client);

private:
    SOCKET m_socket;
};
...

// cServerSocket.cpp (Editer la création du point de terminaison)
...
#include "cServerSocket.h"
#include "cServerClient.h"
#include "cErrorMsg.h"
#include <future>
#include <iostream>

// winsock2
static const int DEF_WINSOCK_SERVER_PORT = 5555;
static const int DEF_WINSOCK_SERVER_BACKLOG = 10;

cServerSocket::cServerSocket(SOCKET _socket)
    : m_socket(_socket)
{
}

cServerSocket::~cServerSocket()
{
    closesocket(m_socket);
}

void cServerSocket::run()
{
    SOCKADDR_IN oServerAddr;
    oServerAddr.sin_addr.s_addr = INADDR_ANY;
    oServerAddr.sin_family = AF_INET;
    oServerAddr.sin_port = htons(DEF_WINSOCK_SERVER_PORT);

    if ((bind(m_socket, reinterpret_cast<SOCKADDR *>(&oServerAddr),
              sizeof(oServerAddr))) == SOCKET_ERROR)
    {
        std::cout << "La liaison de l'adresse du point de terminaison a echoue."
                  << "|errorCode=" << GetLastError()
                  << "|errorMsg=" << getLastErrorMsg(GetLastError())
                  << std::endl;
        return;
    }

    if ((listen(m_socket, DEF_WINSOCK_SERVER_BACKLOG)) == SOCKET_ERROR)
    {
        std::cout << "L'initialisation du nombre de connexions au point de terminaison a echoue."
                  << "|errorCode=" << GetLastError()
                  << "|errorMsg=" << getLastErrorMsg(GetLastError())
                  << std::endl;
        return;
    }

    std::cout << "Le serveur a demarre..."
              << "|port=" << DEF_WINSOCK_SERVER_PORT
              << std::endl;

    while (true)
    {
        SOCKADDR_IN oAddress;
        int oSize = sizeof(oAddress);
        SOCKET oClient = accept(m_socket, reinterpret_cast<SOCKADDR *>(&oAddress), &oSize);

        if (oClient == INVALID_SOCKET)
        {
            std::cout << "La connexion d'un client au point de terminaison a echoue."
                      << "|errorCode=" << GetLastError()
                      << "|errorMsg=" << getLastErrorMsg(GetLastError())
                      << std::endl;
            continue;
        }

        auto oFuture = std::async(std::launch::async, onAccept, oClient);
    }
}

void cServerSocket::onAccept(SOCKET _client)
{
    cServerClient oServerClient(_client);
    oServerClient.run();
}
...

Gestion de la communication avec un client


// cServerClient.h (Editer la communication avec un client)
...
#pragma once

#include <string>
#include <winsock2.h>

class cServerClient
{
    friend class cServerSocket;

private:
    explicit cServerClient(SOCKET _socket);
    ~cServerClient();
    void run();

private:
    bool recvData(std::string &_request);
    bool sendData(const std::string &_response);

private:
    SOCKET m_socket;
};
...

// cServerClient.cpp (Editer la communication avec un client)
...
#include "cServerClient.h"
#include "cServerApp.h"
#include "cString.h"
#include "cErrorMsg.h"
#include <iostream>

// winsock2
static const int DEF_WINSOCK_BUFFER_SIZE = 1024;
static const int DEF_WINSOCK_BUFFER_MAX = 1 * 1024 * 1024; // 1 Mo

// limit
static const int DEF_DATA_LIMIT_SIZE = 100;

cServerClient::cServerClient(SOCKET _socket)
    : m_socket(_socket)
{
}

cServerClient::~cServerClient()
{
    closesocket(m_socket);
}

void cServerClient::run()
{
    std::string oRequest;
    std::string oResponse;

    if (recvData(oRequest))
    {
        cServerApp oApp;
        oApp.run(oRequest, oResponse);
    }
    else
    {
        oResponse = "Un probleme a ete rencontre.";
    }

    sendData(oResponse);

    std::cout << "[Request ]:" << cString(oRequest).escape(DEF_DATA_LIMIT_SIZE) << std::endl;
    std::cout << "[Response]:" << cString(oResponse).escape(DEF_DATA_LIMIT_SIZE) << std::endl;
}

bool cServerClient::recvData(std::string &_request)
{
    std::string oRequest;
    char oBuffer[DEF_WINSOCK_BUFFER_SIZE];
    while (true)
    {
        int oBytes = recv(m_socket, oBuffer, DEF_WINSOCK_BUFFER_SIZE - 1, 0);
        if (oBytes == SOCKET_ERROR)
        {
            std::cout << "La reception des donnees du point de terminaison a echoue."
                      << "|errorCode=" << GetLastError()
                      << "|errorMsg=" << getLastErrorMsg(GetLastError())
                      << std::endl;
            return false;
        }

        oRequest.append(oBuffer, oBytes);

        if (oRequest.size() >= DEF_WINSOCK_BUFFER_MAX)
        {
            std::cout << "Le nombre maximal de donnees sur le point de terminaison a ete atteint."
                      << "|DEF_WINSOCK_BUFFER_MAX=" << DEF_WINSOCK_BUFFER_MAX
                      << std::endl;
            return false;
        }

        u_long oBytesIO;
        int isOK = ioctlsocket(m_socket, FIONREAD, &oBytesIO);

        if (isOK == SOCKET_ERROR)
        {
            std::cout << "La lecture du nombre de donnees restantes sur le point de terminaison a echoue."
                      << "|errorCode=" << GetLastError()
                      << "|errorMsg=" << getLastErrorMsg(GetLastError())
                      << std::endl;
            return false;
        }

        if (oBytesIO == 0)
        {
            break;
        }
    }
    _request = oRequest;
    return true;
}

bool cServerClient::sendData(const std::string &_response)
{
    int oBytes = send(m_socket, _response.c_str(), (int)_response.length(), 0);
    if (oBytes == SOCKET_ERROR)
    {
        std::cout << "La reception des donnees du point de terminaison a echoue."
                  << "|errorCode=" << GetLastError()
                  << "|errorMsg=" << getLastErrorMsg(GetLastError())
                  << std::endl;
        return false;
    }
    return true;
}
...

Gestion du serveur applicatif


// cServerApp.h (Editer le serveur applicatif)
...
#pragma once

#include <string>

class cServerApp
{
    friend class cServerClient;

private:
    explicit cServerApp();
    ~cServerApp();
    void run(const std::string &_request, std::string &_response);
};
...

// cServerApp.cpp (Editer le serveur applicatif)
...
#include "cServerApp.h"
#include "cHTTP.h"

cServerApp::cServerApp()
{
}

cServerApp::~cServerApp()
{
}

void cServerApp::run(const std::string &_request, std::string &_response)
{
    cHTTP oHTTP;
    _response = oHTTP.getResponse("<h1>Bonjour tout le monde.</h1>");
}
...

Gestion de la réponse HTTP


// cHTTP.h (Editer la réponse HTTP)
...
#pragma once

#include <string>

class cHTTP
{
private:
    struct sResponse
    {
        std::string version;
        int status;
        std::string reason;
        std::string connection;
        std::string contentType;
        int contentLength;
        std::string contentText;
    };

public:
    cHTTP();
    ~cHTTP();
    std::string getResponse(const std::string &_content) const;

private:
    sResponse initResponse(const std::string &_content) const;
};
...

// cHTTP.cpp (Editer la réponse HTTP)
...
#include "cHTTP.h"
#include <format>
#include <sstream>

static const std::string DEF_HTTP_VERSION_1_1 = "HTTP/1.1";
static const int DEF_HTTP_STATUS_200 = 200;
static const std::string DEF_HTTP_REASON_OK = "OK";
static const std::string DEF_HTTP_CONNECTION_CLOSE = "Close";
static const std::string DEF_HTTP_CONTENT_TYPE_HTML = "text/html; charset=UTF-8";

cHTTP::cHTTP()
{
}

cHTTP::~cHTTP()
{
}

std::string cHTTP::getResponse(const std::string &_content) const
{

    sResponse oResponse = initResponse(_content);
    std::stringstream oResponseText;

    oResponseText << std::format("{} {} {}\r\n", oResponse.version, oResponse.status, oResponse.reason);
    oResponseText << std::format("Connection: {}\r\n", oResponse.connection);
    oResponseText << std::format("Content-Type: {}\r\n", oResponse.contentType);
    oResponseText << std::format("Content-Length: {}\r\n", oResponse.contentLength);
    oResponseText << "\r\n";
    oResponseText << oResponse.contentText;
    oResponseText << "\r\n";

    return oResponseText.str();
}

cHTTP::sResponse cHTTP::initResponse(const std::string &_content) const
{
    sResponse oResponse;
    oResponse.version = DEF_HTTP_VERSION_1_1;
    oResponse.status = DEF_HTTP_STATUS_200;
    oResponse.reason = DEF_HTTP_REASON_OK;
    oResponse.connection = DEF_HTTP_CONNECTION_CLOSE;
    oResponse.contentType = DEF_HTTP_CONTENT_TYPE_HTML;
    oResponse.contentLength = (int)_content.size();
    oResponse.contentText = _content;
    return oResponse;
}
...

Gestion des messages d'erreurs


// cErrorMsg.h (Editer la lecture des messages d'erreurs)
...
#pragma once

#include <string>

std::string getLastErrorMsg(int _error);
...

// cErrorMsg.cpp (Editer la lecture des messages d'erreurs)
...
#include "cErrorMsg.h"
#include <Windows.h>

static const int DEF_WINSOCK_ERROR_MSG_LENGTH = 512;

std::string getLastErrorMsg(int _error)
{
    char oErrorMsg[DEF_WINSOCK_ERROR_MSG_LENGTH] = {0};
    int oLength = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
                                NULL,
                                _error,
                                MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                                oErrorMsg,
                                DEF_WINSOCK_ERROR_MSG_LENGTH - 1,
                                NULL);
    if (oLength > 0)
    {
        oErrorMsg[oLength - 1] = 0;
    }
    std::string oMessage = oErrorMsg;
    return oMessage;
}
...

Gestion du fichier CMake


// CMakeLists.txt (Editer le fichier CMake)
...
cmake_minimum_required(VERSION 3.10.0)
project(rdvcpp VERSION 0.1.0 LANGUAGES C CXX)

set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_STANDARD 20)

add_executable(${PROJECT_NAME}
    main.cpp
    cServer.cpp
    cServerInit.cpp
    cServerSocket.cpp
    cServerClient.cpp
    cServerApp.cpp
    cHTTP.cpp
    cErrorMsg.cpp
    cString.cpp
)

target_link_libraries(${PROJECT_NAME}
    ws2_32
)
...

Exécution du projet


// Terminal (Exécuter le projet)
...
rdvcpp.exe
...

// Application (Opérations sur le démarrage du serveur)
// Application (Opérations sur la requête HTTP)
// Application (Opérations sur la réponse HTTP)

image.png

// Application (Opérations sur le navigateur web)
image.png


Traiter une requête GET au sein d'un serveur HTTP en C++ sous Windows


C++ est un langage de programmation orientée objet prenant en charge la création de serveur HTTP. Un serveur HTTP est un serveur basé sur le protocole TCP/IP et chargé de traiter des requêtes HTTP reçues à partir d'un client HTTP. Un client HTTP est une application capable d'émettre des requêtes HTTP. Une requête HTTP commence par le nom de la méthode HTTP. La méthode GET permet de demander la lecture d'une ressource présente sur le serveur HTTP. La requête GET contient l'URI de la ressource demandée. L'URI est le chemin de la ressource demandée sur le serveur HTTP. Le serveur HTTP renvoie au client une réponse HTTP. Une réponse HTTP commence par la version HTTP. Dans ce tutoriel, nous utiliserons le navigateur web comme client HTTP pour émettre une requête HTTP. Nous utiliserons le module (Winsock2), pour implémenter un serveur HTTP capable de renvoyer une réponse HTTP imprimable dans le navigateur web. Nous traiterons la requête GET afin de récupérer l'URI de la ressource, déterminer le mime type de la ressource, charger la ressource et renvoyer la ressource au client HTTP. Si la ressource existe ou n'existe pas, nous définirons un code d'état et un message sur la raison correspondants à la situation afin de préparer la réponse HTTP à renvoyer au client avec le bon code d'état. Pour des raisons de sécurité, nous limiterons la taille des données échangées entre le client vers le serveur à 1 Mo. Pour des raisons d'agilité, nous utiliserons les principes de développement SOLID, RAII, OBJET VALIDE.


Gestion du programme principal


// main.cpp (Editer le programme principal)
...
#include "cServer.h"

int main(int _argc, char **_argv)
{
    cServer oServer;
    oServer.run();
    return 0;
}
...

Gestion du serveur applicatif


// cServerApp.h (Gérer le serveur applicatif)
...
#pragma once

#include <string>

class cServerApp
{
public:
    explicit cServerApp();
    ~cServerApp();
    void run(const std::string &_request, std::string &_response) const;
};
...

// cServerApp.cpp (Gérer le serveur applicatif)
...
#include "cServerApp.h"
#include "cHTTP.h"
#include "cHttpApp.h"

// http
static const std::string DEF_HTTP_CONTENT_TYPE_HTML = "text/html; charset=UTF-8";

cServerApp::cServerApp()
{
}

cServerApp::~cServerApp()
{
}

void cServerApp::run(const std::string &_request, std::string &_response) const
{
    cHTTP oHTTP;
    cHTTP::sRequest oRequestHTTP;

    if (oHTTP.analyzeRequestHTTP(oRequestHTTP, _request))
    {
        cHttpApp oApp;
        std::string oResponse;
        cHTTP::STATUS_CODE oStatusCode = HTTP_STATUS_CODE::OK_200;
        std::string oContentType = DEF_HTTP_CONTENT_TYPE_HTML;

        oApp.run(oRequestHTTP, oResponse, oContentType, oStatusCode);
        _response = oHTTP.getResponse(oResponse, oContentType, oStatusCode);
    }
    else
    {
        _response = "Un probleme a ete rencontre.";
    }
}
...

Gestion de la requête et de la réponse HTTP


// cHTTP.h (Gérer la requête et la réponse HTTP)
...
#pragma once

#include "cHttpStatusCode.h"
#include <string>

class cHTTP
{
public:
    struct sRequest
    {
        std::string method;
        std::string uri;
        std::string version;
    };

    struct sResponse
    {
        std::string version;
        int status;
        std::string reason;
        std::string connection;
        std::string contentType;
        int contentLength;
        std::string contentText;
    };

    using STATUS_CODE = HTTP_STATUS_CODE::eValues;

public:
    cHTTP();
    ~cHTTP();
    bool analyzeRequestHTTP(sRequest &_requestHTTP, const std::string &_request) const;
    std::string getResponse(const std::string &_contentText, const std::string &_contentType, const STATUS_CODE &_statusCode) const;
    bool isGET(const sRequest &_requestHTTP) const;
    bool isPOST(const sRequest &_requestHTTP) const;

private:
    bool isMethodAccepted(const std::string &_methodHTTP) const;
    bool isVersionAccepted(const std::string &_versionHTTP) const;
    sResponse initResponse(const std::string &_contentText, const std::string &_contentType, const STATUS_CODE &_statusCode) const;
};
...

// cHTTP.cpp (Gérer la requête et la réponse HTTP)
...
#include "cHTTP.h"
#include "cString.h"
#include <format>
#include <sstream>
#include <iostream>

// http
static const std::string DEF_HTTP_VERSION_1_0 = "HTTP/1.0";
static const std::string DEF_HTTP_VERSION_1_1 = "HTTP/1.1";
static const std::string DEF_HTTP_VERSION_2_0 = "HTTP/2.0";
//
static const std::string DEF_HTTP_CONNECTION_CLOSE = "Close";
//
static const std::string DEF_HTTP_METHOD_GET = "GET";
static const std::string DEF_HTTP_METHOD_POST = "POST";

// request
static const int DEF_REQUEST_LIMIT_SIZE = 100;
static const int DEF_REQUEST_HEADER_SIZE = 256;
static const std::string DEF_REQUEST_SEP_SPACE = " ";
static const std::string DEF_REQUEST_SEP_CRLF = "\r\n";

cHTTP::cHTTP()
{
}

cHTTP::~cHTTP()
{
}

bool cHTTP::analyzeRequestHTTP(sRequest &_requestHTTP, const std::string &_request) const
{
    const std::string oRequestHeader = _request.substr(0, DEF_REQUEST_HEADER_SIZE);

    // method
    size_t oMethodStart = 0;
    size_t oMethodEnd = oRequestHeader.find(DEF_REQUEST_SEP_SPACE, oMethodStart);
    if (oMethodEnd == std::string::npos)
    {
        std::cout << "[Error]:La methode de la requete HTTP n'est pas determinee."
                  << "|request=" << cString(oRequestHeader).escape(DEF_REQUEST_LIMIT_SIZE)
                  << std::endl;
        return false;
    }
    std::string oMethod = oRequestHeader.substr(oMethodStart, oMethodEnd - oMethodStart);
    if (!isMethodAccepted(oMethod))
    {
        std::cout << "[Error]:La methode de la requete HTTP n'est pas supportee."
                  << "|request=" << cString(oRequestHeader).escape(DEF_REQUEST_LIMIT_SIZE)
                  << std::endl;
        return false;
    }

    // uri
    size_t oUriStart = oMethodEnd + DEF_REQUEST_SEP_SPACE.size();
    size_t oUriEnd = oRequestHeader.find(DEF_REQUEST_SEP_SPACE, oUriStart);
    if (oUriEnd == std::string::npos)
    {
        std::cout << "[Error]:L'uri de la requete HTTP n'est pas determinee."
                  << "|request=" << cString(oRequestHeader).escape(DEF_REQUEST_LIMIT_SIZE)
                  << std::endl;
        return false;
    }
    std::string oUri = oRequestHeader.substr(oUriStart, oUriEnd - oUriStart);

    // version
    size_t oVersionStart = oUriEnd + DEF_REQUEST_SEP_SPACE.size();
    size_t oVersionEnd = oRequestHeader.find(DEF_REQUEST_SEP_CRLF, oVersionStart);
    if (oVersionEnd == std::string::npos)
    {
        std::cout << "[Error]:La version de la requete HTTP n'est pas determinee."
                  << "|request=" << cString(oRequestHeader).escape(DEF_REQUEST_LIMIT_SIZE)
                  << std::endl;
        return false;
    }
    std::string oVersion = oRequestHeader.substr(oVersionStart, oVersionEnd - oVersionStart);
    if (!isVersionAccepted(oVersion))
    {
        std::cout << "[Error]:La version de la requete HTTP n'est pas supportee."
                  << "|request=" << cString(oRequestHeader).escape(DEF_REQUEST_LIMIT_SIZE)
                  << std::endl;
        return false;
    }

    _requestHTTP.method = oMethod;
    _requestHTTP.uri = oUri;
    _requestHTTP.version = oVersion;
    return true;
}

std::string cHTTP::getResponse(const std::string &_contentText, const std::string &_contentType, const STATUS_CODE &_statusCode) const
{

    sResponse oResponse = initResponse(_contentText, _contentType, _statusCode);
    std::stringstream oResponseText;

    oResponseText << std::format("{} {} {}\r\n", oResponse.version, oResponse.status, oResponse.reason);
    oResponseText << std::format("Connection: {}\r\n", oResponse.connection);
    oResponseText << std::format("Content-Type: {}\r\n", oResponse.contentType);
    oResponseText << std::format("Content-Length: {}\r\n", oResponse.contentLength);
    oResponseText << "\r\n";
    oResponseText << oResponse.contentText;
    oResponseText << "\r\n";

    return oResponseText.str();
}

bool cHTTP::isGET(const sRequest &_requestHTTP) const
{
    return (_requestHTTP.method == DEF_HTTP_METHOD_GET);
}

bool cHTTP::isPOST(const sRequest &_requestHTTP) const
{
    return (_requestHTTP.method == DEF_HTTP_METHOD_POST);
}

bool cHTTP::isMethodAccepted(const std::string &_methodHTTP) const
{
    if ((_methodHTTP == DEF_HTTP_METHOD_GET) ||
        (_methodHTTP == DEF_HTTP_METHOD_POST))
    {
        return true;
    }
    return false;
}

bool cHTTP::isVersionAccepted(const std::string &_versionHTTP) const
{
    if ((_versionHTTP == DEF_HTTP_VERSION_1_0) ||
        (_versionHTTP == DEF_HTTP_VERSION_1_1) ||
        (_versionHTTP == DEF_HTTP_VERSION_2_0))
    {
        return true;
    }
    return false;
}

cHTTP::sResponse cHTTP::initResponse(const std::string &_contentText, const std::string &_contentType, const STATUS_CODE &_statusCode) const
{
    sResponse oResponse;
    oResponse.version = DEF_HTTP_VERSION_1_1;
    oResponse.status = _statusCode;
    oResponse.reason = HTTP_STATUS_REASON(_statusCode);
    oResponse.connection = DEF_HTTP_CONNECTION_CLOSE;
    oResponse.contentType = _contentType;
    oResponse.contentLength = (int)_contentText.size();
    oResponse.contentText = _contentText;
    return oResponse;
}
...

Gestion de l'application HTTP


// cHttpApp.h (Gérer l'application HTTP)
...
#pragma once

#include "cHTTP.h"
#include <string>

class cHttpApp
{
public:
    using sRequest = cHTTP::sRequest;
    using STATUS_CODE = cHTTP::STATUS_CODE;

public:
    explicit cHttpApp();
    ~cHttpApp();
    void run(const sRequest &_requestHTTP, std::string &_responseText, std::string &_contentType, STATUS_CODE &_statusCode) const;
};
...

// cHttpApp.cpp (Gérer l'application HTTP)
...
#include "cHttpApp.h"
#include "cHttpGet.h"

cHttpApp::cHttpApp()
{
}

cHttpApp::~cHttpApp()
{
}

void cHttpApp::run(const sRequest &_requestHTTP, std::string &_responseText, std::string &_contentType, STATUS_CODE &_statusCode) const
{
    cHTTP oHTTP;
    if (oHTTP.isGET(_requestHTTP))
    {
        cHttpGet oGet;
        oGet.run(_requestHTTP, _responseText, _contentType, _statusCode);
    }
    else if (oHTTP.isPOST(_requestHTTP))
    {
        _responseText = "<h1>Bonjour tout le monde POST</h1>";
    }
    else
    {
        _responseText = "Un problème a été rencontré.";
    }
}
...

Gestion de la requête HTTP GET


// cHttpGet.h (Gérer la requête HTTP GET)
...
#pragma once

#include "cHTTP.h"
#include <string>

class cHttpGet
{
public:
    using sRequest = cHTTP::sRequest;
    using STATUS_CODE = cHTTP::STATUS_CODE;

public:
    explicit cHttpGet();
    ~cHttpGet();
    void run(const sRequest &_requestHTTP, std::string &_responseText, std::string &_contentType, STATUS_CODE &_statusCode) const;
};
...

// cHttpGet.cpp (Gérer la requête HTTP GET)
...
#include "cHttpGet.h"
#include "cFile.h"
#include "cMimeType.h"
#include <iostream>

static const std::string DEF_SERVER_HTTP_ROOT = "../../webroot";

cHttpGet::cHttpGet()
{
}

cHttpGet::~cHttpGet()
{
}

void cHttpGet::run(const sRequest &_requestHTTP, std::string &_responseText, std::string &_contentType, STATUS_CODE &_statusCode) const
{
    std::string oFilename = DEF_SERVER_HTTP_ROOT + _requestHTTP.uri;
    cFile oFile(oFilename);
    if (!oFile.exitsFile())
    {
        _statusCode = HTTP_STATUS_CODE::NotFound_404;
        std::cout << "[Error]:La ressource n'a pas ete trouvee."
                  << "|filename=" << oFilename
                  << std::endl;
        return;
    }
    std::string oExtension = oFile.getExtension();
    _contentType = MIME_TYPE(oExtension);
    _responseText = oFile.readBin();
}
...

Gestion des mimes types


// cMimeType.h (Gérer les mimes types)
...
#pragma once

#include <string>

std::string MIME_TYPE(const std::string &_extension);
...

// cMimeType.cpp (Gérer  les mimes types)
...
#include "cMimeType.h"
#include <map>
#include <iostream>

static const std::map<std::string, std::string> gMimeType = {
    ...
    {"3gpp", "audio/3gpp"},
    {"jpm", "video/jpm"},
    {"mp3", "audio/mp3"},
    {"rtf", "text/rtf"},
    {"wav", "audio/wave"},
    {"xml", "text/xml"},
    ...
};

std::string MIME_TYPE(const std::string &_extension)
{
    std::string oExtention(_extension);
    if (oExtention[0] == '.')
    {
        oExtention.erase(0, 1);
    }
    std::map<std::string, std::string>::const_iterator it;
    for (it = gMimeType.begin(); it != gMimeType.end(); ++it)
    {
        if (it->first == oExtention)
        {
            return it->second;
        }
    }
    std::cout << "[Error]:Le mimetype du fichier est inconnu."
              << "|extension=" << _extension
              << std::endl;
    return "text/html";
}
...

Gestion des codes d'état de la requête HTTP


// cHttpStatusCode.h (Gérer les codes d'état de la requête HTTP)
...
#pragma once

#include <string>

struct HTTP_STATUS_CODE
{
    enum eValues
    {
        ...
        // Information responses
        Continue_100 = 100,
        SwitchingProtocol_101 = 101,
        Processing_102 = 102,
        EarlyHints_103 = 103,
        ...
    };
};

std::string HTTP_STATUS_REASON(HTTP_STATUS_CODE::eValues _statusCode);
...

// cHttpStatusCode.cpp (Gérer les codes d'état de la requête HTTP)
...
#include "cHttpStatusCode.h"
#include <iostream>

std::string HTTP_STATUS_REASON(HTTP_STATUS_CODE::eValues _statusCode)
{
    switch (_statusCode)
    {
    ...
    case HTTP_STATUS_CODE::Continue_100:
        return "Continue";
    case HTTP_STATUS_CODE::SwitchingProtocol_101:
        return "Switching Protocol";
    case HTTP_STATUS_CODE::Processing_102:
        return "Processing";
    case HTTP_STATUS_CODE::EarlyHints_103:
        return "Early Hints";
    ...

    default:
    case HTTP_STATUS_CODE::InternalServerError_500:
    {
        std::cout << "[Error]:Le status de la requête HTTP est inconnu."
                  << "|statusCode=" << _statusCode
                  << std::endl;
        return "Internal Server Error";
    }
    }
}
...

Gestion du fichier CMake


// CMakeLists.txt (Gérer le fichier CMake)
...
cmake_minimum_required(VERSION 3.10.0)
project(rdvcpp VERSION 0.1.0 LANGUAGES C CXX)

set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_STANDARD 20)

add_executable(${PROJECT_NAME}
    main.cpp
    cServer.cpp
    cServerInit.cpp
    cServerSocket.cpp
    cServerClient.cpp
    cServerApp.cpp
    cHTTP.cpp
    cHttpApp.cpp
    cHttpGet.cpp
    cHttpStatusCode.cpp
    cMimeType.cpp
    cErrorMsg.cpp
    cString.cpp
    cFile.cpp
)

target_link_libraries(${PROJECT_NAME}
    ws2_32
)
...

Exécution du projet


// Terminal (Exécuter le projet)
...
rdvcpp.exe
...

// Navigateur WEB (Requête HTTP GET sur des ressources existantes)
// Navigateur WEB (Requête HTTP GET sur la ressource [index.html])

image.png

// Serveur HTTP (Opérations sur des ressources existantes)
// Serveur HTTP (Opérations sur la ressource [/index.html])
// Serveur HTTP (Opérations sur la ressource [/data/img/logo.png])
// Serveur HTTP (Opérations sur la ressource [/css/styles.css])

image.png

// Serveur HTTP (Opérations sur des ressources existantes)
// Serveur HTTP (Contenu de la ressource [/index.html])

image.png

// Serveur HTTP (Opérations sur des ressources existantes)
// Serveur HTTP (Contenu de la ressource [/data/img/logo.png])

image.png

// Serveur HTTP (Opérations sur des ressources existantes)
// Serveur HTTP (Contenu de la ressource [/css/styles.css])

image.png

// Application (Opérations sur le navigateur web)
// Application (Opérations sur une ressource existante)
// Application (Opérations sur la ressource [/index.htmlo])

image.png

// Application (Opérations sur le serveur HTTP)
// Application (Opérations sur une ressource inexistante)
// Application (Opérations sur la ressource [/index.htmlo])

image.png

Notes sur les formats de données


// Notes (Format d'une requête HTTP GET)
// Notes (Format d'une requête HTTP GET [/index.html])

...
GET /index.html HTTP/1.1
Host: localhost:5555
Connection: keep-alive
sec-ch-ua: "Google Chrome";v="135", "Not-A.Brand";v="8", "Chromium";v="135"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br, zstd
Accept-Language: fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7,zh-CN;q=0.6,zh;q=0.5
Cookie: PHPSESSID=g5eglo9gq2jm7c0o3nu193ig13
...

// Notes (Format d'une réponse HTTP)
// Notes (Format d'une réponse HTTP 202)
// Notes (Format d'une réponse HTTP 202 [/index.html])

...
HTTP/1.1 200 OK
Connection: Close
Content-Type: text/html
Content-Length: 487

<!DOCTYPE html>
<html lang="fr">
  <head>
    <title>ReadyCPP | Serveur HTTP</title>
    <meta charset="UTF-8" />
    <link rel="shortcut icon" type="image/png" href="/data/img/logo.png" />
    <meta
      name="viewport"
      content="width=device-width, maximum-scale=1.0, minimum-scale=1.0,
      initial-scale=1.0, user-scalable=no"
    />
    <link rel="stylesheet" href="/css/styles.css" />
  </head>
  <body>
    <h1>Bonjour tout le monde</h1>
  </body>
</html>
...

// Notes (Format d'une réponse HTTP)
// Notes (Format d'une réponse HTTP 202)
// Notes (Format d'une réponse HTTP 202 [/data/img/logo.png])

...
HTTP/1.1 200 OK
Connection: Close
Content-Type: image/png
Content-Length: 24318

xPNG
xxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxx
...

// Notes (Format d'une réponse HTTP)
// Notes (Format d'une réponse HTTP 202)
// Notes (Format d'une réponse HTTP 202 [/css/styles.css])

...
HTTP/1.1 200 OK
Connection: Close
Content-Type: text/css
Content-Length: 50

body
{
    background-color: darkslategray;
}
...

// Notes (Format d'une réponse HTTP)
// Notes (Format d'une réponse HTTP 404)
// Notes (Format d'une réponse HTTP 404 [/index.htmlo])

...
HTTP/1.1 404 Not Found
Connection: Close
Content-Type: text/html; charset=UTF-8
Content-Length: 0
...