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.
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.
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? =)
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.
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?
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).
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.
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?
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.
Siis sitä ei voi käyttää graafiseen ohjelmointiin? Opas kertoo kyllä toista, että sillä voi tehdä tekstigraafisia ohjelmia.
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.)
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ä...
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.
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.
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)?
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.
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.
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.
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.
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.
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.
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.
Niimpä siinä tosiaan taitaa käydä. Pohdin tuota sitten, kun saan sen toimimaan yhdelle clientille.
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]); }
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 =)
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ää.
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.
Macro kirjoitti:
Tähän samaan chat-juttuun liittyen, että miten saan ns. selkokielisen ajan tietoon?
strftime on yksi vaihtoehto, käyttöesimerkki sivulla.
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.
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); } } * => } } }
Hauska, että sinäkin jaksat tehdä näitä, mutta eikös ole hieman väärä aihe?
- Macro
Odotellessa Metabolixin vinkkiä...
Aihe on jo aika vanha, joten et voi enää vastata siihen.