Kirjautuminen

Haku

Tehtävät

Keskustelu: Ohjelmointikysymykset: C++ File upload

Sivun loppuun

jokupoika [06.06.2009 19:12:15]

#

Moi. Tää liittyy taas tohon mun hikiseen peliin, mutta päätin kuitenkin tehdä erillisen aiheen. Leikitään että mun peli osaa tehdä tiedoston nimeltä hiscore.html. Se pitäis saada uploadattua http palvelimelle. Mulla on oma http palvelin koneella, tehty abyss web serverillä. Löydin tällaisen koodinpätkän netistä (http://www.example-code.com/vcpp/upload_simpleExample.asp):

#include <CkUpload.h>

void ChilkatSample(void)
    {
    CkUpload upload;

    //  Specify the page (ASP, ASP.NET, Perl, Python, Ruby, CGI, etc)
    //  that will process the HTTP Upload.
    upload.put_Hostname("www.chilkatsoft.com");
    upload.put_Path("/receiveUpload.aspx");

    //  Add one or more files to be uploaded.
    upload.AddFileReference("file1","dude.gif");
    upload.AddFileReference("file2","pigs.xml");
    upload.AddFileReference("file3","sample.doc");

    //  Do the upload.  The method returns when the upload
    //  is completed.
    //  This component also includes asynchronous upload capability,
    //  which is demonstrated in another example.
    bool success;
    success = upload.BlockingUpload();
    if (success != true) {
        printf("%s\n",upload.lastErrorText());
    }
    else {
        printf("Files uploaded!\n");
    }

    }

Onnistuin jopa "asentaan" ton chilkatin kirjastot, ja toi funktio menee jo kääntäjästäkin läpi, jee. Mutta en tiedä mitä tohon upload.put_Path("/receiveUpload.aspx"); kohtaan pitäisi laittaa? Pitääkö mun hommata mun serverille joku tollanen receiveUpload.aspx tiedosto joka jotenkin prosessoi noita uploadauksia? Mistähän sellasen saisi ja millanen sen pitäisi olla? Vai pitääkö hankkia joku ilmainen hostaus sivu.
Ja mites salasanojen kanssa, eihän toi kysy sitä missään kohdassa.

Mitä muuta oleellista en oo ottanut huomioon? Kuulostaa jotenkin silti "liian helpolta"

os [07.06.2009 15:44:46]

#

Tuo koodi, jos et sitä ole enempää muokannut, yrittää selvästi lähettää kolmea tiedostoa Chilkatin palvelimelle. Kohta "www.chilkatsoft.com" sinun täytyy siis korvata oman palvelimesi osoitteella. Jos testaat ohjelmaa omalla koneellasi pyörivän HTTP-palvelimen avulla (mikä on ihan hyvä idea), voit kirjoittaa tähän aluksi "localhost".

Saadaksesi homman oikeasti toimimaan sinun täytyy kuitenkin konfiguroida HTTP-palvelimesi käsittelemään noiden tiedostojen vastaanottaminen. Eli se mitä tuon "/receiveUpload.aspx"-kohdan paikalle kirjoitetaan, riippuu täysin siitä, miten olet palvelimesi konfiguroinut. Tuossa esimerkissä siis oletetaan, että palvelimella tosiaan on receiveUpload.aspx-niminen ASP-skripti, jonka palvelin ajaa ja joka käsittelee tuon oman softasi lähettämän HTTP-POST-hakemuksen (joka sisältää nuo tiedostot) haluamallasi tavalla. Suosittelisin (jos et löydä simppelimpää vaihtoehtoa) lähtemään liikkeelle esimerkiksi PHP:llä, jolla tuo pitäisi saada toimimaan ihan hyvin ja netistä pitäisi myös löytyä tähän kohtuullisen hyvää ohjeistusta sekä valmiita skriptejä.

Homma ei ole mitään rakettitiedettä, mutta kannattaa silti (aluksi) tutustua riittävän hyvin Web-palvelimen toimintaan, jotta suurin piirtein tietää, mitä on tekemässä.

jokupoika [07.06.2009 19:36:57]

#

Nonii nyt ollaan jo aika lähellä. Kiitos noista. Asensin Apachen ja PHP:n, ja onnistuneesti jo webformin avulla pystyn uppaan tiedoston palvelimelle.

Ohjelmassakin toimii jotenkuten, ohjelma pyytää palomuurilta lupaa päästä nettiin, ja kun antaa luvan, tulee "Files uploaded!" eli tuo funktio siis onnistuu tehtävässään. Mutta palvelimelle ei kuitenkaan ilmesty mitään.


upload skripti näyttää tältä jos sillä on merkitystä

<?php
$target = "upload/";
$target = $target . basename( $_FILES['uploaded']['name']) ;
$ok=1;
if(move_uploaded_file($_FILES['uploaded']['tmp_name'], $target))
{
echo "Tiedosto ". basename( $_FILES['uploadedfile']['name']). " upattu onnistuneesti";
}
else {
echo "Tapahtui virhe.";
}
?>

os [07.06.2009 21:18:27]

#

Näyttäisi siltä, että PHP-skriptissä ja HTTP-viestissä määritetyt tiedostonnimet eivät vastaa toisiaan. PHP-skriprisi olettaa, että HTTP-viestissä on yksi tiedosto, johon viitataan tässä viestissä nimellä "uploaded" (ja sen jäkeen, virheellisesti, 7. rivillä nimellä "uploadedfile"), vaikka C++-koodissasi viestiin laitetaan kolme tiedostoa, joihin viitataan nimillä "file1", "file2" ja "file3". Homma toimii kuitenkin HTML-formilla, koska siinä lähetettävän tiedoston nimeksi HTTP-viestissä (kohdassa <input name=...) ilmeisesti laitetaan "uploaded".

EDIT: Kannattaa olla hieman varovainen tämän tyyppisten sovellusten kanssa. Tuo skripti on sellaisenaan sen verran vaarallinen, ettei sitä tulkkaavaa webbipalvelinta kannata unohtaa koneelle pyörimään. Eli oikeasti olisi hyvä toteuttaa jonkinlainen autentikaatio ja/tai ainakin lisätä skriptiin jonkinlaisia tarkistuksia liittyen lähetettyjen tiedostojen kokoon ja tyyppiin.

jokupoika [07.06.2009 21:35:52]

#

Kiitos! Tein noi korjaukset ja nyt toimii!

lainaus:

Kannattaa olla hieman varovainen tämän tyyppisten sovellusten kanssa. Tuo skripti on sellaisenaan sen verran vaarallinen, ettei sitä tulkkaavaa webbipalvelinta kannata unohtaa koneelle pyörimään. Eli oikeasti olisi hyvä toteuttaa jonkinlainen autentikaatio ja/tai ainakin lisätä skriptiin jonkinlaisia tarkistuksia liittyen lähetettyjen tiedostojen kokoon ja tyyppiin.

Joo noi on nyt seuraavana todo-listalla. Halusin vaan ensin että se on mahdollisimman yksinkertaisessa muodossa virheiden löytämisen kannalta.

jokupoika [07.06.2009 22:46:17]

#

tossa näytti olevan hieno, mutta ei kuitenkaan toimi. Selaimella toimii.
https://www.ohjelmointiputka.net/koodivinkit/24906-php-turvallinen-ja-monipuolinen-upload

eikö tässä pitäisi upload.AddFileReference("file1","dude.gif"); tuon file1:n tilalle laittaa file? Pitääkö jotain muita muutoksia tehdä? PHP:stä en tajua hölkäsenpöläystä....=)

tommonen myös tulee selaimella
Notice: Undefined index: upload in C:\Program Files\Apache Software Foundation\Apache2.2\htdocs\upload.php on line 141 (tuo on siis tuon skriptin polku)

jokupoika [08.06.2009 01:16:43]

#

lainaus:

Eli oikeasti olisi hyvä toteuttaa jonkinlainen autentikaatio

Miten toi olis mahdollista toteuttaa? Siten että toi C++ ohjelmakin pääsis sitten siitä läpi, tolla chilkatilla.

Grez [08.06.2009 01:39:13]

#

No tuo käyttämäsäi skriptihän ottaa aivan selvästi vastaan yhden tiedoston, jonka kenttänimi on "file". Koska olet laittanut AddFileReference funktiolla kenttänimeksi file1, niin tuo PHP-skripti ei luonnollisesti löydä kenttää file.

jokupoika [08.06.2009 13:32:43]

#

Oon laittanut kenttänimeksi file (sori jos sanoin epäselvästi mut tarkoitin sitä kun sanoin näin):

lainaus:

eikö tässä pitäisi upload.AddFileReference("file1","dude.gif"); tuon file1:n tilalle laittaa file?

os [08.06.2009 19:17:47]

#

Tuo täältä löytynyt skripti olettaa, että liität POST-hakemukseesi kentän nimeltä "upload", jonka arvon pitäisi olla uudehko UNIX-aikaleima. Sellainen on tuon skriptin luomassa HTML-formissa ja Chilkatin kirjaston avulla voit ehkä periaatteessa lisätä sellaisen myös C++-ohjelmaasi.

Mieluummin kannattaisi mielestäni kuitenkin vaihtaa skriptiä tai muokata mainittu ominaisuus skriptistä pois, koska siitä ei ole sinun (C++-toteutuksesi) kannalta erityistä hyötyä. Tuossa skriptissä siis suurin osa riveistä liittyy web-käyttöön (siis HTML-formien kautta), joka on tuon C++-toteutuksesi kannalta tarpeetonta materiaalia.

jokupoika [08.06.2009 19:22:12]

#

Onko ehdottaa hyvää skriptiä? Kokeilin kaikki putkan ja mureakuhan skriptit mutta mikään ei toiminut tän ohjelman kanssa, ainoastaan selaimella. Eli luultavasti sama vika niissäkin.

Tai osaatko kertoa miten tohon alkuperäiseen, "vaaralliseen" skriptiin saisi helposti lisättyä koko- ja tyyppirajoituksen? Täältä sen otin http://php.about.com/od/advancedphp/ss/php_file_upload_2.htm ja siellä on kyllä ohjeetkin miten noi rajoitukset saa, mutta ne pukkaa erroria, ja kuten sanoin en osaa PHP:tä
EDIT: ei se erroria pukkaakkaan vaan noticeja... ja antaa uploadata esim PHP tiedostoja vaikka artikkelissa väitetään että ne on estetty. Tarkoitus olisi että vaikka vain .txt tiedostoja pytyy uppaamaan. Ja pitääkö sellainenkin tarkistus vielä lisätä ettei tiedosto voi olla esim pahaskripti.php.txt?

Entäs jos mun palvelimella ei ole ollenkaan sellaista HTML sivua mistä uppauksia voisi tehdä, niin voiko joku silti pelkän tämän php-skriptin avulla sinne jotain uploadata? Meinaan toi mun peli joka uppauksen suorittaa ei ole yleisessä jakelussa joten jos se olisi ainoa tapa upata niin ei olisi vaaraa hyökkäjistä?

<?php
$target = "upload/";
$target = $target . basename( $_FILES['uploaded']['name']) ;
$ok=1;

//This is our size condition
if ($uploaded_size > 350000)
{
echo "Your file is too large.<br>";
$ok=0;
}

//This is our limit file type condition
if ($uploaded_type =="text/php")
{
echo "No PHP files<br>";
$ok=0;
}

//Here we check that $ok was not set to 0 by an error
if ($ok==0)
{
Echo "Sorry your file was not uploaded";
}

//If everything is ok we try to upload it
else
{
if(move_uploaded_file($_FILES['uploaded']['tmp_name'], $target))
{
echo "The file ". basename( $_FILES['uploaded']['name']). " has been uploaded";
}
else
{
echo "Sorry, there was a problem uploading your file.";
}
}
?>

Tää nyt ei enää oikee tänne alueelle kuulu mutta ei viitsi uuttakaan topiccia alottaa

os [08.06.2009 21:24:35]

#

jokupoika kirjoitti:

Entäs jos mun palvelimella ei ole ollenkaan sellaista HTML sivua mistä uppauksia voisi tehdä, niin voiko joku silti pelkän tämän php-skriptin avulla sinne jotain uploadata? Meinaan toi mun peli joka uppauksen suorittaa ei ole yleisessä jakelussa joten jos se olisi ainoa tapa upata niin ei olisi vaaraa hyökkäjistä?

Vastaus ensimmäiseen kysymykseen on ehdottomasi kyllä. Vaikka koneellasi ei olisi tuota HTML-tiedostoa tai kenelläkään muulla tuota C++-ohjelmaasi, ei tämä estäisi ilkeämielisiä tahoja väärinkäyttämään "palveluasi".

jokupoika kirjoitti:

Tai osaatko kertoa miten tohon alkuperäiseen, "vaaralliseen" skriptiin saisi helposti lisättyä koko- ja tyyppirajoituksen? Täältä sen otin http://php.about.com/od/advancedphp/ss/php_file_upload_2.htm

Vaarallisen skriptin muuttaminen turvalliseksi onkin helpommin sanottu kuin tehty :)
Ilman kunnollista autentikaatiota tuo on todellisuudessa aika näpräilyä, koska kuka tahansa voi periaatteessa edelleen helposti uploadata palvelimellesi mitä tahansa, varsinkin, jos et tarkista tiedostojen sisältöä.

Tuon linkittämäsi sivun ohje on valitettavasti niin täynnä virheitä, että se kannattaa varmaankin unohtaa.

Jos haluat vain korvata tietyn nimisen palvelimella olevan HTML-tiedoston ohjelmasi lähettämällä uudella versiolla ilman autentikaatiota ja edistyneempää HTTP-neuvottelua, voisit käyttää esimerkiksi seuraavanlaista koodia (jota voi edelleen käyännössä myös testata HTML-formilla)

<?php

$polku = "";
$kohdenimi = "viimeisin.html";
$sallitut_tyypit = array( "text/html" );
$suurin_sallittu_koko = 100 * 1024; // 100 kt
$httpnimi = "http-post-nimi";

if (isset($_FILES[$httpnimi]))
{
	//$alkup_nimi = $_FILES[$httpnimi]["name"];
	$koko = $_FILES[$httpnimi]["size"];
	$tyyppi = $_FILES[$httpnimi]["type"];
	$valiaik_nimi = $_FILES[$httpnimi]["tmp_name"];
	$virhekoodi = $_FILES[$httpnimi]["error"];

	$uusi_nimi = $polku . $kohdenimi;

	if ( $virhekoodi <= 0 &&
		 in_array($tyyppi, $sallitut_tyypit) &&
		 $koko <= $suurin_sallittu_koko)
	{
		if ( file_exists($uusi_nimi) )
		{
			unlink($uusi_nimi);
		}

		if ( move_uploaded_file($valiaik_nimi, $uusi_nimi) )
		{
			echo "Lataus valmis";
		}
	}
	else
	{
		echo "Virheellinen tiedosto";
	}
}
else
{
	echo "Oikean niminen tiedosto puuttuu";
}
?>

Joudut varmaankin muuttamaan tästä muuttujien kohdenimi (nimi palvelimella), polku (polku palvelimella) ja httpnimi (tiedoston nimi HTTP-viestissä, C++-koodissasi esim. "file1") arvoja. Jos Chilkat ei tunnista tiedoston MIME-tyyppiä, voit joko yrittää tehdä asialle jotain tai kommentoida tuosta skriptistä pois rivin in_array($tyyppi....

Tällä skriptillä siis palvelimellesi voi edelleen joutua 100kt mitä tahansa kenen tahansa uploadaamaa roskaa nimellä "viimeisin.html" (tämä tiedosto voi virhetilanteessa myös kadota), tai jos olen tehnyt jonkin fataalin virheen, niin vähän paha sanoa, mitä kaikkea voi periaatteessa tapahtua. Sama pätee olennaisesti kaikkiin muihinkin epämääräisiin skripteihin, jota netistä löytyy, joten kannataa tosiaan olla tarkkana.

Oikeastihan nettiin ladattava hiscore-lista kannattaisi toteuttaa siten, että uutta HTML-muotoista tiedostoa ei ladata suoraan palvelimelle, vaan ohjelma lähettää palvelimelle (esim. juuri HTTP-) hakemuksen, jonka palvelimella oleva skripti parsii ja laittaa siinä olevat (uudet) tiedot tietokantaan. Listan katselu tapahtuisi siten, että sivulle surffatessa (toinen) skripti lukee listan tietokannasta ja tulostaa sen halutun laiseksi webbisivuksi (perinteisesti HTML:ksi). Tälläisenkin systeemin toteuttaminen on mahdollista pienehköllä syventymisellä esimerkiksi HTTP:n, PHP:n ja tietokantojen käyttöön.

os [08.06.2009 23:11:10]

#

Sen verran täytyy vielä korjailla omia kommenttejaan, että vaikka yksinkertaisimmin toimivien (netti)sovellusten tekeminen on nykytekniikalla aika helppoa, on aidosti tietoturvallisten ja luotettavien systeemien kehittäminen tosiaan siinä määrin haastavampaa, että "ei mitään rakettitiedettä" alkaa olla tässä yhteydessö aika vähättelevä ellei peräti harhaanjohtava ilmaisu :)

jokupoika [08.06.2009 23:59:13]

#

lainaus:

Oikeastihan nettiin ladattava hiscore-lista kannattaisi toteuttaa siten, että uutta HTML-muotoista tiedostoa ei ladata suoraan palvelimelle, vaan ohjelma lähettää palvelimelle (esim. juuri HTTP-) hakemuksen, jonka palvelimella oleva skripti parsii ja laittaa siinä olevat (uudet) tiedot tietokantaan. Listan katselu tapahtuisi siten, että sivulle surffatessa (toinen) skripti lukee listan tietokannasta ja tulostaa sen halutun laiseksi webbisivuksi (perinteisesti HTML:ksi). Tälläisenkin systeemin toteuttaminen on mahdollista pienehköllä syventymisellä esimerkiksi HTTP:n, PHP:n ja tietokantojen käyttöön.

Jep, en ollut ajatellut noin pitkälle, innoissani vaan halusin ton upload ominaisuuden toimiin. Eihän tossa tosiaan mitään järkeä ole että aina lähetetään uus tiedosto palvelimelle. Toi sun kuvailema tapa kuulostaa niin hyvältä (ja monimutkaiselta) että todennäköisesti saatte vastailla piakkoin taas pöljiin kysymyksiin sitä koskien ;)

Tai sitten laitan download mahdollisuuden tohon peliin ja aina aluksi ladataan palvelimelta se uusin hiscore-tidosto ja sitten muokataan sitä ja lähetetään uudelleen...? Toimisiko...

lainaus:

Sen verran täytyy vielä korjailla omia kommenttejaan, että vaikka yksinkertaisimmin toimivien (netti)sovellusten tekeminen on nykytekniikalla aika helppoa, on aidosti tietoturvallisten ja luotettavien systeemien kehittäminen tosiaan siinä määrin haastavampaa, että "ei mitään rakettitiedettä" alkaa olla tässä yhteydessö aika vähättelevä ellei peräti harhaanjohtava ilmaisu :)

Mitä pahimmassa tapauksessa joku voi sitten saada aikaan jos jonkun skriptinsä saa mulle uploadattua? Pystyykö sillä sitten tekemään ihan mitä tahansa mun koneella? Ja onkohan mahdollista määritellä jotenkin kaikki uudet tiedostot siten että niiden ajaminen ei olisi mahdollista, jostain suojaus asetuksista esim? Ainoastaanhan tuon upload.php:n tulisi oltava suoritettava? Jos jonkun hiscore.txt avaa palvelimella niin eihän se ole "suorittamista" vaan "lukemista"?

TeNDoLLA [09.06.2009 00:30:25]

#

Jos php:ta käytät muutenkin, niin php:llä voi vaihtaa tiedoston oikeudet miksi haluaa. Eli uploadin jälkeen annat tiedostolle semmoiset oikeudet ettei sitä voi ajaa. https://www.php.net/manual/en/function.chmod.php

jalski [09.06.2009 14:47:16]

#

Yksi ehkä yksinkertaisempi vaihtoehto, jos ajat palvelinta omalla koneellasi voisi olla tutustuminen socketteihin ja oman serveri-ohjelman toteutus. Serveri odottelee yhteydenottoja asiakasohjelmalta (=peliltä), jonka jälkeen asiakasohjelma lähettää tulostiedot jossain yksinkertaisessa muodossa. Serveri käy tulostiedon läpi, muodostaa ja tallentaa html-tiedoston.

Jos haluat vain yksinkertaisesti saada hi-scoret nettiin näkyviin, niin voit vaikka laittaa serveri-ohjelman kuuntelemaan http-pyyntöjä ja laittaa vastaamaan html-muotoisella tulostiedolla.

jokupoika [09.06.2009 15:39:36]

#

lainaus:

Yksi ehkä yksinkertaisempi vaihtoehto

No ei aikanaan kuulosta yhtään yksinkertaisemmalta =)
Socketteja kattelin kun tota upload ominaisuutta lähdin miettimään. Meni vähän yli hilseen.

jokupoika [09.06.2009 18:06:05]

#

Käytän nyt tuota os:n kirjoittamaa skriptiä. Hyvin tuntuu toimivan. Lisäsin ton echo "Lataus valmis" -rivin jälkeen tämmöisen chmod($kohdenimi, 0644);
Onkohan tolla mitään virkaa loppujen lopuks, ja onko se edes oikein kirjoitettu?

os [09.06.2009 18:46:46]

#

jokupoika kirjoitti:

Mitä pahimmassa tapauksessa joku voi sitten saada aikaan jos jonkun skriptinsä saa mulle uploadattua? Pystyykö sillä sitten tekemään ihan mitä tahansa mun koneella?

Pahin mahdollinen tapaus on tosiaan se, että hyökkääjä saa koneesi kokonaan hallintaansa ja voi siis tehdä sillä aivan mitä tahansa. Tätä lievemmät ja helpommin sattuvat tapaukset, kuten esimerkiksi se, että joku ajaa tietokoneellasi rajatuin oikeuksin omaa skriptiään tai lataa sinne omia tiedostojaan, voivat myös olla hyvin ikäviä. Kauhuskenaarioita voi lueskella netistä vaikkapa hakusanalla kyberrikollisuus. Syynä tietoturvan pettämiseen voi siis periaatteessa olla lähes mikä tahansa systeemin osista, esimerkiksi skripti, webbipalvelin (erityisesti jos se on konffittu huonosti) tai vaikka käyttöjärjestelmä.

Riski muiden kuin tyypillisimpien automatisoitujen hyökkäysten kohteeksi joutumiselle on käytännössä pienissä palveluissa melko pieni, mutta kannataa silti olla tarkkana.

EDIT:

jokupoika kirjoitti:

lainaus:

Yksi ehkä yksinkertaisempi vaihtoehto

No ei aikanaan kuulosta yhtään yksinkertaisemmalta =)
Socketteja kattelin kun tota upload ominaisuutta lähdin miettimään. Meni vähän yli hilseen.

No yksinkertaisempi siinä mielessä, että joudut koodaamaan HTTP-palvelimen toiminnallisuuden itse, jolloin siinä ei ole niin paljon ylimääräistä kuin Apachessa :)

No ei, tietojen päivityksen nettiin saa kyllä tehtyä hyvin kevyesti sockettien avulla, mutta jos tosiaan haluaa webbikäyttöliittymän ja tietokannat, niin kannattaa mieluummin käyttää webbipalvelinta, HTTP:tä ja skriptejä, koska niillä serveripää pelaa helposti ja saumattomasti yhteen, ja kaikki webbipalvelimen hienoudet ovat suoraan hyödynnettävissä.

Jos tosiaan aiot tehdä tällaisen systeemin, niin kannattaa suosiolla opetella (vähän) webbiohjelmointia, koska täysin valmiita skriptejä et varmaankaan tule löytämään. Vaihtoehtoisia tekniikoita on lukuisia. Yksi perinteinen ja suosittu valinta on PHP & MySQL, joiden käyttöön löytyy opas myös täältä. HTTP:n toimintaan joudut tutustumaan vielä hieman erikseen.

jalski [10.06.2009 09:53:47]

#

Ajattelin lähinnä harjoituksena jotain allaolevan tapaista C++:lla ja socketeilla toteutettuna.

Oma helppo ja "vähän" oikaiseva toteutukseni on tehty Infernon Limbolla. Inferno tarjoaa tiedosto liittymän resursseihin, kuten tcp/ip-pinoon. Yhteydenmuodostus ja liikennöinti tapahtuu yksinkertaisilla luku -ja kirjoitusoperaatioilla. Koodi on aika helppolukuista ja täällä varmaan moni pystyy kirjoittamaan parannellun C++ käännöksen, mikä käyttää socketteja yhteyksiin.

Inferno: http://www.vitanuova.com/inferno/index.html

Serveri-ohjelma ottaa parametrikseen portin numeron (web = 80) ja jää kuuntelemaan yhteydenottoja. Mikäli serverille lähettää viestin, mikä alkaa "MSG " ja jatkuu omavalintaisella viestillä, niin voi vaihtaa serveri-ohjelman oletusviestiä. Muissa tapauksissa serveri palauttaa html-muodossa viestin asiakasohjelmalle.

Ohjelma koostuu kolmesta säikeestä + yksi säie per asiakas:

pää-säie:
debuggaus-tiedon ikkunankäsittely, käynnistää päivitys-säikeen ja kuuntelu-säikeen.

päivitys-säie:
kanavan avulla pitää huolen, että jos serveri palvelee useampaa asiakasta niin vain yksi säie kerrallaan voi muuttaa viestiä.

kuuntelu-säie:
odottelee yhteydenottoja ja käynnistää jokaista asiakasta palvelemaan oman uuden säikeen.

Serveri:

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;

updatechan : chan of string;

dmsg := "Hello!";



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;

	# First, announce the service. This creates a line directory
	# and conn.cfd will be open on the ctl file
	(n, conn) := sys->announce("tcp!*!" + port);
	if (n < 0)
	{
		sys->fprint(sys->fildes(2), "Server: announce failed\n");
		raise "fail:announce failed";
	}

	updatechan = chan of string;

	spawn updatethread();
	spawn listenthread(conn);

	(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);

	}


}


updatethread()
{

	while(1)
	{
		dmsg = <-updatechan;
	}
}



listenthread(conn : Connection)
{
	# Listen for incoming connections, spawn new thread
	# for each incoming connection.
	while (1)
	{
		listen(conn);
	}
}



listen(conn : Connection)
{

	(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;

	# The connections data file is not opened by default,
	#  must explicitly do so to accept the new connection
	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;

	n = sys->read(rdfd, buf, len buf);


	if(string buf[0:4] == "MSG ")
        		updatechan <-= string buf[4:n];

		sys->write(wdfd, array of byte ("HTTP/1.1 200 OK\nConnection: close\n\n" + "<HTML><BODY>" + dmsg + "</BODY></HTML>\n" ),
                               len ("HTTP/1.1 200 OK\nConnection: close\n\n" + "<HTML><BODY>" + dmsg + "</BODY></HTML>\n" ));

	msg = remote + ":\n" + string buf[:n];

	tk->cmd(main, ".msg.t insert end '"+msg + "\n\n");
	tk->cmd(main, "update");

}

Client, millä voi muuttaa serverin palauttamaa viestiä:
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 := "MSG " + 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("\n%s\n", string buf[:n]);

}

jalski [10.06.2009 20:15:44]

#

Tein pari muutosta tuohon Infernolle Limbolla kirjoitettuun serveri-esimerkkiini. Poistin päivitys-säikeen ja korvasin sen monitori-säikeellä, mikä erillisen kirjoitus -ja lukukanavan avulla varmistaa, että vain yksi säie (asiakas) pääsee kerralla lukemaan viestimerkkijonoa tai kirjoittamaan siihen.

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;

writechan : chan of string;
readchan : chan of string;

dmsg : string;




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;

	# First, announce the service. This creates a line directory
	# and conn.cfd will be open on the ctl file
	(n, conn) := sys->announce("tcp!*!" + port);
	if (n < 0)
	{
		sys->fprint(sys->fildes(2), "Server: announce failed\n");
		raise "fail:announce failed";
	}

	dmsg = "Hello!";

	writechan = chan of string;
	readchan = chan of string;

	spawn monitorthread();
	spawn listenthread(conn);

	(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);

	}


}



monitorthread()
{

	while(1)
	{
		alt {
			readchan  <- = dmsg => ;

			dmsg = <- writechan => ;
		}
	}
}



listenthread(conn : Connection)
{
	# Listen for incoming connections, spawn new thread
	# for each incoming connection.
	while (1)
	{
		listen(conn);
	}
}



listen(conn : Connection)
{

	(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;

	# The connections data file is not opened by default,
	#  must explicitly do so to accept the new connection
	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;
	debugmsg : string;

	n = sys->read(rdfd, buf, len buf);


	if(string buf[0:4] == "MSG ")
        		writechan <- = string buf[4:n];

	msg = <- readchan;

	sys->write(wdfd, array of byte ("HTTP/1.1 200 OK\r\nConnection: close\r\n\n" + "<HTML><BODY>" + msg + "</BODY></HTML>\r\n" ),
                               len ("HTTP/1.1 200 OK\r\nConnection: close\r\n\n" + "<HTML><BODY>" + msg + "</BODY></HTML>\r\n" ));

	debugmsg = remote + ":\n" + string buf[:n];

	tk->cmd(main, ".msg.t insert end '"+debugmsg + "\n\n");
	tk->cmd(main, "update");

}

Sivun alkuun

Vastaus

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

Tietoa sivustosta