Kirjautuminen

Haku

Tehtävät

Keskustelu: Ohjelmointikysymykset: C++, Limbo: Ongelma socket-koodivinkin kanssa

Sivun loppuun

Macro [03.10.2009 16:20:43]

#

Heippa taas!

Koittaisin muokkaamalla tätä koodivinkkiä tehdä yksinkertaisen chat ohjelman.

Ongelmaksi muodostui se, että se herjaa noista Winsock jutuista koodissa (serveri, en ehtinyt kokeilla clienttiä). Tarkoitus olisi saada tälläinen komentorivipohjaisena tehty chat-ohjelma. Onko socketit paras tapa tehdä, vai mitä jos käyttää jotain muuta tyyliä?

Vai, olisiko järkevää harjoitella tuota Visual Basic 6. sen verran, että osaisin tehdä itselleni sopivan version? Samalla saisi siitä graafisemman. SDL:ssä ei kun pysty kunnolla lukemaan tekstiä, toisin sanoen tekemään formia.

Metabolix [03.10.2009 16:26:51]

#

Esimerkiksi SDL_net-kirjasto yksinkertaistaa asioita jonkin verran. Yksinkertaisen esimerkin voit lukea täältä. Kommunikointi perustuu pohjimmiltaan joka tapauksessa socketteihin, mutta monet kirjastot (kuten SDL_net) yksinkertaistavat tilannetta (ja toisaalta rajoittavat hieman ohjelmoijan vapauksia). Lisäksi tällaiset kirjastot piilottavat käyttöjärjestelmien erot.

VB 6:tta ei kannata opetella, se on jo vähitellen historiaa. Ennemmin VB.Net, tai kun nyt C++:aa osaat, niin C# tai Java.

Macro [03.10.2009 17:14:38]

#

Tiesinkin jo tuosta SDL_net kirjastosta. Latasin sen SDL_net-devel-1.2.7-VC8.zip sielä SDL:n kotisivuilta, ja laitoin includet include kansioon. Mutta, sielläkin on sellainen SDL_net.lib tiedosto. Pitikö se muuttaa SDL_net.a nimiseksi?

Oletko testannut sivun esimerkkejä, että toimivatko ne? =)

Metabolix [03.10.2009 22:47:10]

#

Kirjaston suhteen käy perinteinen menettely, eli nimen muunnos (SDL_net.lib -> libSDL_net.a) ja siirto (.h -> include, .a -> lib, .dll -> ohjelman hakemisto). Esimerkkejä en ole testannut, mutta sivusto on ilmeeltään luotettava ja koodi on kohtalaisen siististi kirjoitettu, joten on syytä olettaa, että ne toimivat.

Macro [04.10.2009 11:28:51]

#

Sain molemmat kääntymään noista kun muutin linkkerin asetuksia ja muutin koodissa pari include-kohtaa.

Kuitenkaan ne eivät toimi kunnolla. Serveri sanoo, että "Usage: C:\...\server.exe host port" ja clientti on tyhjänä. Serverin kun käänsin, niin palomuuri kysyi, että sallitaanko ohjelma.


Tässä tuli mieleen, että kannattaako sitä sittenkään tehdä C++:lla ja SDL:llä, koska SDL:n tekstinkäsittely puoli on hankala. Komentorivipohjaisena ohjelman saa toimimaan, mutta entä jos siitä haluaa graafisen?

jalski [04.10.2009 12:11:43]

#

Mitä tarkoitat sillä, että eivät toimi kunnolla?

Esimerkkien koodia ja toimintaa näkemättä arvelisin, että serveri haluaa parametriksi portin mitä kuunnella ja palomuurissa pitää tietysti olla kyseinen portti auki.

Mielestäni opetellessa kannattaa ensin aloittaa komentorivipohjaisena ja aluksi niin, että serveri hyväksyy vain yhden yhteyden kerrallaan. Kun saat tämän toimimaan, niin voit harjoitella säikeiden kanssa touhuamista ja lisätä sitten tuen useammalle asiakkaalle siten, että pääsäie kuuntelee yhteydenottoja ja jokaista asiakasta palvellaan omassa säikeessään.

Graafisen toteutuksen tielle ei kannata lähteä ennen säikeiden käytön hallitsemista. Joudut nimittäin huolehtimaan, että muu ohjelma ei blokkaa käyttöliittymää. Tämä onnistuu kyllä ilman säikeitäkin, mutta ei niin nätisti (esim. timeout sockets).

Metabolix [04.10.2009 13:30:27]

#

Koodin perusteella palvelin kuuntelee automaattisesti portissa 2000 ja asiakkaalle pitää antaa parametrina palvelin (oma kone eli localhost) ja portti (2000).

Esimerkki kirjoitti:

/* Resolving the host using NULL make network interface to listen */
SDLNet_ResolveHost(&ip, NULL, 2000)
/* Resolve the host we are connecting to */
SDLNet_ResolveHost(&ip, argv[1], atoi(argv[2]))

Siis aja nämä komennot eri ikkunoissa:
server
client localhost 2000

Voit käyttää SDL_netiä, vaikka tekisit loput ohjelmasta jollain muulla kirjastolla. Joka tapauksessa joudut opettelemaan jonkin uuden asian, oli se sitten wxWidgets, Curses (PDCurses) tai C#-kieli kehitysympäristöineen.

Macro [04.10.2009 15:30:01]

#

Jep, kiitos tiedosta.

Ncurses vaikutti parhaalta vaihtoehdolta. Pystyykö sillä toteuttamaan helposti/kohtalaisesti tämmöisen kirjoitusalueen, kun esimerkiksi nettisivupuolella textarea? Jos joku käyttää msn messengeriä, skypeä tms. niin samanlainen kirjoitusloota :)

Kai Ncursesia ja SDL:ää voi käyttää sekaisin? Että SDL:llä hoitaa kaiken tarvittavan, mutta tällä tekisi tuon tekstialueen?

Muokkaus. Katos kummaa, tuo Ncurses onkin Unix-järjestelmistä, että täytyy vilkaista sitä PDCurses kirjastoa. Onnistuuhan sillä silti samat asiat?

Metabolix [04.10.2009 15:46:05]

#

Curses (N, X, PD, ...) on täysin tekstitilaa varten. Ei ole oikeastaan mitään järkeä käyttää sitä SDL:n kanssa sekaisin, koska eihän käyttäjä voi kuitenkaan järkevästi käsitellä samaan aikaan SDL-ikkunaa ja konsoli-ikkunaa. Cursesilla saa kuitenkin tehtyä tekstitilassa ohjelmia, joissa tekstiä voi tulostaa konsolissa mihin tahansa kohtaan, näppäinten luku ei vaadi Enterin painamista ja näppäintä odotetaan enintään tietty aika, ennen kuin ohjelma jatkuu itsekseen. Tällä tavalla saadaan aikaan varsin käyttökelpoisia ohjelmia.

Macro [04.10.2009 16:36:42]

#

Siis sitä ei voi käyttää graafiseen ohjelmointiin? Opas kertoo kyllä toista, että sillä voi tehdä tekstigraafisia ohjelmia.

Metabolix [04.10.2009 16:39:28]

#

Tekstigraafinen ohjelma tarkoittaa tekstitilassa toimivaa ohjelmaa, joka kuitenkin muistuttaa graafista ohjelmaa niiltä osin, kuin äsken selitin. Muutenhan komentoriviohjelmassa käyttäjän syötettä luetaan rivi kerrallaan ja tulostettavat rivit ilmestyvät ruudun alareunaan. Eikö esimerkiksi linkittämäni irssi näytä tähän verrattuna melkoisen graafiselta? (Älä mene lankaan: joissain kuvissa näkyy oikeaa grafiikkaa, joka on peräisin Linuxissa konsolin taustakuvasta eikä suinkaan Curses-ohjelmasta.)

Macro [04.10.2009 16:47:15]

#

Ahaa! Joo, näyttävät hyvältä. En tarvitse mitään super-graafista ohjelmaa, vaan tuon näköisen kuin tuo elite-ulkoasu on.

Mutta, saako Windows-konsoliin värit ilman mitään lisäkirjastoa? Siis, tekstin värin muutettua eri henkilöille.

Varmaankin käytän tuota PDCursersia tässä projektissa, koska online-käyttäjät olisi hyvä saada tuonne reunaan. Siis, nuo ikkunat on tehty tälle (PD)Curserilla, eikä se ole mikään konsoli? Konsolia kun ei saa tarpeeksi suureksi venytettyä...

Metabolix [04.10.2009 17:23:33]

#

Valitettavasti se käyttää nimenomaan sitä valmista konsoli-ikkunaa. (Irssiä yleensä käytetään Linuxissa, jolloin kirjastona on ncurses, ja Linuxin konsoli-ikkuna on paljon taipuisampi kuin Windowsin.) Yksinkertaiseen chattiin sekin ikkuna riittää minusta aivan hyvin.

Värejä saa kyllä vaihdettua, esimerkiksi PDCurses hoitaa asian melko helposti. Taustalla ovat toki pohjimmiltaan Windowsin omat funktiot, mutta itse en ainakaan koskisi niihin, kun hyviä vaihtoehtojakin on.

Macro [04.10.2009 18:21:48]

#

Selvä, kiitos.

En saanut toimimaan ohjelmaa, minkä linkkasit aikaisemmin. Siis sitä serveri-client juttua.

string ip = "123";
string tunnus;
string salasana;
string viesti;

if(ip == "123") {
	cout << "Yhdistä serveriin osoitteessa: ";
	cin >> ip;
} else {
	cout << "\n\nAnna tunnus: ";
	cin >> tunnus;

	if(tunnus.lenght() > 0) {
		cout << "\n\nAnna salasana: ";
		cin >> salasana;

		if(tunnus == "Macro" & salasana == "salainenkala" || tunnus == "Kala" & salasana == "salainenfisu") {
			system("CLS");
			cout << "Tervetuloa " << tunnus << "!\n\n";

			hae_viestit(ip);

			cout << "Kirjoita: ";
			cin >> viesti;

			laheta_viesti(viesti);
		} else {
			cout << "Tunnus ja/tai salasana meni väärin!";
		}
	}
}

Viritys voisi näyttää suunnilleen tältä. Kuitenkin, tuossa on korjattavaa, kuten tuo kirjoita-kohta, koska se kysyy viestiä vain kerran.

Tuolta voisi näyttää client-ohjelma.

Miten tekisit itse ohjelman? Millaisen siitä tekisit? Voisitko antaa jonkin esimerkin, mikä lähettää jotain serverille ja tulostaa viestin vaikka funktiolla lue_viestit ihan esimerkin ja soveltamisen vuoksi, koska en saanut noita muita toimimaan.

Legu [04.10.2009 19:16:21]

#

Macro kirjoitti:

if(tunnus == "Macro" & salasana == "salainenkala" || tunnus == "Kala" & salasana == "salainenfisu") {

Tuossa on ainakin selkeä virhe, &:n tilalla pitäisi olla &&.

Mitä se tekee / ei tee? Mitä noissa funktioissa on? Entä serveri, toimiiko se (esim. Windowssissa telnetillä voit kokeilla)?

Macro [04.10.2009 19:18:18]

#

Legu kirjoitti:

Macro kirjoitti:

if(tunnus == "Macro" & salasana == "salainenkala" || tunnus == "Kala" & salasana == "salainenfisu") {

Tuossa on ainakin selkeä virhe, &:n tilalla pitäisi olla &&.

Logiikkavirhe, tietenkin noin =)

Legu kirjoitti:

Mitä se tekee / ei tee? Mitä noissa funktioissa on? Entä serveri, toimiiko se (esim. Windowssissa telnetillä voit kokeilla)?

Ei mitään, tarkoitin että tuolla rakenne-tyylillä voisi olla se ohjelma.

Metabolix [04.10.2009 19:28:57]

#

Rakenne on ihan fiksusti suunniteltu, keskiosan ympärille vain tarvitaan while. (Tietysti salasana pitää tarkistaa palvelimella, muutenhan sen voi kaivaa tuosta ohjelmasta.)

Tulevan varalle sanon vinkkinä, että lukuisat sisäkkäiset iffit tekevät koodista hankalalukuista. Tuossakin tunnuksen tarkistus on koodin alkupuolella ja virheilmoitus loppupuolella; mietipä vain, jos siinä välissä olisi vaikka 50 riviä koodia! Pulma ratkeaa joko funktioilla ja return-lauseella try-catch-throw-virheenkäsittelyllä:

bool yhdista(...) {
  if (tunnus != "Marco") {
    std::cout << "Väärä tunnus!" << std::endl;
    return false;
  }
  if (salasana != "kissa2") {
    std::cout << "Väärä salasana!" << std::endl;
    return false;
  }
  // ...
}
try {
  if (tunnus != "Marco") {
    throw "Väärä tunnus!";
  }
  if (salasana != "kissa2") {
    throw "Väärä salasana!";
  }
} catch (const char *virhe) {
  std::cout << virhe << std::endl;
}

Kirjoitan jossain vaiheessa pienen esimerkin itse aiheesta.

Legu [04.10.2009 19:35:04]

#

Ei se ihan noin yksinkertaista ole.

Tuon tunnus- ja salasanatarkastuksen pitää olla tietenkin palvelimen päässä. Clientti lähettää tunnuksen ja salasanan(hashin), ja palvelin kertoo että pääseekö sisään vai ei. Sitten toi pitää tehdä kahdessa säikeessä tai jotenkin muuten (esim. sillä curses-kirjastolla) niin, että syötteen luku ei sotke tuota viestien hakua ja tulostusta, ja toisinpäin.

Lähde nyt vaikka siitä, että teet ihan vaan ohjelman, joka lähettää palvelimelle "moi", ja sitten palvelin vastaa "no moi". Sen jälkeen opettelet, miten saat palvelimen toimimaan niin, että se on yhteydessä useampaan kuin yhteen clienttiin. Sitten opettelet sen cursesin käytön. Teet vaikka "chatin", joka ei kuitenkaan toimi socketeilla vaan esim. niin että voi kirjoittaa viestejä, mutta ne eivät lähde minnekkään, vaan tulostuvat pelkästään omalle ruudulle, kuten chatissa.

Siltä pohjalta on sitten hyvä lähteä miettimään, että mitenhän nuo saisi yhdistettyä "oikeaksi" chatiksi.

Macro [05.10.2009 10:01:31]

#

Aika ristiriitaista keskustelua. Toisen mielestä on hyvä, ja toisen mielestä taas ei. Legu on kyllä ihan oikeassa, että salasana- ja tunnustarkastuksen pitää olla palvelimen päässä tietenkin.

Ehkei se niiden tulostelu ja lähettäminen ole vaikeaa, kun tietää miten ne välitetään.

Metabolix kirjoitti:

Kirjoitan jossain vaiheessa pienen esimerkin itse aiheesta.

Kiitoksia.

jalski [05.10.2009 10:58:17]

#

Mielestäni kannattaa ensimmäisessä Client-Server toteutuksessa unohtaa salasanat ja muut tarkastukset.

Toteuta ensiksi vaikka serveri-ohjelma, mikä hyväksyy vain yhden yhteyden kerrallaan, vastaanottaa clientilta yhden viestin, sulkee yhteyden ja näyttää ruudulla osoitteen, mistä viesti on tullut sekä itse viestin. Eli siis saman tyylinen kuin joskus Windows verkkojen mukana tullut viestitysohjelma. Tähän toteutukseen sopiva client-ohjelma lähettää vain viestin ja vastaanottaa kuittauksen.

Kun kerkiän, niin laitan malliksi Xharbourilla toteutetun graafisen esimerkin. Vaikka tuo ei C++ olekaan, niin syntaksi muistuttaa Basic:ia ja pystyt varmasti hahmottamaan ohjelman rakenteen.

Macro [05.10.2009 17:21:36]

#

Kiitos, toivottavasti saan selvää siitä. Odotan kuitenkin, että jos Metabolix onnistuu jossain vaiheessa saamaan sen esimerkin tehtyä, niin uskon siitä olevan enemmän apua.

Eikös muka se vain mene siitä vain, että monta clienttiä yhdistää samaan aikaan? Mikä virhe tapahtuu siinä? Näin kun ajattelen, niin ei siinä mielestäni mitään ongelmaa ole.

Legu [05.10.2009 17:34:30]

#

On siinä sellainen "ongelma", että yleensä tuo sockettien vastaanotto on blockkaava (kuten SDL_netissäkin), joten tämä ei suoraan toimi:

while (serveri_päällä) {
    if (joku_yhdistämässä()) {
        ota_vastaan();
    }
    for (int i = 0; i < yhdistäneitä_clienttejä; ++i) {
        vastaanota(clientit[i], puskuri, puskurin_koko);
        for (int a = 0; a < yhdistäneitä_clienttejä; ++a) {
            if (a == i) continue;
            lähetä(clientit[a], puskuri, puskurin_koko);
        }
    }
}

Tuossa tuo vastaanotto odottaa (toteutuksesta riippuen), kunnes vähintään yksi tavu on vastaanotettu ko. clientiltä. Tällöin serveri menee kokonaan "jumiin", jos esim. joku yhdistää, muttei lähetä mitään dataa. Apuun tulee SDL_netin tapauksessa SDLNet_CheckSockets ja SDLNet_SocketReady, joilla voidaan katsoa, mitkä clienteistä ovat lähettäneet dataa, ja vastaanotetaan vain niistä socketeista, jottei jäädä odottelemaan.

Macro [05.10.2009 18:37:08]

#

Niimpä siinä tosiaan taitaa käydä. Pohdin tuota sitten, kun saan sen toimimaan yhdelle clientille.

jalski [06.10.2009 12:53:00]

#

Innostuin itsekin tekemään yksinkertaista chat-serveriä Infernolle Limbolla. Inferno tarjoaa tiedostoliitynnän tcp/ip-pinoon, joten homma pelaa yksinkertaisilla tiedostonluku -ja kirjoitusoperaatioilla. Socketeista ei tarvitse tietää mitään. Limbolla Säikeet ja niiden synkronointi on helppo ja luonteva toteuttaa kanavilla ja alt-konstruktiolla.

Alapuolella oleva ei ole chat-serveri, vaan yksinkertainen esimerkki tcp/ip:n käytöstä Infernolla.


Yksinkertainen serveri. Haluaa parametrikseen portin mitä kuuntelee.
Kirjoittaa debug-tietoa yhteyksistä graafiseen ikkunaan.

implement Server;

include "sys.m";
        sys: Sys;
        Connection : import Sys;

include "draw.m";
        draw: Draw;
        Screen, Display, Image, Context, Point, Rect: import draw;

include "tk.m";
        tk: Tk;
        Toplevel: import tk;

include "tkclient.m";
        tkclient: Tkclient;
        Resize, Hide, Help : import tkclient;


Server : module
{
        init : fn(ctxt : ref Draw->Context, argv : list of string);
};



msg_cfg := array[] of {
        "frame .msg",
        "scrollbar .msg.scroll -command {.msg.t yview}",
        "text .msg.t -width 15c -height 7c -state disabled -yscrollcommand {.msg.scroll set} -bg white",
        "pack .msg.t -side left -expand 1 -fill both",
        "pack .msg.scroll -side left -fill y",
        "pack .msg -expand 1 -fill both -padx 5 -pady 5",
        "pack propagate . 0",
        "update"
};


ctxt: ref Draw->Context;
main : ref Tk->Toplevel;



# Vastaa C:n main - funktiota
init(xctxt : ref Draw->Context, argv : list of string)
{
	sys = load Sys Sys->PATH;


	if(int (len argv) != 2) {
		sys->fprint(sys->fildes(2), "usage: %s port\n", hd argv);
 		raise "fail:bad usage";
	}

	sys->pctl(Sys->NEWPGRP, nil);

	draw = load Draw Draw->PATH;
	tk = load Tk Tk->PATH;
 	tkclient = load Tkclient Tkclient->PATH;
 	tkclient->init();

	if (xctxt == nil)
		xctxt = tkclient->makedrawcontext();

	ctxt = xctxt;

	param := tl argv;
	port := hd param;

 	(n, conn) := sys->announce("tcp!*!" + port);
 	if (n < 0)
	{
		sys->fprint(sys->fildes(2), "Server: announce failed\n");
		raise "fail:announce failed";
	}

# Oma säie kuuntelemaan yhteydenottoja
	spawn listenthread(conn);

	(t, wmctl) := tkclient->toplevel(ctxt, nil, "Server Debug Window", Tkclient->Appl);
	if(t == nil)
		return;

	main = t;

# Tästä alaspäin alustetaan Tk ja jäädän käsittelemään siihen liittyviä viestejä
	cmd := chan of string;
	tk->namechan(main, cmd, "cmd");

	for (c:=0; c < len msg_cfg; c++)
		tk->cmd(main, msg_cfg[c]);

	tkclient->startinput(main, "ptr" :: "kbd" :: nil);
	tkclient->onscreen(main, nil);

	for(;;) alt {
			s := <-main.ctxt.kbd =>
				tk->keyboard(main, s);
			s := <-t.ctxt.ptr =>
				tk->pointer(main, *s);
			s := <-main.ctxt.ctl or
			s = <-main.wreq or
			s = <-wmctl =>
				tkclient->wmctl(main, s);

		}

}

# kuuntelee yhteydenottoja ja käynnistää
# asiakasta palvelemaan oman säikeensä
listenthread(conn : Connection)
{
	while (1)
	{
		(ok, c) := sys->listen(conn);
		if (ok < 0)
        	{
			sys->fprint(sys->fildes(2), "Server: listen failed\n");
			raise "fail:listen failed";
		}

        	spawn workerthread(c);


	}

}


# Hae osoitteesta pelkkä ip
parseip(address: string): string
{
	(nil, tokens) := sys->tokenize(address, "!");

	ip:= hd tokens;

	return ip;
}


# Palvelee asiakasta
workerthread(conn : Connection)
{
	buf := array [sys->ATOMICIO] of byte;

	rdfd := sys->open(conn.dir + "/data", Sys->OREAD);
	wdfd := sys->open(conn.dir + "/data", Sys->OWRITE);
	rfd := sys->open(conn.dir + "/remote", Sys->OREAD);

	n := sys->read(rfd, buf, len buf);
	remote:= parseip(string buf[:n-1]);

	msg : string;

	msg = "[" + remote + "]: Connection established\n" ;
	tk->cmd(main, ".msg.t insert end '" + msg );
	tk->cmd(main, "update");

	while( (n=sys->read(rdfd, buf, len buf ) ) >= 0  )
	{
		msg = "[" + remote + "]: Send " +  string n + " bytes : " + string buf[0:n] + "\n" ;
		tk->cmd(main, ".msg.t insert end '" + msg );
		tk->cmd(main, "update");


		sys->write(wdfd, array of byte ("Hello from server!\n"),
			len ("Hello from server!\n" ) );
	}

	msg = "[" + remote + "]: Connection Closed\n" ;
	tk->cmd(main, ".msg.t insert end '" + msg );
	tk->cmd(main, "update");

}

Yksinkertainen client edelliselle.
Käyttö: client osoite!portti 'viesti'

implement Client;

include "sys.m";
	sys: Sys;
	Connection : import Sys;
include "draw.m";


Client : module
{
	init : fn(nil : ref Draw->Context, argv : list of string);
};


init(nil : ref Draw->Context, argv : list of string)
{
	sys = load Sys Sys->PATH;

	if(int (len argv) != 3)	{
		sys->fprint(sys->fildes(2), "usage: %s address 'message'\n", hd argv);
		raise "fail:bad usage";
	}

	param := tl argv;
	address := "tcp!" + hd param;
	param = tl param;
	message := hd param;

	(n, conn) := sys->dial(address, "");
	if (n < 0) {
		sys->fprint(sys->fildes(2), "Client: connection refused\n");
		raise "fail:connection refused";
	}

	req := array of byte message;

	sys->write(conn.dfd, req, len req);

	buf := array [sys->ATOMICIO] of byte;

	n = sys->read(conn.dfd, buf, len buf);

	sys->print("\nReply from server: %s\n", string buf[:n]);

}

Macro [07.10.2009 14:42:42]

#

Hauska, että itsekkin teit tälläisen. Kuitenkaan en paljon mitään tuosta ymmärtänyt. Jotkin jutut, kuten if ja print ovat helppo ymmärtää, mutta noita muita pitää hetki ajatella, ennen kuin tietää edes arvailla.

Olen tässä koittanut pähkinää särkeä opettelemalla tämän asian, mutta se on kova pähkinä. Jos Metabolix jossain vaiheessa laittaa sen esimerkin, kuten aikaisemmin mainitsin, niin se olisi varmaan aika hyvä särkemään sen pähkinän =)

jalski [07.10.2009 22:14:10]

#

Sain eilen aloittamani chat-serverin perustoiminnot toteutettua Infernolle Limbolla. Onko täällä kukaan tehnyt omaa chat-serveriä? Minkälaiseen ohjelman rakenteeseen päädyit?

Oma toteutukseni koostuu tällä hetkellä useammasta säikeestä ja hakee vielä lopullista muotoaan:

Pääsäie:
Hoitaa nyt lähinnä debug viesti-ikkunan käsittelyn.

Kuuntelusäie : Kuuntelee yhteydenottoja ja käynnistää jokaista asiakasta palvelemaan oman säikeensä.

Työläissäie: palvelee asiakasta.

Hallintasäie:
Pitää kirjaa asiakkaista, ilmoittaa tapahtumista ja muutoksista asiakkaille, hoitaa viestinvälityksen asiakkaille. Toteutettu siten, että odottaa komentokanavaan tulevia ohjauviestejä ja toimii niiden mukaan. Komentoviesti koostuu: komennosta esim. "new", viestistä, clientin id:sta ja yhteyden tiedoista.

Ajatuksia ja ideoita saapi esittää.

Macro [08.10.2009 16:14:43]

#

Tähän samaan chat-juttuun liittyen, että miten saan ns. selkokielisen ajan tietoon?

int main() {
    time_t rawtime;
    struct tm * timeinfo;

    time(&rawtime);
    timeinfo = localtime(&rawtime);
    string timee = asctime(timeinfo);
}

Tämä antaa aikas sekavan tuloksen, kun pitäisi saada tietoon esimerkiksi 12:45:12 tai 8.10.2009 12:45:12.

Muuten, miten pystyisin tekemään laskurin, mikä laskisi että kauan on johonkin tiettyyn hetkeen?
Sen pitäisi palauttaa aika tyyliin 2 päivää, 23 tuntia, 4 minuuttia ja 6 sekuntia.

Macro
Ps. Metabolixille, että oletko jo tehnyt sen esimerkin? Kiinnostaisi katsoa.

Legu [08.10.2009 16:20:13]

#

Macro kirjoitti:

Tähän samaan chat-juttuun liittyen, että miten saan ns. selkokielisen ajan tietoon?

strftime on yksi vaihtoehto, käyttöesimerkki sivulla.

Macro [08.10.2009 16:40:47]

#

Kiitos. =) Jos tiedetään aika, vaikka 26.10.2009 10:00 ja tämän hetkinen aika, niin miten pystyn laskemaan, että kuinka kauan on kyseiseen hetkeen?

PHP:llä osaisin tehdä, mutta tarvitsen tästä ohjelman koneelleni.

jalski [09.10.2009 22:19:21]

#

Alla esimerkkinä Infernolle Limbolla toteutettu chat-serverin runko. Ohjelma on hyvin pelkistetty ja näyttää vain miten palvella useampaa asiakasta.

Testauksen voi hoitaa vaikka avaamalla telnetin useampaan ikkunaan. Huomasin muuten, että ääkköset eivät toimi windowsin telnet-yhteyden kautta. Sopii korjata ja kertoa, mikä mätti...

implement Server;

include "sys.m";
        sys: Sys;
        Connection : import Sys;

include "draw.m";
        draw: Draw;
        Screen, Display, Image, Context, Point, Rect: import draw;

include "tk.m";
        tk: Tk;
        Toplevel: import tk;

include "tkclient.m";
        tkclient: Tkclient;
        Resize, Hide, Help : import tkclient;


Server : module
{
        init : fn(ctxt : ref Draw->Context, argv : list of string);
};



msg_cfg := array[] of {
        "frame .msg",
        "scrollbar .msg.scroll -command {.msg.t yview}",
        "text .msg.t -width 15c -height 7c -state disabled -yscrollcommand {.msg.scroll set} -bg white",
        "pack .msg.t -side left -expand 1 -fill both",
        "pack .msg.scroll -side left -fill y",
        "pack .msg -expand 1 -fill both -padx 5 -pady 5",
        "pack propagate . 0",
        "update"
};


Client : adt {
   id : string;
   connection : Connection;
};


Message : adt {
   command : string;
   msg : string;
   client : Client;
};


commandch : chan of Message;


ctxt: ref Draw->Context;
main : ref Tk->Toplevel;




init(xctxt : ref Draw->Context, argv : list of string)
{
   sys = load Sys Sys->PATH;


   if(int (len argv) != 2) {
      sys->fprint(sys->fildes(2), "usage: %s port\n", hd argv);
       raise "fail:bad usage";
   }

   sys->pctl(Sys->NEWPGRP, nil);

   draw = load Draw Draw->PATH;
   tk = load Tk Tk->PATH;
    tkclient = load Tkclient Tkclient->PATH;
    tkclient->init();

   if (xctxt == nil)
      xctxt = tkclient->makedrawcontext();

   ctxt = xctxt;

   param := tl argv;
   port := hd param;

   (n, conn) := sys->announce("tcp!*!" + port);
   if (n < 0)
   {
      sys->fprint(sys->fildes(2), "Server: announce failed\n");
      raise "fail:announce failed";
   }

   spawn listenthread(conn);

   commandch = chan of Message;
   spawn managerthread();

   (t, wmctl) := tkclient->toplevel(ctxt, nil, "Server Debug Window", Tkclient->Appl);
   if(t == nil)
   {
      return;
   }

   main = t;

   cmd := chan of string;
   tk->namechan(main, cmd, "cmd");

   for (c:=0; c < len msg_cfg; c++)
      tk->cmd(main, msg_cfg[c]);

   tkclient->startinput(main, "ptr" :: "kbd" :: nil);
   tkclient->onscreen(main, nil);

   for(;;) alt {
         s := <-main.ctxt.kbd =>
            tk->keyboard(main, s);
         s := <-t.ctxt.ptr =>
            tk->pointer(main, *s);
         s := <-main.ctxt.ctl or
         s = <-main.wreq or
         s = <-wmctl =>
            tkclient->wmctl(main, s);

      }

}



listenthread(conn : Connection)
{
   while (1)
   {

      (ok, c) := sys->listen(conn);
      if (ok < 0)
      {
         sys->fprint(sys->fildes(2), "Server: listen failed\n");
         raise "fail:listen failed";
      }

      spawn workerthread(c);


   }

}





parseip(address: string): string
{
   (nil, tokens) := sys->tokenize(address, "!");

   ip:= hd tokens;

   return ip;
}




workerthread(conn : Connection)
{
   buf := array [sys->ATOMICIO] of byte;

   rdfd := sys->open(conn.dir + "/data", Sys->OREAD);
   rfd := sys->open(conn.dir + "/remote", Sys->OREAD);

   n := sys->read(rfd, buf, len buf);
   remote:= parseip(string buf[:n-1]);

   commandch <- = ("new", "", (remote, conn));

   output := "";

   while( (n=sys->read(rdfd, buf, len buf ) ) > 0 )
   {
      output += string buf[:n];

      if(len output >= 2)
      {
        if(output[len output - 2:] == "\r\n")
         {
            commandch <- = ("broadcast", output, (remote, conn));
            output = "";
         }

      }

   }

   commandch <- = ("bye", "", (remote, conn));

}



search(client : Client, clients : array of Client) : (int, string)
{
   count := len clients;

   if(count < 1)
   {
      return (-1, "zero size");
   }

   for(i := 0; i < count; i++)
   {
      if (clients[i].connection.dir == client.connection.dir)
      {
         return (i, "");
      }
   }

   return (-1, "not found");
}



managerthread()
{
   clients : array of Client;

   while (1)
   {
      msg := <- commandch;

      case msg.command  {
         "new" =>
            message := "[" + msg.client.id + "]: ESTABLISHED CONNECTION" ;
            tk->cmd(main, ".msg.t insert end '" + message + "\n" );
            tk->cmd(main, "update");

            i := len clients;
            tempclients := array[i + 1] of Client;
            (tempclients[0:], tempclients[i], tempclients[i+1:])  =  (clients[0:i], msg.client, clients[i:]);
            clients = tempclients;

            if(len clients > 0)
            {
               message += "\r\n";

               for( i := 0; i < len clients; i++ )
               {
                  wdfd := sys->open(clients[i].connection.dir + "/data", Sys->OWRITE);
                  sys->write(wdfd, array of byte message, len array of byte message);
               }
            }

         "bye" =>
            message := "[" + msg.client.id + "]: CLOSED CONNECTION" ;
            tk->cmd(main, ".msg.t insert end '" + message + "\n");
            tk->cmd(main, "update");

            (index, err) := search(msg.client, clients);
            if (err != "")
            {
               sys->fprint(sys->fildes(2), "Server: manager error\n");
               raise "fail:manager error";
            }

            clients[index:] = clients[index + 1:];
            clients = clients[0:len clients -1];

            if(len clients > 0 )
            {
               message += "\r\n";

               for( i := 0; i < len clients; i++ )
               {
                  wdfd := sys->open(clients[i].connection.dir + "/data", Sys->OWRITE);
                  sys->write(wdfd, array of byte message, len array of byte message);
               }
            }


         "broadcast" =>
            message := "[" + msg.client.id + "]: " + "BROADCAST\n";
            tk->cmd(main, ".msg.t insert end '" + message );
            tk->cmd(main, "update");

            if(len clients > 0)
            {
               message := "[" + msg.client.id + "]: " + msg.msg;

               for( i := 0; i < len clients; i++ )
               {
                  wdfd := sys->open(clients[i].connection.dir + "/data", Sys->OWRITE);
                  sys->write(wdfd, array of byte message, len array of byte message);
               }
            }

         * =>

      }

   }

}

Macro [14.10.2009 17:08:15]

#

Hauska, että sinäkin jaksat tehdä näitä, mutta eikös ole hieman väärä aihe?

- Macro
Odotellessa Metabolixin vinkkiä...


Sivun alkuun

Vastaus

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

Tietoa sivustosta