Introducció

En aquest tema, explorarem com integrar funcionalitats de xarxa en les aplicacions DirectX. La capacitat de comunicar-se a través de la xarxa és essencial per a molts jocs i aplicacions modernes, permetent el joc multijugador, la sincronització de dades en temps real i molt més.

Objectius

  • Comprendre els conceptes bàsics de la programació de xarxes.
  • Aprendre a configurar una connexió de xarxa en una aplicació DirectX.
  • Implementar la comunicació bàsica entre clients i servidors.
  • Gestionar la sincronització de dades en temps real.

Conceptes Bàsics de Xarxes

Protocols de Xarxa

Els protocols de xarxa defineixen com es comuniquen els dispositius a través de la xarxa. Els dos protocols més comuns són:

  • TCP (Transmission Control Protocol): Proporciona una connexió fiable i orientada a la connexió. És ideal per a aplicacions que requereixen la transmissió precisa de dades, com ara jocs de torns.
  • UDP (User Datagram Protocol): És un protocol sense connexió i no garanteix la fiabilitat de la transmissió. És més ràpid que TCP i s'utilitza sovint en jocs en temps real on la velocitat és més important que la precisió.

Adreces IP i Ports

  • Adreça IP: Identifica de manera única un dispositiu en una xarxa.
  • Port: Identifica una aplicació o servei específic en un dispositiu.

Configuració de la Connexió de Xarxa

Llibreries de Xarxa

Per implementar funcionalitats de xarxa en DirectX, podem utilitzar llibreries com Winsock (Windows Sockets API). Winsock proporciona una interfície per a la programació de xarxes en Windows.

Exemple de Configuració de Winsock

A continuació, es mostra un exemple bàsic de com inicialitzar Winsock i establir una connexió TCP.

#include <winsock2.h>
#include <ws2tcpip.h>
#include <iostream>

#pragma comment(lib, "Ws2_32.lib")

int main() {
    WSADATA wsaData;
    int iResult;

    // Inicialitzar Winsock
    iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (iResult != 0) {
        std::cerr << "WSAStartup failed: " << iResult << std::endl;
        return 1;
    }

    // Crear un socket
    SOCKET ConnectSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (ConnectSocket == INVALID_SOCKET) {
        std::cerr << "Error at socket(): " << WSAGetLastError() << std::endl;
        WSACleanup();
        return 1;
    }

    // Configurar l'adreça del servidor
    sockaddr_in clientService;
    clientService.sin_family = AF_INET;
    clientService.sin_addr.s_addr = inet_addr("127.0.0.1");
    clientService.sin_port = htons(27015);

    // Connectar al servidor
    iResult = connect(ConnectSocket, (SOCKADDR*)&clientService, sizeof(clientService));
    if (iResult == SOCKET_ERROR) {
        std::cerr << "Unable to connect to server: " << WSAGetLastError() << std::endl;
        closesocket(ConnectSocket);
        WSACleanup();
        return 1;
    }

    std::cout << "Successfully connected to server!" << std::endl;

    // Tancar el socket
    closesocket(ConnectSocket);
    WSACleanup();

    return 0;
}

Explicació del Codi

  1. Inicialització de Winsock: WSAStartup inicialitza l'ús de la llibreria Winsock.
  2. Creació del Socket: socket crea un nou socket per a la comunicació.
  3. Configuració de l'Adreça del Servidor: sockaddr_in defineix l'adreça del servidor al qual ens connectarem.
  4. Connexió al Servidor: connect intenta establir una connexió amb el servidor.
  5. Tancament del Socket: closesocket tanca el socket i WSACleanup allibera els recursos de Winsock.

Comunicació Bàsica entre Clients i Servidors

Enviar i Rebre Dades

Un cop establerta la connexió, podem enviar i rebre dades utilitzant les funcions send i recv.

// Enviar dades
const char* sendbuf = "Hello, Server!";
iResult = send(ConnectSocket, sendbuf, (int)strlen(sendbuf), 0);
if (iResult == SOCKET_ERROR) {
    std::cerr << "send failed: " << WSAGetLastError() << std::endl;
    closesocket(ConnectSocket);
    WSACleanup();
    return 1;
}

// Rebre dades
char recvbuf[512];
iResult = recv(ConnectSocket, recvbuf, 512, 0);
if (iResult > 0) {
    std::cout << "Bytes received: " << iResult << std::endl;
    std::cout << "Data: " << recvbuf << std::endl;
} else if (iResult == 0) {
    std::cout << "Connection closed" << std::endl;
} else {
    std::cerr << "recv failed: " << WSAGetLastError() << std::endl;
}

Explicació del Codi

  1. Enviar Dades: send envia dades al servidor.
  2. Rebre Dades: recv rep dades del servidor.

Exercici Pràctic

Objectiu

Implementar una aplicació client-servidor bàsica on el client envia un missatge al servidor i el servidor respon amb un missatge de confirmació.

Instruccions

  1. Servidor: Escriu un programa que escolti connexions entrants i respongui amb un missatge de confirmació.
  2. Client: Escriu un programa que es connecti al servidor, enviï un missatge i mostri la resposta del servidor.

Solució

Servidor

#include <winsock2.h>
#include <ws2tcpip.h>
#include <iostream>

#pragma comment(lib, "Ws2_32.lib")

int main() {
    WSADATA wsaData;
    int iResult;

    // Inicialitzar Winsock
    iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (iResult != 0) {
        std::cerr << "WSAStartup failed: " << iResult << std::endl;
        return 1;
    }

    // Crear un socket per escoltar
    SOCKET ListenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (ListenSocket == INVALID_SOCKET) {
        std::cerr << "Error at socket(): " << WSAGetLastError() << std::endl;
        WSACleanup();
        return 1;
    }

    // Configurar l'adreça del servidor
    sockaddr_in serverService;
    serverService.sin_family = AF_INET;
    serverService.sin_addr.s_addr = INADDR_ANY;
    serverService.sin_port = htons(27015);

    // Lligar el socket
    iResult = bind(ListenSocket, (SOCKADDR*)&serverService, sizeof(serverService));
    if (iResult == SOCKET_ERROR) {
        std::cerr << "bind failed: " << WSAGetLastError() << std::endl;
        closesocket(ListenSocket);
        WSACleanup();
        return 1;
    }

    // Escoltar connexions entrants
    iResult = listen(ListenSocket, SOMAXCONN);
    if (iResult == SOCKET_ERROR) {
        std::cerr << "listen failed: " << WSAGetLastError() << std::endl;
        closesocket(ListenSocket);
        WSACleanup();
        return 1;
    }

    std::cout << "Waiting for client to connect..." << std::endl;

    // Acceptar una connexió
    SOCKET ClientSocket = accept(ListenSocket, NULL, NULL);
    if (ClientSocket == INVALID_SOCKET) {
        std::cerr << "accept failed: " << WSAGetLastError() << std::endl;
        closesocket(ListenSocket);
        WSACleanup();
        return 1;
    }

    std::cout << "Client connected!" << std::endl;

    // Rebre dades del client
    char recvbuf[512];
    iResult = recv(ClientSocket, recvbuf, 512, 0);
    if (iResult > 0) {
        std::cout << "Bytes received: " << iResult << std::endl;
        std::cout << "Data: " << recvbuf << std::endl;

        // Enviar resposta al client
        const char* sendbuf = "Message received!";
        iResult = send(ClientSocket, sendbuf, (int)strlen(sendbuf), 0);
        if (iResult == SOCKET_ERROR) {
            std::cerr << "send failed: " << WSAGetLastError() << std::endl;
        }
    } else if (iResult == 0) {
        std::cout << "Connection closing..." << std::endl;
    } else {
        std::cerr << "recv failed: " << WSAGetLastError() << std::endl;
    }

    // Tancar el socket
    closesocket(ClientSocket);
    closesocket(ListenSocket);
    WSACleanup();

    return 0;
}

Client

#include <winsock2.h>
#include <ws2tcpip.h>
#include <iostream>

#pragma comment(lib, "Ws2_32.lib")

int main() {
    WSADATA wsaData;
    int iResult;

    // Inicialitzar Winsock
    iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (iResult != 0) {
        std::cerr << "WSAStartup failed: " << iResult << std::endl;
        return 1;
    }

    // Crear un socket
    SOCKET ConnectSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (ConnectSocket == INVALID_SOCKET) {
        std::cerr << "Error at socket(): " << WSAGetLastError() << std::endl;
        WSACleanup();
        return 1;
    }

    // Configurar l'adreça del servidor
    sockaddr_in clientService;
    clientService.sin_family = AF_INET;
    clientService.sin_addr.s_addr = inet_addr("127.0.0.1");
    clientService.sin_port = htons(27015);

    // Connectar al servidor
    iResult = connect(ConnectSocket, (SOCKADDR*)&clientService, sizeof(clientService));
    if (iResult == SOCKET_ERROR) {
        std::cerr << "Unable to connect to server: " << WSAGetLastError() << std::endl;
        closesocket(ConnectSocket);
        WSACleanup();
        return 1;
    }

    std::cout << "Successfully connected to server!" << std::endl;

    // Enviar dades
    const char* sendbuf = "Hello, Server!";
    iResult = send(ConnectSocket, sendbuf, (int)strlen(sendbuf), 0);
    if (iResult == SOCKET_ERROR) {
        std::cerr << "send failed: " << WSAGetLastError() << std::endl;
        closesocket(ConnectSocket);
        WSACleanup();
        return 1;
    }

    // Rebre dades
    char recvbuf[512];
    iResult = recv(ConnectSocket, recvbuf, 512, 0);
    if (iResult > 0) {
        std::cout << "Bytes received: " << iResult << std::endl;
        std::cout << "Data: " << recvbuf << std::endl;
    } else if (iResult == 0) {
        std::cout << "Connection closed" << std::endl;
    } else {
        std::cerr << "recv failed: " << WSAGetLastError() << std::endl;
    }

    // Tancar el socket
    closesocket(ConnectSocket);
    WSACleanup();

    return 0;
}

Conclusió

En aquest tema, hem après els conceptes bàsics de la programació de xarxes i com integrar funcionalitats de xarxa en una aplicació DirectX utilitzant Winsock. Hem vist com establir una connexió TCP, enviar i rebre dades, i hem implementat una aplicació client-servidor bàsica. Aquestes habilitats són fonamentals per desenvolupar jocs i aplicacions que requereixen comunicació en temps real a través de la xarxa.

© Copyright 2024. Tots els drets reservats