Kirjautuminen

Haku

Tehtävät

Keskustelu: Ohjelmointikysymykset: C++: SDL_net@WinXP: Lähetetty tieto ei saavu perille oikein

ville-v [25.11.2008 19:42:30]

#

Ongelma: Server ei saa kaikkea dataa, mitä client lähettää. Kaikki ei-SDL_net-funktiot ovat itseni rakentamia, joten ongelma voi johtua pienestäkin virheestä. Olen mielestäni käynyt kaikki vaihtoehdot läpi ja korjannutkin pari pienempää bugia, jotka eivät kuitenkaan vaikuttaneet ongelmaan. Koodien alapuolella lokeja ja lisäselostusta.

Ongelmallisen funktion käyttämä command_data-luokka:

class command_data{
public:
        unsigned char command;
        /**
         * Length of data, without length of length or command!
         * This is different to what is sent in protocol!
         * In protocol the complete length of packet is included!
         */
        unsigned short length;
        unsigned char *data;

        command_data() : command(0), length(0), data(NULL){}
        command_data(const unsigned char c, const unsigned short l, unsigned char *d) : command(c), length(l), data(d){}
        command_data(const unsigned char c, const unsigned short l, char *d) : command(c), length(l), data((unsigned char*)d){}
        command_data(const unsigned char c, const unsigned short l, const unsigned char *d) : command(c), length(l), data((unsigned char*)d){}
        command_data(const unsigned char c, const unsigned short l, const char *d) : command(c), length(l), data((unsigned char*)d){}

        inline const char *print(void) const
        {
                static char *buffer = new char[length+30];
                sprintf(buffer, "Command %x, data length %d, data: %s", command, length, data);
                return buffer;
        }
};

Serverin dataa vastaanottava funktio on luokassa, josta löytyvät mm. seuraavat jäsenmuuttujat, joita käytetään:

TCPsocket handle;
bool disconnected;
unsigned char *data_buffer;
unsigned short buffer_position;

Tarkistin jo, että jäsenmuuttujat alustetaan joka tilanteessa oikein.

Varsinainen funktio sitten (palauttaa false mikäli uutta komentoa ei ole saatavilla, true muuten):

const bool socket::check_for_command(command_data *buffer)
{
        // Is socket ready?
        if(handle == NULL){
                disconnected = true;
                return false;
        }
        else if(!this->is_ready()){
                return false;
        }
        // Receive more data from socket
        const int received = SDLNet_TCP_Recv(handle, &data_buffer[buffer_position], sizeof(data_buffer) - buffer_position);
        if(received > 0){
                log.debug("check_for_command():");
                sprintf(log_buffer, "Received %d bytes of data.", received);
                log.debug(log_buffer);
                sprintf(log_buffer, "10 first bytes of data_buffer, hex: 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x", data_buffer[0], data_buffer[1], data_buffer[2], data_buffer[3], data_buffer[4], data_buffer[5], data_buffer[6], data_buffer[7], data_buffer[8], data_buffer[9]);
                log.debug(log_buffer);
                sprintf(log_buffer, "Repeat, decimal: %d %d %d %d %d %d %d %d %d %d", data_buffer[0], data_buffer[1], data_buffer[2], data_buffer[3], data_buffer[4], data_buffer[5], data_buffer[6], data_buffer[7], data_buffer[8], data_buffer[9]);
                log.debug(log_buffer);
                sprintf(log_buffer, "Repeat, char: %c %c %c %c %c %c %c %c %c %c", data_buffer[0]? data_buffer[0] : 1, data_buffer[1]? data_buffer[1] : 1, data_buffer[2]? data_buffer[2] : 1, data_buffer[3]? data_buffer[3] : 1, data_buffer[4]? data_buffer[4] : 1, data_buffer[5]? data_buffer[5] : 1, data_buffer[6]? data_buffer[6] : 1, data_buffer[7]? data_buffer[7] : 1, data_buffer[8]? data_buffer[8] : 1, data_buffer[9]? data_buffer[9] : 1);
                log.debug(log_buffer);

                buffer_position += received;

                // Parse commands from buffer, if there are enough bytes for one command to exist
                if(buffer_position >= 3){
                        unsigned char *pointer_to_data = data_buffer;

                        // Second and third bytes of buffer must be length
                        const unsigned short length = (data_buffer[1] << 8)|(data_buffer[2]);
                        sprintf(log_buffer, "Length of command in buffer is %d, buffer length is %d.", length, buffer_position);
                        log.debug(log_buffer);

                        // False length
                        if(length > server.MAX_COMMAND_LENGTH){
                                // TODO: Kick user
                                log.debug("False length.");
                                return false;
                        }

                        // There is at least one complete command inside the buffer
                        if(buffer_position >= (length - 1)){
                                log.debug("if(buffer_position >= (length - 1))");

                                if(buffer == NULL){
                                        log.debug("Buffer was NULL.");
                                        buffer = new command_data();
                                }

                                buffer->command = data_buffer[0];
                                // Length of data, not of packet
                                buffer->length = length - 3;

                                // Copying command data into command buffer
                                //delete[] buffer->data;
                                buffer->data = new unsigned char[length];
                                memcpy(buffer->data, pointer_to_data+3, buffer->length);

                                // Moving index
                                pointer_to_data += length;
                                buffer_position -= length;
                                log.debug("About to move rest of the data.");
                                // Moving remaining data into the beginning
                                if(buffer_position > 0 && pointer_to_data != data_buffer){
                		      memmove(data_buffer, pointer_to_data, buffer_position);
                		      log.debug("Got one command, but there is still incomplete command in a buffer.");
                		}
                		if(!buffer->length){
                                        buffer->data = NULL;
                                }
                                log.debug(buffer->print());

                		// Removing some incorrect commands right away
                		if(buffer->command == 0){
                                        log.debug("if(buffer->command == 0)");
                                        return false;
                                }

                                refresh_last_action();
                                log.debug("check_for_command() ending.");
                		return true;
                        }
                }
                log.debug("check_for_command() ending, !(buffer_position >= 3)");
	        return false;
	}
        // Error occured
        else{
                // Disconnecting user
                sprintf(log_buffer, "check_for_command(): received == %d.", received);
                log.debug(log_buffer);
                server.players[player_index].log_out();
                clear();
        }
        return false;
}

Client-puolen ongelmallinen funktio, joka lähettää dataa (sock ja set globaaleja muuttujia, joita ei kuitenkaan käytetä kuin kyseisestä tiedostosta):

const bool send_command(command_data *cmd)
{
        if(sock == NULL){
                return true;
        }
        else if(cmd == NULL){
                log.debug("send_command(): cmd == NULL");
                return true;
        }
        else if(!cmd->command){
                return true;
        }

        const unsigned short total_len = cmd->length + 3;
        if(total_len >= 2000){
                log.error("send_command(): Too long command.");
                return false;
        }
#define DEBUG_NETWORK
#ifdef DEBUG_NETWORK
        unsigned char *command_buffer = new unsigned char[(total_len>10)?total_len:10];
#else
        unsigned char *command_buffer = new unsigned char[total_len];
#endif
        unsigned short point = 0;

        // Constructing header with command and length
        command_buffer[point++] = cmd->command;
        command_buffer[point++] = char_from_short_high(total_len);
        command_buffer[point++] = char_from_short_low(total_len);

        // Copying data into the buffer
        if(cmd->length > 0){
                memcpy(&command_buffer[point], cmd->data, cmd->length);
        }
        log.debug("send_command():");
#ifdef DEBUG_NETWORK
        sprintf(log_buffer, "10 first bytes of command_buffer, hex: 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x", command_buffer[0], command_buffer[1], command_buffer[2], command_buffer[3], command_buffer[4], command_buffer[5], command_buffer[6], command_buffer[7], command_buffer[8], command_buffer[9]);
        log.debug(log_buffer);
        sprintf(log_buffer, "Repeat, decimal: %d %d %d %d %d %d %d %d %d %d", command_buffer[0], command_buffer[1], command_buffer[2], command_buffer[3], command_buffer[4], command_buffer[5], command_buffer[6], command_buffer[7], command_buffer[8], command_buffer[9]);
        log.debug(log_buffer);
        sprintf(log_buffer, "Repeat, char: %c %c %c %c %c %c %c %c %c %c", command_buffer[0]? command_buffer[0] : 1, command_buffer[1]? command_buffer[1] : 1, command_buffer[2]? command_buffer[2] : 1, command_buffer[3]? command_buffer[3] : 1, command_buffer[4]? command_buffer[4] : 1, command_buffer[5]? command_buffer[5] : 1, command_buffer[6]? command_buffer[6] : 1, command_buffer[7]? command_buffer[7] : 1, command_buffer[8]? command_buffer[8] : 1, command_buffer[9]? command_buffer[9] : 1);
        log.debug(log_buffer);
#else
        log.debug(cmd->print());
#endif

        // Sending command and deleting both buffers
        const int result = SDLNet_TCP_Send(sock, command_buffer, total_len);
        delete[] command_buffer;
        //delete cmd;

        sprintf(log_buffer, "Result is %d, total_len is %d.", result, total_len);
        log.debug(log_buffer);
        log.debug("send_command() ends.");

        // An error occured
        if(result < total_len){
                log.debug("send_command(): Sending data failed.");
                return false;
        }
        return true;
}

Tapahtumien kulku:

Client ottaa yhteyden serveriin ja lähettää pingin: ensimmäinen tavu on komento, kaksi seuraavaa pituus ja neljä seuraavaa aika. Tulostuksen mukaan kaikki sujuu oikein:

2008/11/25 19:21:38: send_command():
2008/11/25 19:21:38: 10 first bytes of command_buffer, hex: 0xfd 0x0 0x7 0x49 0x2c 0x34 0x22 0xba 0xee 0xfe
2008/11/25 19:21:38: Repeat, decimal: 253 0 7 73 44 52 34 186 238 254
2008/11/25 19:21:38: Repeat, char: ý � � I , 4 " º î þ
2008/11/25 19:21:38: Result is 7, total_len is 7.
2008/11/25 19:21:38: send_command() ends.

Server havaitsee yhteydenoton, mutta vastaanottaa vain neljä tavua. Bufferin tulostuksesta näkyy, että näin todella käy:

2008/11/25 19:21:38: check_for_command():
2008/11/25 19:21:38: Received 4 bytes of data.
2008/11/25 19:21:38: 10 first bytes of data_buffer, hex: 0xfd 0x0 0x7 0x49 0xd 0xf0 0xad 0xba 0xd 0xf0
2008/11/25 19:21:38: Repeat, decimal: 253 0 7 73 13 240 173 186 13 240
2008/11/25 19:21:38: Repeat, char: ý � � I
 ð ­ º
 ð
2008/11/25 19:21:38: Length of command in buffer is 7, buffer length is 4.
2008/11/25 19:21:38: check_for_command() ending, !(buffer_position >= 3)

// Tämä tapahtuu silmukan seuraavalla kierroksella: Server sulkee socketin
2008/11/25 19:21:38: check_for_command(): received == 0.
2008/11/25 19:21:38: A socket has been closed.

Client ei tajua, että socket on suljettu, seuraava ping siis epäonnistuu:

2008/11/25 19:21:42: send_command():
2008/11/25 19:21:42: 10 first bytes of command_buffer, hex: 0xfd 0x0 0x7 0x49 0x2c 0x34 0x26 0xba 0xee 0xfe
2008/11/25 19:21:42: Repeat, decimal: 253 0 7 73 44 52 38 186 238 254
2008/11/25 19:21:42: Repeat, char: ý � � I , 4 & º î þ
2008/11/25 19:21:42: Result is 0, total_len is 7.
2008/11/25 19:21:42: send_command() ends.
2008/11/25 19:21:42: send_command(): Sending data failed.

Client ottaa uudelleen yhteyden, ja server avaa uuden socketin, jolla on siis eri buffer:

2008/11/25 19:21:44: A socket has been opened.

Client yrittää uudelleen ja taas lähetys onnistuu:

2008/11/25 19:21:50: send_command():
2008/11/25 19:21:50: 10 first bytes of command_buffer, hex: 0xfd 0x0 0x7 0x49 0x2c 0x34 0x2d 0xba 0xee 0xfe
2008/11/25 19:21:50: Repeat, decimal: 253 0 7 73 44 52 45 186 238 254
2008/11/25 19:21:50: Repeat, char: ý � � I , 4 - º î þ
2008/11/25 19:21:50: Result is 7, total_len is 7.
2008/11/25 19:21:50: send_command() ends.

Sama kuitenkin toistuu, server vastaanottaa neljä tavua ja seuraavalla kutsukerralla SDL_net ilmoittaa clientin sulkeneen yhteyden:

2008/11/25 19:21:50: check_for_command():
2008/11/25 19:21:50: Received 4 bytes of data.
2008/11/25 19:21:50: 10 first bytes of data_buffer, hex: 0xfd 0x0 0x7 0x49 0xd 0xf0 0xad 0xba 0xd 0xf0
2008/11/25 19:21:50: Repeat, decimal: 253 0 7 73 13 240 173 186 13 240
2008/11/25 19:21:50: Repeat, char: ý � � I
 ð ­ º
 ð
2008/11/25 19:21:50: Length of command in buffer is 7, buffer length is 4.
2008/11/25 19:21:50: check_for_command() ending, !(buffer_position >= 3)

// Seuraavalla kutsukerralla
2008/11/25 19:21:50: check_for_command(): received == 0.
2008/11/25 19:21:50: A socket has been closed.

Mikäli komennon pituus on vain kolme tavua, vastaanottaminen sujuu aivan normaalisti. SDLNet_TCP_Recv() palauttaa kuitenkin pidemmillä komennoilla pituudeksi neljä tavua, onnistuneesti lähetetyn komennon pituudesta huolimatta. Mistähän tällainen käytös johtuu ja miten ongelmaa voisi selvittää pidemmälle?

Gaxx [25.11.2008 20:24:34]

#

ville-v kirjoitti:

const int received = SDLNet_TCP_Recv(handle, &data_buffer[buffer_position], sizeof(data_buffer) - buffer_position);

sizeof(data_buffer) - buffer_position

Tutkit osoittimen pituutta ja vähennät siitä (yleensä) nollan :)

Metabolix [26.11.2008 09:39:10]

#

Muutenkin koodisi on täynnä kaikenlaista ontuvaa toteutusta ja jopa virheitä. Esimerkiksi print-funktiossa varaat liian vähän tilaa tekstille ja palautat vielä osoittimen, jota et myöhemmin koodissasi vapauta. Kaikin puolin koodissa olisi paljon parannettavaa, ja parannusten kanssa myös sen toiminta olisi varmempaa. Kun kerran C++:aa käytät, voisit käyttää sitä myös tekstien ja taulukoiden käsittelyyn: vector toimii kuten taulukko mutta vapauttaa itsensä, ja tekstin säilyttäminen string-oliossa ja muotoileminen stringstreamin avulla estäisi äsken mainitsemani virheen. Datan vastaanotossa voi käyttää puskurina lyhyttä, paikallista char-taulua, josta datan saa sitten heti siirtää stringiin tai char-vektoriin insert-metodilla. Ainoa kohta, jossa osoitin on ehkä perusteltu, on tuo command_data::data, mutta senkin kohdalla kannattaisi harkita vaihtoehtoisia toteutustapoja.

Ihan ensimmäinen askel tuonkin debuggaamisessa olisi katsoa, mitkä parametrit SDLNet_TCP_Send ja SDLNet_TCP_Recv ihan oikeasti saavat. Ei auta tulostella dataa ja katsoa, että siinähän sitä on, vaan pitää tulostaa olennaiset tiedot eli se, paljonko funktion on käsketty lähettää tai vastaanottaa ja paljonko oikeasti meni.

ville-v [26.11.2008 11:43:45]

#

Gaxx kirjoitti:

sizeof(data_buffer) - buffer_position

Tutkit osoittimen pituutta ja vähennät siitä (yleensä) nollan :)

Olet oikeassa, laitoin tilalle sen pituuden jonka varaan taulukolle. Mites sen saisi sizeof()illa?

Legu [26.11.2008 14:25:14]

#

ville-v kirjoitti:

Gaxx kirjoitti:

sizeof(data_buffer) - buffer_position

Tutkit osoittimen pituutta ja vähennät siitä (yleensä) nollan :)

Olet oikeassa, laitoin tilalle sen pituuden jonka varaan taulukolle. Mites sen saisi sizeof()illa?

Ei mitenkään. sizeof-operaattori suoritetaan käännösvaiheessa, joten dynaamisesti varatun muistin kokoa sillä ei voi selvittää. Mainittakoon vielä, että staattisen taulukon tapauksessa sizeof-operaattorin käyttö onnistuu:

char buffer_static[123];
char * buffer_dynamic = new char[123];

// sizeof (buffer_static) = 123 * sizeof(char) = 123
// sizeof (buffer_dynamic) = sizeof(char*) = 4

delete [] buffer_dynamic;

Vastaus

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

Tietoa sivustosta