Kirjautuminen

Haku

Tehtävät

Keskustelu: Koodit: C++: UDP-verkkosysteemi

Sivun loppuun

Gaxx [16.02.2007 14:30:22]

#

Monen yrityksen ja erehdyksen jälkeen sain valmiiksi UDP-protocollaa käyttävän nettisysteemin. Olen pyrkinyt tekemään siitä mahdollisimman yksinkertaisen ja helppokäyttöisen, mutta silti monipuolisen. Systeemi huolehtii itse hävinneiden pakettien uudelleen lähetyksen ja käsittelee paketit siinä järjestyksessä, kuin ne lähetetäänkin. Luokka voi toimia isäntänä, käyttäjänä tai vaikka molempina yhtäaikaa.

Käyttö on yksinkertaista. Ensiksi on alustettava SDL_net ja tämän jälkeen kutsutaan luokan alustavaa Initialize-funktiota. Jos suurimman sallitun käyttäjämäärän jättää nollaksi, systeemi toimii pelkkänä käyttäjänä.

Alustamisen jälkeen voidaan yhdistää isäntään Connect-funktiolla ja yhteyden katkaisu tapahtuu Disconnect-funktiolla.

Viestien lähetys ja vastaanottaminen tapahtuvat SendData- ja RecvData-funktioilla. RecvData-funktio hoitaa lisäksi viestien uudelleenlähetyksen sekä serveripuolen käyttäjien yhteyspyyntöjen käsittelyn.

Luokan muisti vapautuu Release-funktiolla, joka suoritetaan myös, kun luokka tuhotaan.

Lisäksi luokassa on sisäänrakennettu mahdollisuus aikakatkaisulle.

Käyttäjien potkiminen käy Kick-funktiolla ja Reserved-funktio kertoo, onko kyseinen id varattu.

Jos löydätte luokasta virheitä, pyydän ilmoittamaan niistä alla kommenteissa tai sitten sähköpostilla.

Muokkaushistoria

28.6.2007 — Korjattu virhe pakettien uudelleenlähetyksessä.

29.12.2007 — Korjattu virhe luokan alustuksessa(kts. kommentit).

4.1.2008 — Testasin järjestelmän todellisen paineen alla ja löysin sieltä särön(pakettien uudelleen lähetyksestä), jonka korjasin. Lisäksi parantelin virheenkäsittelyä ja poistin vastuuta käyttäjän harteilta lisäilemällä jokusen tarkistuksen. Nyt järjestelmä toimii moitteettomasti myös äärimmäisen raskaassa käytössä. Päivitin myös esimerkit!

Lisäksi mainittakoon, että nykyään saman ip:n takaa voi liittyä monta käyttäjää, kunhan ne majailevat eri porttien takana(vrt. monta konetta saman ip:n takana).

9.9.2008 — Pientä viilailua tehty viimeisimmän projektin ohessa. Korjattu myös puhdas virhe serverin kick-komennon käsittelyssä käyttäjän päässä.

UDP.h

#ifndef UDP_H
#define UDP_H

#include <SDL/SDL_net.h>

const int HOST = 0;                                                             // Isännän indexi
const int NOTIMEOUT = 0;                                                        // Ei aikakatkaisua

class UDP {
    private:
    UDPsocket socket;                                                           // Socketti
    UDPpacket *packet;                                                          // Käsittelypaketti

    UDPpacket ***sbuffer;                                                       // Lähtevien pakettien puskuri
    UDPpacket ***rbuffer;                                                       // Saapuvien pakettien puskuri
    int *sbindex;                                                               // Lähtevän puskurin osoitin
    int *rbindex;                                                               // Saapuvan puskurin osoitin

    IPaddress *user;                                                            // Käyttäjien osoitteet

    Uint32 *time;                                                               // Viimeisimpien viestien saapumisajankohdat

    int maxusers;                                                               // Suurin mahdollinen käyttäjämäärä
    int maxpacketsize;                                                          // Suurin mahdollinen paketin koko
    int buffersize;                                                             // Pakettipuskurien koko
    Uint32 timeout;                                                             // Aikakatkaisu

    public:
    UDP();                                                                      // Constructor
    ~UDP();                                                                     // Destructor

    int Initialize(int musers, int mpsize, int bsize, Uint16 port);             // Alustaa luokan käyttökuntoon
    void Release();                                                             // Vapauttaa luokan

    int Connect(const char *ip, Uint16 port);                                   // Yhdistää isäntään
    int KeepGoing();                                                            // Kertoo serverille olemassaolostaan
    void Disconnect();                                                          // Katkaisee yhteyden isäntään

    int SendData(void *pdata, int lenght, int id);                              // Lähettää paketin
    int RecvData(void *pdata, int *id);                                         // Vastaanottaa paketin

    void Kick(int id);                                                          // Katkaisee yhteyden käyttäjään
    void SetTimeout(int second);                                                // Asettaa aikakatkaisun
    bool Reserved(int id);                                                      // Onko id varattu
};

#endif

UDP.cpp

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

// Viestityypit
const int UDP_MESSAGE    = 0;    // Normaali viesti(ensimmäistä kertaa)
const int UDP_REMESSAGE  = 1;    // Uudelleen lähetetty viesti
const int UDP_RESEND     = 2;    // "Pyydän lähettämään paketin # uudelleen"
const int UDP_CT         = 3;    // Yhteyskokeilu
const int UDP_CTA        = 4;    // Yhteyskokeilun vastaus
const int UDP_DISCONNECT = 5;    // Yhteyden katkaiseminen
const int UDP_KG         = 6;    // "Keep going, I am here!" - Olen linjoilla

UDP::UDP() {
    // Aikakatkaisu oletuksena pois päältä
    timeout = NOTIMEOUT;

    // Merkitään luokka alustamattomaksi
    user = NULL;
}

UDP::~UDP() {
    // Vapautetaan varattu muisti
    if(user)
        Release();
}

/* ------------------------------------------------------------ *\
Alustaa luokan toimintakelposeksi

parametrit:
musers: suurin sallittu käyttäjämäärä
mpsize: paketin maksimikoko
bsize: puskurin koko
port: käytettävä portti

palautusarvot:
0: Alustaminen onnistui
-1: Alustaminen epäonnistui
\* ------------------------------------------------------------ */
int UDP::Initialize(int musers, int mpsize, int bsize, Uint16 port) {
    // Jos luokka on alustettu
    if(user)
        Release();

    // Asetetaan asetukset
    maxusers = musers+1;
    maxpacketsize = mpsize;
    buffersize = bsize;

    // Koska paketin indexille on varattu vain kaksi tavua
    if(buffersize > 65536)
        buffersize = 65536;

    // Varataan tilaa käsittelypaketille
    packet = SDLNet_AllocPacket(maxpacketsize);

    // Varataan tilaa puskuritaulukolle
    sbindex = new int[maxusers];
    rbindex = new int[maxusers];
    sbuffer = new UDPpacket**[maxusers];
    rbuffer = new UDPpacket**[maxusers];
    for(int i = 0; i < maxusers; i++) {
        sbuffer[i] = NULL;
        rbuffer[i] = NULL;
    }

    // Alustetaan ajastimet
    time = new Uint32[maxusers];

    // Alustetaan osotteet
    user = new IPaddress[maxusers];
    for(int i = 0; i < maxusers; i++)
        user[i].host = 0;

    // Avataan socketti tietojen välittämistä varten
    socket = SDLNet_UDP_Open(port);
    if(!socket)
        return -1;

    return 0;
}

/* ------------------------------------------------------------ *\
Vapauttaa luokan varaaman muistin
\* ------------------------------------------------------------ */
void UDP::Release() {
    // Jos luokkaa ei ole alustettu
    if(!user)
        return;

    // Vapautetaan käsittelypaketti
    SDLNet_FreePacket(packet);
    packet = NULL;

    // Vapautetaan puskuritaulukot
    for(int i = 0; i < maxusers; i++) {
        if(sbuffer[i]) {
            for(int j = 0; j < buffersize; j++) {
                SDLNet_FreePacket(sbuffer[i][j]);
                SDLNet_FreePacket(rbuffer[i][j]);
            }
        }

        delete [] sbuffer[i];
        delete [] rbuffer[i];
    }

    delete [] sbuffer;
    delete [] rbuffer;
    sbuffer = NULL;
    rbuffer = NULL;

    // Vapautetaan puskuritaulukoiden "osoittimet"
    delete [] sbindex;
    delete [] rbindex;
    sbindex = NULL;
    rbindex = NULL;

    // Vapautetaan käyttäjätaulu
    delete [] user;
    user = NULL;

    // Suljetaan socketti
    SDLNet_UDP_Close(socket);
    socket = NULL;
}

/* ------------------------------------------------------------ *\
Yhdistää isäntään

parametrit:
ip: ip-osoite
port: portti

palautusarvot:
0: Yhdistäminen onnistui
-1: Isäntä ei vastaa
-2: Virheellinen osoite tai alustamaton luokka
\* ------------------------------------------------------------ */
int UDP::Connect(const char *ip, Uint16 port) {
    Uint32 timer;
    int result;

    // Jos luokkaa ei ole alustettu
    if(!user)
        return -2;

    // Suljetaan mahdollinen voimassa oleva yhteys
    if(sbuffer[HOST])
        Disconnect();

    // Määritetään isännän osoite
    if(SDLNet_ResolveHost(&user[HOST], ip, port))
        return -2;

    // Varataan muistia puskureille
    sbuffer[HOST] = new UDPpacket*[buffersize];
    rbuffer[HOST] = new UDPpacket*[buffersize];
    for(int i = 0; i < buffersize; i++) {
        sbuffer[HOST][i] = SDLNet_AllocPacket(maxpacketsize);
        rbuffer[HOST][i] = NULL;
    }

    // Nollataan puskurien osoittimet
    sbindex[HOST] = 0;
    rbindex[HOST] = 0;

    // Luodaan yhteyskokeilusanoma
    packet->data[0] = UDP_CT;
    packet->len = 1;

    // Nollataan socketin suuntaus
    SDLNet_UDP_Unbind(socket, 0);

    // Suunnataan socketti isäntään
    if(SDLNet_UDP_Bind(socket, 0, &user[HOST]))
        return -2;

    // Lähetetään sanoma
    if(!SDLNet_UDP_Send(socket, 0, packet))
        return -2;

    // Odotetaan vastausta(3s)
    timer = SDL_GetTicks();
    while(timer+3000 > SDL_GetTicks()) {
        // Vastaanotetaan paketti
        result = SDLNet_UDP_Recv(socket, packet);

        // Jos saatiin paketti
        if(result > 0)
            // Varmistetaan paketin osoite
            if(packet->address.host == user[HOST].host)
                // Varmistetaan, sisältö
                if(packet->data[0] == UDP_CTA)
                    return 0;

        // Jos tapahtui virhe
        if(result < 0)
            return -2;
    }

    // Tuhotaan isännän osote
    user[HOST].host = 0;

    // Vapautetaan varattu muisti
    delete [] sbuffer[HOST];
    delete [] rbuffer[HOST];
    sbuffer[HOST] = NULL;
    rbuffer[HOST] = NULL;

    return -1;
}

/* ------------------------------------------------------------ *\
Lähettää tiedon serverille olemassa olostaan

palautusarvot:
0: Lähetys onnistui
-1: Viestin lähettäminen epäonnistui
-2: Virheellinen osoite tai alustamaton luokka
\* ------------------------------------------------------------ */
int UDP::KeepGoing() {
    Uint16& packetid = (Uint16&)packet->data[1]; // Viittaus paketin indexiin

    // Jos luokkaa ei ole alustettu
    if(!user)
        return -2;

    // Kootaan paketti
    packet->data[0] = UDP_KG;
    packetid = sbindex[HOST];
    packet->len = 3;

    // Nollataan socketin suuntaus
    SDLNet_UDP_Unbind(socket, 0);

    // Suunnataan socketti isäntään
    if(SDLNet_UDP_Bind(socket, 0, &user[HOST]))
        return -2;

    // Lähetetään sanoma
    if(!SDLNet_UDP_Send(socket, 0, packet))
        return -1;

    return 0;
}

/* ------------------------------------------------------------ *\
Katkaisee yhteyden isäntään
\* ------------------------------------------------------------ */
void UDP::Disconnect() {
    // Jos luokkaa ei ole alustettu
    if(!user)
        return;

    // Jos isäntään ei ole yhteyttä
    if(!user[HOST].host)
        return;

    // Lähetetään ilmoitus yhteydenkatkaisusta
    packet->data[0] = UDP_DISCONNECT;
    packet->len = 1;
    SDLNet_UDP_Unbind(socket, 0);
    SDLNet_UDP_Bind(socket, 0, &user[HOST]);
    SDLNet_UDP_Send(socket, 0, packet);

    // Tuhotaan isännän osoite
    user[HOST].host = 0;

    // Vapautetaan varattu muisti
    for(int i = 0; i < buffersize; i++) {
        SDLNet_FreePacket(sbuffer[HOST][i]);
        if(rbuffer[HOST][i])
            SDLNet_FreePacket(rbuffer[HOST][i]);
    }

    delete [] sbuffer[HOST];
    delete [] rbuffer[HOST];
    sbuffer[HOST] = NULL;
    rbuffer[HOST] = NULL;
}

/* ------------------------------------------------------------ *\
Lähettää paketin vastaanottajalle

parametrit:
pdata: lähetettävä viesti
lenght: viestin pituus
id: käyttäjän id

palautusarvot:
0: Paketti lähetys onnistui
-1: Paketin lähetys epäonnistui
-2: Luokkaa ei ole alustettu
\* ------------------------------------------------------------ */
int UDP::SendData(void *pdata, int lenght, int id) {
    Uint8 *data = (Uint8*)pdata;
    Uint16& packetid = (Uint16&)packet->data[1]; // Viittaus paketin indexiin

    // Jos luokkaa ei ole alustettu
    if(!user)
        return -2;

    // Jos id on laiton
    if(id < 0 || id >= maxusers)
        return -1;

    // Jos id:llä ei ole käyttäjää, poistutaan
    if(!user[id].host)
        return -1;

    // Jos viestin pituus on suurempi, kuin sen suurin sallittu pituus
    if(lenght > maxpacketsize)
        return -1;

    // Luodaan lähetettävä paketti
    packet->data[0] = UDP_MESSAGE;
    packetid = sbindex[id];
    memcpy(&packet->data[3], data, lenght);
    packet->len = lenght+3;

    // Lisätään viesti puskuriin
    memcpy(sbuffer[id][sbindex[id]]->data, data, lenght);
    sbuffer[id][sbindex[id]]->len = lenght;
    sbindex[id]++;
    if(sbindex[id] >= buffersize)
        sbindex[id] = 0;

    // Nollataan socketin suuntaus
    SDLNet_UDP_Unbind(socket, 0);

    // Suunnataan socketti
    if(SDLNet_UDP_Bind(socket, 0, &user[id]))
        return -2;

    // Lähetetään paketti
    if(!SDLNet_UDP_Send(socket, 0, packet))
        return -2;

    return 0;
}

/* ------------------------------------------------------------ *\
Käsittelee vastaanotetut paketit: vastaanottaa viestit,
käsittelee yhteyspyynnöt, hoitaa viestien uudelleenlähetykset ja
yhteyksie aikakatkaisut sekä yhteyksien sulkemiset

parametrit:
pdata: osoite viestimuuttujaan
id: osoite käyttäjän id-muuttujaan

palautusarvot:
Palauttaa paketin pituuden
0: Ei pakettia
-1: Yhteys katkaistu
-2: Virheellinen osoite tai alustamaton luokka
\* ------------------------------------------------------------ */
int UDP::RecvData(void *pdata, int *id) {
    int index = -1;
    int lenght;
    int userid = -1;
    static int lasthand = 0;
    Uint8 *data = (Uint8*)pdata;
    Uint16& packetid = (Uint16&)packet->data[1]; // Viittaus paketin indexiin

    // Jos luokkaa ei ole alustettu
    if(!user)
        return -2;

    // Kun jokainen käyttäjä on käyty läpi
    if(lasthand >= maxusers)
        lasthand = 0;

    // Luetaan puskurit
    for(int i = lasthand; i < maxusers; i++) {
        lasthand++;
        // Jos käyttäjän puskurille on varattu muistia
        if(rbuffer[i]) {
            // Jos pakettipaikassa on jotain
            if(rbuffer[i][rbindex[i]]) {
                // Palautetaan tieto, pituus ja id
                memcpy(data, rbuffer[i][rbindex[i]]->data,
                rbuffer[i][rbindex[i]]->len);
                lenght = rbuffer[i][rbindex[i]]->len;
                if(id != NULL)
                    *id = i;

                // Vapautetaan puskurin paketti
                SDLNet_FreePacket(rbuffer[i][rbindex[i]]);
                rbuffer[i][rbindex[i]] = NULL;

                // Siirretään puskuriosoitin seuraavaan paikkaan
                rbindex[i]++;
                if(rbindex[i] >= buffersize)
                    rbindex[i] = 0;

                return lenght;
            }

            // Jos aikakatkaisu on käytössä
            if(timeout) {
                // Jos käyttäjästä ei ole kuulunut riittävän pitkään
                if(time[i]+timeout < SDL_GetTicks()) {
                    // Jos ollaan serveri
                    if(maxusers > 0) {
                        // palautetaan id
                        if(id != NULL)
                            *id = i;

                        // Poistetaan käyttäjälistalta
                        Kick(i);
                    } else {
                        // Katkaistaan yhteys
                        Disconnect();
                    }

                    return -1;
                }
            }
        }
    }

    // Vastaanotetaan paketti
    if(SDLNet_UDP_Recv(socket, packet) > 0) {
        // Jos viesti on virheellinen
        if(packet->data[0] != 3 && packetid > buffersize)
            return 0;

        // Selvitetään käyttäjän id
        for(int i = 0; i < maxusers; i++) {
            if(packet->address.host == user[i].host &&
            packet->address.port == user[i].port) {
                userid = i;
                break;
            }
        }

        // Jos käyttäjä ei ole listalla, eikä viesti ole yhteyspyyntö
        if(userid == -1 && packet->data[0] != 3)
            return 0;

        // Päivitetään viimeisimmän viestin saapumisajankohta
        time[userid] = SDL_GetTicks();

        // Käsitellään paketti sen tyypin mukaan
        switch(packet->data[0]) {
            case UDP_MESSAGE:
                // Jos paketti voidaan palauttaa saman tien
                if(packetid == rbindex[userid]) {
                    // Palautetaan tieto ja id
                    memcpy(data, &packet->data[3], packet->len-3);
                    if(id != NULL)
                        *id = userid;

                    // Siirretään puskurin osoitinta eteenpäin
                    rbindex[userid]++;
                    if(rbindex[userid] >= buffersize)
                        rbindex[userid] = 0;

                    // Palautetaan viestin pituus
                    return packet->len-3;
                }

                // Jos puskuri pääsee täyttymään
                if(packetid == rbindex[userid]-1 ||
                (packetid >= buffersize-1 && rbindex[userid] == 0)) {
                    // Jos ollaan serveri
                    if(maxusers > 0) {
                        // Pudotetaan käyttäjä linjalta
                        Kick(userid);
                    } else {
                        // Katkaistaan yhteys
                        Disconnect();
                        return -1;
                    }
                    break;
                }

                // Lisätään paketti puskuriin odottamaan käsittelyä
                rbuffer[userid][packetid] = SDLNet_AllocPacket(packet->len-3);
                memcpy(rbuffer[userid][packetid]->data,
                &packet->data[3], packet->len-3);
                rbuffer[userid][packetid]->len = packet->len-3;

                // Kootaan pyyntö lähettää aikasempi paketti uudelleen
                packet->data[0] = UDP_RESEND;
                packetid = rbindex[userid];
                packet->len = 3;

                // Nollataan socketin suuntaus
                SDLNet_UDP_Unbind(socket, 0);

                // Suunnataan socketti
                if(SDLNet_UDP_Bind(socket, 0, &packet->address))
                    return -2;

                // Lähetetään paketti
                if(!SDLNet_UDP_Send(socket, 0, packet))
                    return -2;

                break;
            case UDP_REMESSAGE:
                // Jos paketti voidaan palauttaa saman tien
                if(packetid == rbindex[userid]) {
                    // Palautetaan tieto ja id
                    memcpy(data, &packet->data[3], packet->len-3);
                    if(id != NULL)
                        *id = userid;

                    // Siirretään puskurin osoitin eteenpäin
                    rbindex[userid]++;
                    if(rbindex[userid] >= buffersize)
                        rbindex[userid] = 0;

                    return packet->len-3;
                }

                break;
            case UDP_RESEND:
                // Lähetetään pyydetty paketti uudelleen
                if(sbuffer[userid][packetid]) {
                    // Kootaan paketti
                    packet->data[0] = UDP_REMESSAGE;
                    packet->len = sbuffer[userid][packetid]->len+3;
                    memcpy(&packet->data[3], sbuffer[userid][packetid]->data,
                    sbuffer[userid][packetid]->len);

                    // Nollataan socketin suuntaus
                    SDLNet_UDP_Unbind(socket, 0);

                    // Suunnataan socketti
                    if(SDLNet_UDP_Bind(socket, 0, &packet->address))
                        return -2;

                    // Lähetetään paketti
                    if(!SDLNet_UDP_Send(socket, 0, packet))
                        return -2;
                }

                break;
            case UDP_CT:
                // Lisätään käyttäjä listaan
                for(int i = 1; i < maxusers; i++) {
                    if(user[i].host == 0) {
                        user[i] = packet->address;
                        index = i;
                        break;
                    }
                }

                // Jos käyttäjäpaikat on täynnä
                if(index == -1)
                    break;

                // Varataan käyttäjän puskureille muistia
                sbuffer[index] = new UDPpacket*[buffersize];
                rbuffer[index] = new UDPpacket*[buffersize];
                for(int i = 0; i < buffersize; i++) {
                    sbuffer[index][i] = SDLNet_AllocPacket(maxpacketsize);
                    rbuffer[index][i] = NULL;
                }

                // Nollataan käyttäjän puskurien osoittimet
                sbindex[index] = 0;
                rbindex[index] = 0;

                // Päivitetään viimeisimmän viestin aika
                time[index] = SDL_GetTicks();

                // Kootaan paketti
                packet->data[0] = UDP_CTA;
                packet->len = 1;

                // Nollataan socketin suuntaus
                SDLNet_UDP_Unbind(socket, 0);

                // Suunnataan socketti
                if(SDLNet_UDP_Bind(socket, 0, &packet->address))
                    return -2;

                // Lähetetään paketti
                if(!SDLNet_UDP_Send(socket, 0, packet))
                    return -2;

                break;
            case UDP_DISCONNECT:
                // Jos käyttäjää ei ole olmassakaan
                if(!user[userid].host)
                    break;

                // Suljetaan yhteys
                user[userid].host = 0;

                // Vapautetaan käyttäjän puskurit
                for(int i = 0; i < buffersize; i++) {
                    SDLNet_FreePacket(sbuffer[userid][i]);
                    if(rbuffer[userid][i]) {
                        SDLNet_FreePacket(rbuffer[userid][i]);
                        rbuffer[userid][i] = NULL;
                    }
                }

                delete [] sbuffer[userid];
                delete [] rbuffer[userid];
                sbuffer[userid] = NULL;
                rbuffer[userid] = NULL;

                break;
            case UDP_KG:
                // Jos paketti voitaisiin lukea suoraan
                if(packetid == rbindex[userid])
                    break;

                // Kootaan pyyntö lähettää aikasempi paketti uudelleen
                packet->data[0] = UDP_RESEND;
                packetid = rbindex[userid];
                packet->len = 3;

                // Nollataan suuntaus
                SDLNet_UDP_Unbind(socket, 0);

                // Suunnataan socketti
                if(SDLNet_UDP_Bind(socket, 0, &packet->address))
                    return -2;

                // Lähetetään paketti
                if(!SDLNet_UDP_Send(socket, 0, packet))
                    return -2;

                break;
            default:
                break;
        }
    }

    return 0;
}

/* ------------------------------------------------------------ *\
Katkaisee yhteyden käyttäjään

parametrit:
id: käyttäjän id
\* ------------------------------------------------------------ */
void UDP::Kick(int id) {
    // Jos luokkaa ei ole alustettu
    if(!user)
        return;

    // Jos ei olla serveri
    if(maxusers == 0)
        return;

    // Jos id on laiton
    if(id < 0 || id >= maxusers)
        return;

    // Jos id ei ole käytössä
    if(!user[id].host)
        return;

    // Lähetetään ilmoitus yhteydenkatkaisusta
    packet->data[0] = UDP_DISCONNECT;
    SDLNet_UDP_Unbind(socket, 0);
    SDLNet_UDP_Bind(socket, 0, &user[id]);
    SDLNet_UDP_Send(socket, 0, packet);

    // Poistetaan käyttäjä listalta
    user[id].host = 0;

    // Vapautetaan käyttäjän puskurit
    for(int i = 0; i < buffersize; i++) {
        SDLNet_FreePacket(sbuffer[id][i]);
        if(rbuffer[id][i])
            SDLNet_FreePacket(rbuffer[id][i]);
    }

    delete [] sbuffer[id];
    delete [] rbuffer[id];
    sbuffer[id] = NULL;
    rbuffer[id] = NULL;
}

/* ------------------------------------------------------------ *\
Asettaa aikakatkaisun. Jos käyttäjältä ei saada viestejä annetun
ajan kuluessa, suljetaan tämän yhteys.

parametrit:
second: sekunnit
\* ------------------------------------------------------------ */
void UDP::SetTimeout(int second) {
    timeout = second*1000;
}

/* ------------------------------------------------------------ *\
Tarkastaa, onko id varattu

parametrit:
id: käyttäjän id
\* ------------------------------------------------------------ */
bool UDP::Reserved(int id) {
    // Jos luokka ei ole alustettu
    if(!user)
        return false;

    // Jos ei olla serveri
    if(maxusers == 0)
        return false;

    // Jos id on laiton
    if(id < 0 || id >= maxusers)
        return false;

    // Jos id:lle on merkitty ip
    if(user[id].host)
        return true;

    // Id on vapaa
    return false;
}
// Esimerkki clientistä. Ohjelma luo clientin, yhdistää serveriin, pyytää
// käyttäjältä syötteitä ja lähettää ne serverille.

#include <SDL/SDL.h>
#include <SDL/SDL_net.h>
#include <iostream>
#include <string>

#include "udp.h"

using std::cout;
using std::cin;
using std::endl;
using std::string;

// Pakettien maksimikoot ja puskurien koot tulee
// alustaa samoiksi serverillä ja clientillä
const int PACKET_MAX_SIZE = 1024;
const int PACKET_BUFFER_SIZE = 512;

int main(int count, char* param[]) {
    UDP client;                            // client -objekti
    char packet[PACKET_MAX_SIZE] = {0};    // käsiteltävän paketin puskuri
    string input;                          // käyttäjän syöte
    int result = 0;                        // tulos
    bool exit = false;                     // onko aika lopettaa

    // Alustetaan SDL ja SDL_net
    SDL_Init(0);
    SDLNet_Init();

    // Luetaan ja kirjoitetaan konsoliin
    freopen("CON", "w", stdout);
    freopen("CON", "r", stdin);

    // Alustetaan clientti, jossa siirrettävän paketin dataosuuden maksimi
    // koko on PACKET_MAX_SIZE tavua, puskuriin mahtuu 512 pakettia ja
    // vastaanottaa paketteja ensimmäiseen porttiin, joka on vapaana
    // alustus hetkellä
    if(client.Initialize(0, PACKET_MAX_SIZE, PACKET_BUFFER_SIZE, 0) < 0) {
        cout << "Clientin alustaminen epäonnistui" << endl;
        return EXIT_FAILURE;
    }

    // Yhdistetään serveriin
    if(client.Connect("127.0.0.1", 5000) < 0) {
        cout << "Yhdistäminen serveriin epäonnistui" << endl;
        client.Release(); // Ei pakollinen
        return EXIT_FAILURE;
    }

    // Yhdistäminen onnistui
    cout << "Yhdistettiin serveriin" << endl;

    // Pyöritetään clienttiä
    while(!exit) {
        // Luetaan syöte aina rivinvaihtoon asti
        getline(cin, input);

        // Jos halutaan poistua
        if(input == "/quit") {
            // Lopetetaan
            client.Disconnect();
            exit = true;
        } else {
            // Lähetetään viesti serverille
            result = client.SendData((Uint8*)input.c_str(), input.length(), HOST);
            if(result == -1)
                cout << "Viestin lähettäminen epäonnistui" << endl;

            if(result == -2)
                cout << "Socket error!" << endl;

            // Serverillä lähettäminen menisi samaan tapaan
            // server.SendData((Uint8*)input.c_str(), input.lenght(), userid);
        }

        // Reagoidaan serverin pyyntöihin. pdata voi olla NULL, koska
        // esimerkkiserveri lähettää vain järjestelmän sisäisiä paketteja
        result = client.RecvData(NULL, NULL);

        // Jos serveri katkaisi yhteyden
        if(result == -1) {
            cout << "Serveri katkaisi yhteyden" << endl;
            exit = true;
        }

        if(result == -2) {
            cout << "Socket error!" << endl;
            exit = true;
        }

        // Koska asetimme serverillä timeoutin 60sek, olisi hyvä kertoa
        // serverille välillä olemassaolostamme. Se kävisi yksinkertaisesti
        // niin, että vaikka 20sek välein kutsuttaisiin: client.KeepGoing();.
        // Tällöin clientti lähettää serverille tiedon olemassa olostaan.
    }

    // Luokkaa ei ole pakko vapauttaa, se osaa sen itsekin
    client.Release();

    return EXIT_SUCCESS;
}
// Esimerkki serveristä. Ohjelma luo serverin, vastaanottaa paketteja ja
// tulostaa niiden lähettäjät ja pakettien sisällöt.

#include <SDL/SDL.h>
#include <SDL/SDL_net.h>
#include <iostream>

#include "udp.h"

using std::cout;
using std::cin;
using std::endl;

// Pakettien maksimikoot ja puskurien koot tulee
// alustaa samoiksi serverillä ja clientillä
const int PACKET_MAX_SIZE = 1024;
const int PACKET_BUFFER_SIZE = 512;
const int MAX_USERS = 128;

int main(int count, char* param[]) {
    UDP server;                            // server -objekti
    char packet[PACKET_MAX_SIZE] = {0};    // käsiteltävän paketin puskuri
    int packetlen = 0;                     // vastaanotettavan paketin pituus
    int userid = 0;                        // käyttäjän id
    bool exit = false;                     // onko aika lopettaa

    // Alustetaan SDL ja SDL_net
    SDL_Init(0);
    SDLNet_Init();

    // Ohjetaan tulostus konsoliin
    freopen("CON", "w", stdout);

    // Alustetaan serveri, joka ottaa vastaan maksimissaan 128 käyttäjää,
    // siirrettävän paketin dataosuuden maksimikoko on PACKET_MAX_SIZE tavua,
    // puskuriin mahtuu 512 pakettia ja kuunneltava portti on 5000
    if(server.Initialize(MAX_USERS, PACKET_MAX_SIZE, PACKET_BUFFER_SIZE, 5000) < 0) {
        cout << "Serverin alustaminen epäonnistui" << endl;
        return EXIT_FAILURE;
    }

    // Asetetaan aikakatkaisu(nyt 30 sekuntia, eli jos käyttäjä ei lähetä
    // paketteja tietyn ajan kuluessa, niin se tiputetaan
    server.SetTimeout(30);

    // Alustaminen onnistui
    cout << "Serveri luotiin onnistuneesti, odotellaan käyttäjiä" << endl;

    // Pyöritetään serveriä
    while(!exit) {
        // Katsotaan, onko uusia paketteja
        packetlen = server.RecvData(packet, &userid);

        // Clientillä vastaanottaminen tapahtuisi samaan tapaan
        // client.RecvData(packet, NULL);

        // Jos yhteys katkeaa
        if(packetlen == -1) {
            cout << "Käyttäjä(" << userid << ") pudotettu pitkän viiven takia" << endl;
            exit = true;
        }

        // Jos socketti hajos
        if(packetlen == -2) {
            cout << "Socket-error! " << endl;
            exit = true;
        }

        // Jos saatiin paketti
        if(packetlen > 0) {
            // Lisätään nollatavu viestin perään
            packet[packetlen] = 0;

            // Tulostetaan lähettäjä ja paketin sisältö
            cout << "user " << userid << ": " << packet << endl;
        }

        // Jos haluaisimme tietää, onko jokin tietty id varattu, kutsuisimme
        // server.Reserved(id);
    }

    // Luokkaa ei ole pakko vapauttaa, se osaa sen itsekin
    server.Release();

    return EXIT_SUCCESS;
}

tesmu [24.02.2007 13:18:34]

#

Tämä olisi hieno systeemi jos tässä käytettäisiin ihan systeemin soketteja eikä SDL:n net rajapintaa vaikka ei siinäkään mitään vikaa ole...

T.M. [24.02.2007 17:13:08]

#

Voisiko joku laittaa toimivia esimerkkejä?

Turatzuro [24.02.2007 19:29:24]

#

Onhan se hieno pätkä, ja ehdottomasti mieluummin sdl_netillä kuin systeemin socketeilla... Mutta eikö mieluummin kannattaisi käyttää ihan TCP:tä jos pakettien täytyy liikkua järjestyksessä ja hukkumatta?

Gaxx [24.02.2007 20:02:19]

#

Turatzuro kirjoitti:

Onhan se hieno pätkä, ja ehdottomasti mieluummin sdl_netillä kuin systeemin socketeilla... Mutta eikö mieluummin kannattaisi käyttää ihan TCP:tä jos pakettien täytyy liikkua järjestyksessä ja hukkumatta?

Tarkoitukseni oli tehdä systeemistä mahdollisimman kevyt ja nopea. Kehitin tämän jo reilun vuoden verran kehitteillä olleeseen mmorpg:iin, koska sen aikaisempi verkkosysteemi(TCP) oli kovin hidas ja raskas.

jones7 [25.02.2007 01:44:18]

#

Aivan mahtava koodi, vaikken tuosta Release-funktiosta tykkääkään. Miksei sen tilalla oo esim. Javan String-olio??

koo [25.02.2007 23:13:28]

#

Eipä nyt ihan äkkiä tule mieleen, että miksi C++-luokan Release-funktion tilalla pitäisi olla esim. Javan String-olio.

Noh, mutta kun C++:ssa nyt kummiskin on konstruktorit ja destruktorit, niin onko jokin erityinen syy tehdä Initialize- ja Release-funktiot? Tuollaisten kanssa sattuu helposti virheitä. Esmes, mitä tapahtuukaan, jos Initializea kutsutaan useampaan kertaan?

Luokassa on paljon resurssien hallintaa. Missä on kopioiva konstruktori ja sijoitusoperaattori? Nyt taitaa mennä debuggaushommiksi, jos UDP-olion kopioi toiseksi?

Tarviiko erilaisia määriä ja rajoja edes asetella etukäteen? Eikös koodi olisi dynaamisempaa ja robustimpaa, jos siinä käytettäisiin C++:n container-luokkia ja muita C++-maisuuksia kuten exceptioneita? Koska tämä nyt meinaa kumminkin olla C++:aa, eikös siinä pitäisi ainakin varautua exceptioneihin?

Jos lähetettävien pakettien pitää olla määrämuotoisia (tunnus-id-sisältö), eikö se olisi hyvä pakottaa kutsurajapinnassa?

Systeemin raskauteen ja hitauteen vaikuttaa ihan varmasti ekaksi se, että siinä ylipäätänsä tehdään i/o:ta. UDP on kevyt vain sen takia, että jotakin jätetään tekemättä. Jos haluaa vikasietoisuutta - niin kuin esmes nuo uudelleenlähetykset - ja niin että se toimii erilaisissa verkoissa käsiin räjähtämättä, pitää olla aika elite fakiiri. Ei se TCP välttämättä ole ihan huono vaihtoehto.

Ihan rivi riviltä en tuota pätkää jaksanut kahlata, mutta kyllä se sinänsä hyvältä idealta vaikuttaa. Ja vielä parempi siitä varmaan tulisi toisen suunnittelukierroksen jälkeen.

feenix [02.03.2007 12:55:40]

#

Jos nyt oikein katsoin niin vastapään pitää osata pyytää puuttuvaa pakettia, eikö? Miten vastapää tietää, että paketti puuttuu? Oletetaan esimerkiksi että lähetän yhden paketin tunnissa. Vasta tunnin päästä lähettäessäni toista pakettia tiedestään että hupsista, edellinen puuttuu, pyydetäänpä sekin sieltä. Eli tässä on ihan erilainen uudelleenlähetys kuin TCP:ssä.

Käytät paria constia, mutta miksi paketin tyypit jne eivät ole vaikka enumeja vaan numerovakioita? Samoin paluuarvot. Nyt -1 voi tarkoittaa "ei saatu yhteyttä", "pakettia ei voitu lähettää" jne. Rumaa.

Ja samaa mieltä noista vakiokokoisten taulukoiden käytöstä. Vectorit käyttöön!

miiro [11.03.2007 10:29:01]

#

jones7: Mut eihän toi oo javaa!

Gaxx [09.05.2007 09:07:48]

#

Vähän jälkikäteen tulee tämä vastaus, mutta tulee kumminkin :)

Tässä on tullut paljon hyvää rakentavaa kommenttia ja siitä suuri kiitos teille! En ole mikää c++-guru, eli kielen kaikki hienoudet eivät ole vielä tuttuja(container-luokka? exceptioni?).

Vaikka järjestelmä sinällään(virheenkorjauskykyineenkin) kelpaa peliini, ryhdyin suunnittelemaan vastaavanlaista järjestelmää TCP:lle. Eiköhän siitä tule entistä parempi :)

moptim [06.06.2007 17:35:56]

#

Saako käyttää omassa projektissa? Itse epäosaan kaiken internetin kanssa ohjelmoinnissa paitsi jotkut Winsockit :(

Gaxx [18.06.2007 10:33:10]

#

Saa käyttää omissa projekteissa. Sitähän varten tuon tänne lähetinkin :).

ville-v [27.12.2007 13:40:47]

#

Voisiko saada esimerkin palvelimesta ja asiakkaasta?

Itsekin varmasti saisin aikaiseksi, mutta turha siihen on aikaa tuhlata jos sinulla on esimerkit valmiina.

Edit:

// Jos luokka on alustettu
if(user) Release();

= SegFault

// Jos luokka on alustettu
//if(user) Release();

= Toimii ongelmitta :)

Kaatumisen aiheuttaa Release-funktion kutsu kesken Initializen. If-lause on aina tosi. Korjaa.

Edit2:

Yritin käyttää. Packetbuffer täyttyy ylimääräisellä datalla joka ei lähde lopusta. TUlin siihen tulokseen että yrität vain huijata ihmisiä. Siirryn SDL_netin suoraan käyttöön.

Gaxx [29.12.2007 18:48:42]

#

ville-v kirjoitti:

// Jos luokka on alustettu
if(user) Release();

= SegFault

// Jos luokka on alustettu
//if(user) Release();

= Toimii ongelmitta :)

Kaatumisen aiheuttaa Release-funktion kutsu kesken Initializen. If-lause on aina tosi. Korjaa.

Jep, olin huomannut virheen aiemmin omissa projekteissani, mutta en ollut muistanut korjata sitä tänne.

ville-v kirjoitti:

Yritin käyttää. Packetbuffer täyttyy ylimääräisellä datalla joka ei lähde lopusta. TUlin siihen tulokseen että yrität vain huijata ihmisiä. Siirryn SDL_netin suoraan käyttöön.

Nyt en ymmärtänyt kunnolla. Mikä puskuri tarkalleen ottaen täyttyy ylimääräisestä datasta? Ei lähde mistä lopusta?

Sen verran voin sanoa luokan toimivuudesta, että olen itse käyttänyt sitä kahdessa projektissani(toinen valmis ja toinen keskeneräinen), enkä ole havainnut toimivuusongelmia.

Olisi mukava, jos jaksaisit tarkentaa havaintojasi :)

ville-v [01.01.2008 11:15:34]

#

Gaxx kirjoitti:

ville-v kirjoitti:

Yritin käyttää. Packetbuffer täyttyy ylimääräisellä datalla joka ei lähde lopusta. TUlin siihen tulokseen että yrität vain huijata ihmisiä. Siirryn SDL_netin suoraan käyttöön.

Nyt en ymmärtänyt kunnolla. Mikä puskuri tarkalleen ottaen täyttyy ylimääräisestä datasta? Ei lähde mistä lopusta?

Sen verran voin sanoa luokan toimivuudesta, että olen itse käyttänyt sitä kahdessa projektissani(toinen valmis ja toinen keskeneräinen), enkä ole havainnut toimivuusongelmia.

Olisi mukava, jos jaksaisit tarkentaa havaintojasi :)

Tämäntapaiset ohjelmat tein. Jos tein jotain väärin, se johtuu siitä ettet vaivautunut tekemään esimerkkejä helppokäyttöiseen luokkaasi.

#include "./socket/UDP.h"
#include <iostream>

// g++ -o udpservertest.bin $(sdl-config --cflags) udpservertest.c ./socket/UDP.c -lSDL_net

int main(void){
	int quit = 0;
	SDL_Init(0);
	SDLNet_Init();
	UDP serversock;

	char packetbuffer[1000];
	int user_id = 0;

	serversock.Initialize(100,1000,1000,5000);
	std::cout << "Aloitetaan" << std::endl;
	while(!quit){
		if(serversock.RecvData(&packetbuffer, &user_id)){
			std::cout << packetbuffer << std::endl;
		}
	}
	serversock.Release();
	return 0;
}
#include "./socket/UDP.h"
#include <iostream>
#include <string>

// g++ -o udpclienttest.bin $(sdl-config --cflags)  udpclienttest.c ./socket/UDP.c -lSDL_net

int main(void){
	int quit = 0;
	SDL_Init(0);
	SDLNet_Init();
	UDP clientsock;

	char packetbuffer[1000];
	int user_id = 0;

	std::string buffer;

	clientsock.Initialize(0,1000,1000,0);
	clientsock.Connect("localhost", 5000);
	while(!quit){
		getline(std::cin, buffer);
		clientsock.SendData((char*)buffer.c_str(), buffer.length(), user_id);
	}
	clientsock.Release();
	return 0;
}

Tulostus:

ville-v@a84-230-83-57:~/Projektit/Senia/mmorpg$ g++ -o udpservertest.bin $(sdl-config --cflags) udpservertest.c ./socket/UDP.c -lSDL_net
ville-v@a84-230-83-57:~/Projektit/Senia/mmorpg$ ./udpservertest.bin
Aloitetaan
moikkaٷ,�ٷ�
miten menee?�
se bugi näkyykin näköjään äkkiä
vai mitä?�kyykin näköjään äkkiä
kumma jos tätä ei ole muilla äkkiä
tämäjos tätä ei ole muilla äkkiä
kaimäjos tätä ei ole muilla äkkiä
riittää tätä ei ole muilla äkkiä
esimerkiksiätä ei ole muilla äkkiä
ville-v@a84-230-83-57:~/Projektit/Senia/mmorpg$ g++ -o udpclienttest.bin $(sdl-config --cflags)  udpclienttest.c ./socket/UDP.c -lSDL_net
ville-v@a84-230-83-57:~/Projektit/Senia/mmorpg$ ./udpclienttest.bin
moikka
miten menee?
se bugi näkyykin näköjään äkkiä
vai mitä?
kumma jos tätä ei ole muilla
tämä
kai
riittää
esimerkiksi

Gaxx [02.01.2008 02:01:52]

#

Olet tehnyt klassisen virheen: nollatavua ei lähetetä(eikä sitä myöskään lisätä vastaanoton yhteydessä).

Luokka ei tietenkään itse lisäile mitään ylimääräisiä nollatavuja, koska sen täytyy osata välittää, myös raakaa dataan, ei vain merkkijonoja, joiden lopetusmerkkinä on tuo mainittu nollatavu. Luokan ei myöskään ole mielekästä tyhjentää paketbufferia(käyttäjä voi tehdä siitä vaikka gigan kokoisen), vaan vastaanotetun datan pituuden saa RecvData:n palautusarvona(lue funktion kuvaus).

Kieltämättä hieman ihmetyttää, etten ole väsännyt niitä esimerkkejä. Lupaan tehdä ne tämän viikon aikana!

Edit: Ei, se on sen verran pieni homma, että voin luvata esimerkit jo huomiseen iltaan mennessä.

Edit: Nono, tuli lunastettua lupaus ja vielä hyvissä ajoin :)


Sivun alkuun

Vastaus

Aihe on jo aika vanha, joten et voi enää vastata siihen.

Tietoa sivustosta