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?
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 :)
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.
Gaxx kirjoitti:
sizeof(data_buffer) - buffer_positionTutkit osoittimen pituutta ja vähennät siitä (yleensä) nollan :)
Olet oikeassa, laitoin tilalle sen pituuden jonka varaan taulukolle. Mites sen saisi sizeof()illa?
ville-v kirjoitti:
Gaxx kirjoitti:
sizeof(data_buffer) - buffer_positionTutkit 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;
Aihe on jo aika vanha, joten et voi enää vastata siihen.