Kirjautuminen

Haku

Tehtävät

Keskustelu: Ohjelmointikysymykset: ircbot ongelma ( C++ )

Sivun loppuun

Viitapiru [06.08.2008 21:43:59]

#

Tervehdys!

Aloitin ohjelmoimaan irc bottia oppimistarkoituksissa. Osan koodista olen kopioinut erilaisista tutoriaaleista ja osan olen kehitellyt itse. Kielihän on itselleni varsin uusi.

Tähän asti on kaikki on toiminut hyvin, botti yhdistää palvelimelle, vastaa palvelimen lähettämiin PINGeihin, liittyy kanavalle, sekä tulostaa vastaanotetun datan komentolinjaan. Myös virheiden raportoimiseen on käytetty muutama koodirivi.

Ongelma piilee nähdäkseni juuri tässä puskurin tulostuksessa. En ymmärrä miksi se tulostaa saman asian moneen kertaan. Yhtäkkiä puskurin tulostaminen myös lakkaa, mutta botti jatkaa muuten normaalia toimintaansa.

Tässä siis koko koodini nähtävillä

// Simple Ircbot
// Project started 5.7.2008

#include <windows.h>
#include <winsock.h>
#include <stdio.h>
#include <iostream>

#define NETWORK_ERROR -1
#define NETWORK_OK     0

using namespace std;

void ReportError(int, const char *);
char buffer[99999];


char user[] = "USER esimerkki d d esimerkki\r\n";
char nick[] = "NICK esimerkki\r\n";
char join[] = "JOIN #example\r\n";



int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmd, int nShow) {
	WORD sockVersion;
	WSADATA wsaData;
	int nret;

	sockVersion = MAKEWORD(1, 1);
	WSAStartup(sockVersion, &wsaData);
	LPHOSTENT hostEntry;

	hostEntry = gethostbyname("se.quakenet.org");

	if (!hostEntry) {
		nret = WSAGetLastError();
		ReportError(nret, "gethostbyname()");

		WSACleanup();
		return NETWORK_ERROR;
	}


	// Create the socket
	SOCKET theSocket;

	theSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

	if (theSocket == INVALID_SOCKET) {
                  nret = WSAGetLastError();
		          ReportError(nret, "socket()");

		WSACleanup();
		return NETWORK_ERROR;
	}


	SOCKADDR_IN serverInfo;
	serverInfo.sin_family = AF_INET;
	serverInfo.sin_addr = *((LPIN_ADDR)*hostEntry->h_addr_list);
	serverInfo.sin_port = htons(6667);


	// Connect to the server
	nret = connect(theSocket, (LPSOCKADDR)&serverInfo,sizeof(struct sockaddr));

	if (nret == SOCKET_ERROR) {
		nret = WSAGetLastError();
		ReportError(nret, "connect()");

		WSACleanup();
		return NETWORK_ERROR;
	}

	else {
         cout << "Succesfully connected" << endl;

         }

         // Send USER and NICK with their params to server
         send(theSocket, user, strlen(user), 0);
         send(theSocket, nick, strlen(nick), 0);

  int a;
  char *motd;
  char *p, tokit[30];
  char *j;
  char poulet[30];



    while(theSocket) {

                  // To print received data to console
                   nret = recv(theSocket, buffer, sizeof(buffer), 0);
                   cout << ("%s\r\n",buffer);

         // See if Message of the Day is received and join channel
         motd = strstr (buffer, "/MOTD");

         if (motd)
         {
                  send(theSocket, join, strlen(join), 0);
                  }

                   // To see if we received PING
                   p = strstr (buffer, "PING :");
                   if (p)
                   {

                         for (p += 6, a = 0; *p && *p != '\n'; p++, a++)
                             tokit[a] = *p;
                         tokit[a] = 0;

                                   // And answer to it
                                   sprintf (poulet, "PONG :%s\n", tokit);
                                   send (theSocket, poulet, strlen (poulet), 0);

                                   printf ("%s\n",poulet);

                   }

}

return 0;
}

// Messagebox for reporting errors

void ReportError(int errorCode, const char *whichFunc) {
   char errorMsg[92];

   ZeroMemory(errorMsg, 92);
   sprintf(errorMsg, "Call to %s returned error %d!", (char *)whichFunc, errorCode);
   MessageBox(NULL, errorMsg, "socketIndication", MB_OK);
}

Eli jos saisin vastauksia noihin kahteen kysymykseen :) Lisäksi mielelläni kuuntelen muita ohjeita / neuvoja koodin parantamiseksi.

Kiitos.

vehkis91 [06.08.2008 21:54:57]

#

Pistä bufferiksi string bufferi, niin silloin mahtuu loputtomiin dataa sinne... Ja muutkin merkkijonot stringeiksi...

Viitapiru [06.08.2008 22:16:24]

#

vehkis91 kirjoitti:

Pistä bufferiksi string bufferi, niin silloin mahtuu loputtomiin dataa sinne... Ja muutkin merkkijonot stringeiksi...

Mitenkäs tämä tapahtuisi käytännössä?

vehkis91 [06.08.2008 22:22:54]

#

#include <string>

//vaihdat näin
char bufferi[]-> string bufferi

string user = "USER esimerkki d d esimerkki\r\n";
string nick = "NICK esimerkki\r\n";
string join = "JOIN #example\r\n";

Jos tuo bufferi ei toimi, niin pistä pelkät merkkijonot sitten stringeisksi.

Viitapiru [06.08.2008 22:38:41]

#

Kokeilin jo tuota aikaisemmin, mutta koska antoi erroria niin päätin kysyä kuinka tuo käännös tapahtuisi :)

Tässä virheet mitä kääntäjä ilmoitti:

In function `int WinMain(HINSTANCE__*, HINSTANCE__*, CHAR*, int)':

cannot convert `std::string' to `char*' for argument `2' to `int recv(SOCKET, char*, int, int)'

no matching function for call to `strstr(std::string&, const char[6])'

vehkis91 [06.08.2008 22:42:50]

#

pistä siihen:

recv(theSocket, buffer.c_str(), sizeof(buffer), 0);

vehkis91 [06.08.2008 23:29:05]

#

recv(theSocket, const_cast<char *>(buffer.c_str()), sizeof(buffer), 0);

KeKimmo [07.08.2008 14:55:51]

#

Tuon const_castin perusteella string-luokan c_str-metodi palauttaa vakioviittauksen stringin sisältämään merkkidataan C-muodossa. Tällöin datalle on luultavasti varattu vain sen verran tilaa, minkä se todellisuudessa vie. Tähän taulukkoon ei ole tarkoitus kirjoittaa mitään, mistä kertoo mm. se, että vehkis joutuu käyttämään const_castia saadakseen virityksen kääntäjästä läpi. Vehkiksen esimerkin string on ainakin alussa tyhjä, joten recv-funktiolle annettavan merkkitaulukon koko on nolla. Sizeof(buffer) taas on string-luokan, ei siis parametrinä annettavan merkkitaulukon, koko.

Joku virhe varmaan unohtui, mutta joku muu voi täydentää.

Yhteenvetona: vehkiksen koodi EI tässä tilanteessa toimi.

EDIT 1:

Recv-funktion palauttama merkkijono ei sisällä merkkijonon loppua osoittavaa nollamerkkiä. Se on lisättävä itse.

vehkis91 [07.08.2008 15:01:25]

#

No mites sitten laitetaan silleen että käytetään stringiä bufferina?

Edit:

recv(theSocket, const_cast<char *>(buffer.c_str()), sizeof(buffer.c_str()), 0);?

KeKimmo [07.08.2008 15:04:34]

#

vehkis91 kirjoitti:

No mites sitten laitetaan silleen että käytetään stringiä bufferina?

Ei tule ainakaan mieleen, miten tuollainen voisi onnistua. Helpoimmalla varmaan pääsee kirjoittamalla oman recv-wrapperin, joka lukee merkkejä vaikkapa rivinvaihtoon asti ja muodostaa niistä stringin.

vehkis91 [07.08.2008 15:07:54]

#

eli lyhyesti käytetään char-taulukko bufferina...

Metabolix [07.08.2008 15:19:18]

#

En näe erityistä syytä stringin käyttöön. IRC-protokolla jo itsessään rajoittaa rivin pituutta, ja puskurisi on aivan tarpeettoman iso.

Kuvittelisin, että recv ei aseta tekstin loppuun tarvittavaa nollamerkkiä. Aseta siis vastaanottamisen jälkeen se itse:

buffer[nlen] = 0;

Ehto "while (theSocket)" on huono, koska tuon muuttujan arvo ei muutu silmukassa.

Koodin parantamisesta: Asettelua ja sisennystä voisit ainakin parantaa, koodi on vaikeaselkoista, kun lohkot eivät ole yhtenäisesti sisennettyjä. Älä myöskään käytä turhan päiten Windowsin outouksia kuten WinMain-funktiota ja muuta sellaista, kun niiden käytölle ei ole erityistä perustetta, vaan tee ohjelmastasi helposti muillekin käyttöjärjestelmille käännettävä.

KeKimmo kertoikin jo olennaisen tästä string-häsellyksestä. Vehkis voisi ehkä hieman harkita ennen vastaamista, ei millään pahalla. Näitä viallisia purkkavinkkejä tahtoo sadella aika tiheään.

vehkis91 [07.08.2008 15:38:56]

#

Mietin kyllä, ja minulle on aina sanottu, että pitää käyttää mahdollisimman paljon stringejäja mahdollisimman vähän char-taulukoita. Joten sori vaan, otan opikseni.

Viitapiru [07.08.2008 16:22:53]

#

Juu, tosiaan vehkiksen kanssa tätä eilen pohdiskeltiin ja päädyttiin siihen tulokseen, että aloitan saman projektin käyttäen SDL_net:iä.

lainaus:

pitää käyttää mahdollisimman paljon stringejäja mahdollisimman vähän char-taulukoita

Täällä ( http://www.nic.funet.fi/c opas/stdlib.html#string ) taasen neuvotaan käyttämään mahdollisimman vähän stringejä ja suosia enemmänki char-taulukoita :)

Noh, en tiedä tästä, jospa joku joka tietää voisi vähän valaista?

Mutta kiitokset jo tässä vaiheessa vastanneille.

vehkis91 [07.08.2008 16:34:35]

#

Tuolla kyllä sanotaan, että ei kannata käyttää char-taulukoita... :P

"C:ssä merkkijonot ovat char-taulukoita. Ja niinhän ne pohjimmiltaan ovat C++:ssakin. Merkkijonoja käytettäessä on hyvä muistaa, että niitä ei pidä mennä käyttämään.Vältä niitä kuin ruttoa."

Blaze [07.08.2008 17:01:41]

#

Se poikkeus tuohon sääntöön on juuri tämä ulkoisten rajapintojen känssä säheltäminen. Niiden kanssa voi vaan olla pakko.

vehkis91 [07.08.2008 17:03:08]

#

Okei, pidän mielessä.

os [07.08.2008 19:12:13]

#

Ja tämä poikkeus johtuu tarkemmin siitä, ettei Windowsille (kuten todella monelle muullekaan järjestelmälle/kirjastolle) ole erillistä C++-ohjelmointirajapintaa, vaan joudutaan käyttämään C-kielelle tarkoitettua APIa. C-kielisiä rajapintoja joutuu käyttämään C++-ohjelmoinnissa yleensä paljon, mikä on hyvä syy osata C++:n lisäksi myös sujuvasti C:tä.

Socketeille toki löytyy myös "epävirallisia" C++-wrappereita
http://www.google.com/search?q=c++ socket

jotka kannattaa ottaa käyttöön tai kirjoittaa itse vastaava, ennen kun pää hajoaa kivikautisten socket-APIen kanssa. Tai ihan hyviähän ne on, jos korkeampi abstraktiotaso ei vaan kelpaa...

vehkis91 [07.08.2008 19:34:46]

#

Tein oman botin käyttämällä SDL_nettiä ja siinä ei ole mitään tuon kaltaisia ongelmia... Koodi on melkeen suoraan käännetty tuosta koodista winsock -> SDL_net.

maz [07.08.2008 21:48:38]

#

Stringit ovat C++ koodarille ylivertainen char taulukoihin verrattuna, turvallisuutensa ja käyttömukavuutensa ansiosta. oikeastaan ainoa syy käyttää chareja, on juuri tämä vaadittu C-yhteensopivuus, sekä se, että joissain systeemeissä ei muisti riitä stringien (eikä varsinkaan STL containereiden) käyttöön. Ja yksi mahdollinen syyhän on tietysti, se, että osa meistä (kuten minä) ovat vanhoja jääriä, joilla on luontainen inho kaikkea "uutta" kohtaan...

Mutta chareja voi kyllä käyttää kun on tarkkana, että.
1. Et ikinä ylikirjoita varaamaasi tilaa!. Huom, char arrayn päättävä NULL merkki vie yhden tavun ylimääräistä tilaa!
2. Et ikinä unohda NULL merkkiä char taulusi lopusta. Suurín osa C:n str* funktioista ottaa argumenttinaan char arrayn alkuosoitteen, ja lukee muistia kunnes ensimmäinen NULL merkki löytyy, tulkiten kaiken tällä välillä tarkoitetuksi char taulukoksi. Jos NULL merkki unohtuu, lukee (tai kirjoittaa!!) str* funktiot satunnaisen pätkän muistia - kunnes kohtaavat ensimmäisen NULL merkin, tai kunnes käyttöjärjestelmä huomaa prosessin kirjoittelevan/lukevan mitä sattuu muistialuetta, ja tappaa sen antaen Ah niin Lohduttavan ilmoituksen:
Segmentation Fault.

C++ stringien kanssa näistä huolenaiheista pääsee osaksi eroon...

Grez [07.08.2008 22:02:09]

#

Lisäksi ottaen huomioon, että kaikissa järjestelmissä muistin määrä on rajallinen, tekee seuraava pointti Stringeistä aivan ylivertaisen...

vehkis91 kirjoitti:

Pistä bufferiksi string bufferi, niin silloin mahtuu loputtomiin dataa sinne..

:D


Sivun alkuun

Vastaus

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

Tietoa sivustosta