Verkko - Pääteohjaus 7
Tutustutaan hieman verkko-ohjelmointiin ja toteutetaan yksinkertainen asiakas ja palvelin eli WWW-selain ja WWW-palvelin. Lisäksi tehdään pieni Chat-palvelin.
WWW-selain
- Tehdään hyvin yksinkertainen prototyyppi WWW-selaimesta. HTTP-protokollan tuntemus voi olla eduksi.
- Aloita uusi WPF-projekti. Lisää lomakkeelle textbox www-palvelimen nimeä varten, hakupainike ja listbox johon haetaan WWW-palvelimelta saatavat tiedot.
- Lisää seuraavat kirjastot käyttöösi:
using System.Net; using System.Net.Sockets; using System.Net.Mime; using System.Threading; using System.Windows.Threading; using System.IO;
-
Ensimmäiseksi pitää avata yhteys www-palvelimeen. Aseta painikkeesta tapahtumaan jotain seuraavanlaista:
try { // huomaa, että textboxissa saa lukea vain palvelimen osoite eli esim. www.google.com eikä mitään muuta palvelin = textBoxPalvelin.Text; // palvelin on luokan attribuutti string palvelin TcpClient client = new TcpClient(); // avataan yhteys palvelimen porttiin 80 // asynkronisesti eri säikeeseen (thread), joka kutsuu httpCallBack-metodia kunhan yhteys aukeaa // viimeinen parametri saa olla mitä tahansa client.BeginConnect(palvelin, 80, new AsyncCallback(httpCallBack), client); } catch (Exception err) { MessageBox.Show(err.Message); } - Kirjoita myös httpCallBack-metodi:
public void httpCallBack(IAsyncResult Ar) { MessageBox.Show("yhteys on avattu"); } - Kokeile ohjelmaa. Kokeile mitä tapahtuu jos yrität messageboxin sijaan kirjoittaa "yhteys on avattu" lomakkeella olevaan listboxiin. Miten käy?
- Joudut kirjoittamaan seuraavan apumetodin listboxin päivittämistä
varten:
// päivittää käyttöliittymässä olevaa listboxia muistakin // säikeistä kuin käyttöliittymäsäikeesta void addToListBox(ListBox b, string e) { this.Dispatcher.Invoke(DispatcherPriority.Normal, (Action)(() => { b.Items.Add(e); })); } - Kokeile nyt korvata messagebox seuraavantyyppisellä kutsulla:
addToListBox(listBoxTulos, "yhteys on avattu");
- Yhteys on auki mutta sillä ei vielä tehdä mitään.
Lisää httpCallBackiin seuraavat:
TcpClient client = (TcpClient)Ar.AsyncState; // luodaan stream josta voidaan lukea ja johon voidaan kirjoittaa NetworkStream ns = client.GetStream(); StreamReader sr = new StreamReader(ns); StreamWriter sw = new StreamWriter(ns); // http-protokolla edellyttää seuraavia. Pyydetään // palvelimen juuressa olevan index.html-tiedoston otsaketietoja sw.WriteLine("HEAD /index.html HTTP/1.1"); // pitää kertoa myös miltä palvelimelta näitä kysytään koska // samassa ip-osoitteessa voi sijaita useita palvelimia sw.WriteLine("Host: " + palvelin); // http-protokolla edellyttää lopuksi tyhjää riviä sw.WriteLine(""); sw.Flush(); // luetaan palvelimen lähettämää dataa niin kauan kuin sitä tulee while (sr.Peek() != -1) { string data = sr.ReadLine(); addToListBox(listBoxTulos, data); // annetaan toiminta-aikaa myös muille säikeille Thread.Sleep(100); } sr.Close(); sw.Close(); ns.Close(); client.Close(); } - Kokeile miten ohjelma toimii nyt. Jos kaikki meni oikein niin WWW-palvelin palauttaa sivun otsikkotietoja ja saat ne näkyviin listboxin sisään.
- Voit kokeilla miten sama toimisi command promptin kautta telnet-ohjelmalla niin saat paremmin hahmotettua http-protokollan toimintaa. kirjoita command promptiin telnet www.google.com ja sen jälkeen samat asiat mitä ohjelmasi.
- Kokeile mitä tapahtuu jos muutat HEAD-sanan tilalle GET.
- Mahdollista minkä tahansa sivun hakeminen. Salli textboxiin kokonaisen www-sivun osoitteen kirjoittaminen ja parsi siitä erikseen palvelimen osoite ja varsinaisen sivun polku. Korvaa /index.html haluamasi sivun polulla.
- Nyt osaat kirjoittaa yksinkertaisen asiakas-ohjelman, joka ymmärtää tekstimuotoista protokollaa. Samaan tapaan onnistuisi moni muukin kuin http-protokolla. Olennaista on tietää käytettävän protokollan muoto eli siirtyykö data määrämittaisina paloina vai erotinmerkein rajattuna. Esim. http-protokollassa rivinvaihto toimii erotinmerkkinä. Jos käytössä olisi määrämittaisia paloja niin streamista lukeminen pitäisi toteuttaa hieman eri tavalla.
- Kokeile miten vastaava onnistuisi valmiilla webclient-luokalla.
WWW-palvelin
- Aloita uusi WPF-sovellus josta tulee hyvin yksinkertainen WWW-palvelin
- Palvelin ei tarvitse mitään graafista käyttöliittymää. Lisää suoraan
lomakkeen loaded-tapahtumaan seuraava ohjelmakoodi:
private void Window_Loaded(object sender, RoutedEventArgs e) { IPHostEntry address = Dns.GetHostEntry(""); // haetaan tämän koneen osoite. Voitaisiin myös suoraan käyttää osoitetta 127.0.0.1 this.Title = address.HostName; TcpListener server = new TcpListener(address.AddressList[0], 9001); // ei laiteta porttiin 80 mikä olisi oikea www-palvelimen portti server.Start(); server.BeginAcceptTcpClient(new AsyncCallback(serverCallBack), server); } - Lisää myös tarvittava serverCallBack:
public void serverCallBack(IAsyncResult Ar) { TcpListener server = (TcpListener)Ar.AsyncState; TcpClient client = (TcpClient)server.EndAcceptTcpClient(Ar); NetworkStream ns = client.GetStream(); StreamReader sr = new StreamReader(ns); StreamWriter sw = new StreamWriter(ns); // luetaan arraylistiin kaikki rivit mitä selain (asiakas) lähettää ArrayList data = new ArrayList(); while (sr.Peek() != -1) { try { data.Add( sr.ReadLine() ); } catch (IOException) { break; } Thread.Sleep(0); // annetaan muiden mahdollisesti odottavien threadien toimia välillä } // tässä pitäisi tarkistaa, että ensimmäinen rivi on GET jne... ja viimeinen rivi tyhjä rivi (pelkkä rivinvaihto) sw.WriteLine("HTTP/1.x 200 OK"); sw.WriteLine("Content-type: text/html"); // Tämä pitää olla // ohitetaan läjä muita rivejä sw.WriteLine("\n"); // rivinvaihdon jälkeen alkaa HTML tai mitä sitten ikinä palvelimelta pyydettiinkin // tässä pitäisi oikeasti tulostaa pyydetyn tiedoston sisältö sw.WriteLine("<html><head><title>Foobar</title>%lt;/head><body><h1>foobar</h1>/body></html>"); sw.Close(); sr.Close(); ns.Close(); } - Kokeile miten palvelin toimii. Muuta selain-ohjelmaasi siten, että se käyttää samaa porttia mitä palvelin (9001) ja yritä sen jälkeen ottaa yhteys palvelimeesi. Jos kaikki menee hyvin niin saat palvelimelta vastauksen.
- Kokeile ottaa yhteyttä myös vieruskaverisi koneella olevaan palvelimeen
- Huomaa, että palvelimesi osaa nyt käsitellä vain yhden pyynnön. Miten saat palvelimen käsittelemään useita pyyntöjä? vinkki: BeginAcceptTcpClient.
- Voisit nyt parannella palvelinta lisäämällä siihen oikeaa protokollan tunnistamista eli tarkistaa onko ensimmäisen rivin ensimmäinen sana GET jne.
Chat
- Toteuta edellätehtyyn tapaan yksinkertainen Chat-palvelin, jonka kanssa voit "keskustella". Aseta palvelin kaiuttamaan takaisin kaikki teksti mitä lähetät sille. Huomaa, että nyt joudut asettamaan palvelimen ikuiseen looppiin. WWW-palvelin ei pidä yhteyttä auki jatkuvasti mutta tämän palvelimen täytyy pitää.
- Tee myös sopiva asiakasohjelma, joka osaa ottaa yhteyden palvelimeen ja lähettää sille tekstiä, jonka kirjoitat lomakkeelle esim. textboxiin. Huomaa, että myös asiakkaassa joudut käyttämään ikuista silmukkaa, joka vastaanottaa palvelimen viestejä.
- Kokeile chatata vieruskaverisi tekemän palvelimen kanssa.

Käyttäjien kommentit