Internet megosztás Ubuntu-val

Biztos létezik már egy pár HowTo, ami ezzel a kérdéssel foglalkozik, ám nekem több mint egy órámba tellett értelmes megoldást találni, így inkább leírom. A címben nem véletlenül Ubuntu-ra hivatkozok, ugyanis az egyes Linux disztribúciókban némely beállítás különbözhet, annak ellenére, hogy az IP szintű csomagszűrés a kernel része. Nos, aki csak emiatt tévedt ide, nem kertelek tovább íme a beállítás:

Biztos létezik már egy pár HowTo, ami ezzel a kérdéssel foglalkozik, ám nekem több mint egy órámba tellett értelmes megoldást találni, így inkább leírom. A címben nem véletlenül Ubuntu-ra hivatkozok, ugyanis az egyes Linux disztribúciókban némely beállítás különbözhet, annak ellenére, hogy az IP szintű csomagszűrés a kernel része. Nos, aki csak emiatt tévedt ide, nem kertelek tovább íme a beállítás:

Feltételezve, hogy az A számítógépen létezik felépített internet kapcsolat, amit a /dev/eth0 hálókártyán keresztűl szeretnénk megosztani a B számítógép számára. Az A gépen a következő parancsokat kell lefuttatni root-ként:


ifconfig eth0 192.168.1.1 netmask 255.255.255.0 broadcast 192.168.1.255
iptables --flush -t nat
iptables --table nat --append POSTROUTING --out-interface wlan1 -j MASQUERADE
iptables --append FORWARD --in-interface eth0 -j ACCEPT
echo 1 > /proc/sys/net/ipv4/ip_forward

Az első sor az eth0 hálózatát állítja be, az eth0-ra csatlakoztatott gépeknek 192.168.1.* tartományból adunk IP címet, az A gépnek 192.168.1.1-et. Természetesen ha ez a beállítás más eszközzel (pl. GUI-val, KNetworkManager, vagy hasonlóval) történik, akkor ez a sor kihagyhandó. Az ezt követő három sor az IP csomagszűrőt állítja be, hogy az eth0 eszközön érkezett IP csomagokat egyszerűen továbbítsa a wlan1 interfészen, ami jelen esetben az internet kapcsolatot adja. Az iptables részletes működését én itt nem írom le, egyrészt mert túl hosszú lenne, más részt én sem vagyok biztos benne, hogy 100%-ig értem. Az utolsó sor szinte minden linux disztrón más. Ez engedélyezi rendszer szinten az IP csomagok továbbítását.

Ha a szervert beállítottuk, a kliens beállítássa már jóval egyszerűbb. Rendszerfüggetlenül fogalmazva, a szerverrel kompatibilis módon be kell állítani a hálózatot, pl. 192.168.1.2 IP címet adunk neki, majd alapértelmezett átjárónak a szerver címét adjuk meg. Ha a kliens gép is linux, és parancssorból kívánjuk beállítani, akkor a következőt kell tenni:


ifconfig eth0 192.168.1.2 netmask 255.255.255.0 broadcast 192.168.1.255
route add default gw 192.168.1.1 eth0

Linux az asszony gépén, avagy az eddigi legnagyobb próbatétel

Hogyan vegyünk rá valakit, hogy használjon linuxot? Egyszerű. Rakjunk fel a gépére Windows Vista-t.

Még örülni is fog neki, hisz pozitívan áll hozzá a hosszúra nyúlt XP használat után. Pedig nem ez volt a szándékom, amikor felraktam egy Vista-t a menyasszonyomnak. Eleinte még én is meg voltam elégedve vele, bár minden apró beállításhoz többszáz visszakérdezésre kellett gondolkodás nélkül “igen”-t kattintani, de egy egyszerű felhasználónak sokat jelent az a néhány képi hatás (ami mellesleg elbújhat egy beryl mellett, de annak az értelmes beállítása még gyakorlott linuxosoknak sem egyszerű feladat). Persze, ronda inkonzisztenciák vannak a felületben (ahogy azt [[http://cubussapiens.hu/node/563|Stampie cikke]] bemutatta), de ezek a laikus szem számára nem, vagy csak nehezen észrevehetőek. Az átlag felhasználó (értsd: vindóz júzer) alkalmazkodik az adott körülményekhez, mert megszokta, hogy nem lehet azokon változtatni.

Hogyan vegyünk rá valakit, hogy használjon linuxot? Egyszerű. Rakjunk fel a gépére Windows Vista-t.

Még örülni is fog neki, hisz pozitívan áll hozzá a hosszúra nyúlt XP használat után. Pedig nem ez volt a szándékom, amikor felraktam egy Vista-t a menyasszonyomnak. Eleinte még én is meg voltam elégedve vele, bár minden apró beállításhoz többszáz visszakérdezésre kellett gondolkodás nélkül “igen”-t kattintani, de egy egyszerű felhasználónak sokat jelent az a néhány képi hatás (ami mellesleg elbújhat egy beryl mellett, de annak az értelmes beállítása még gyakorlott linuxosoknak sem egyszerű feladat). Persze, ronda inkonzisztenciák vannak a felületben (ahogy azt [[http://cubussapiens.hu/node/563|Stampie cikke]] bemutatta), de ezek a laikus szem számára nem, vagy csak nehezen észrevehetőek. Az átlag felhasználó (értsd: vindóz júzer) alkalmazkodik az adott körülményekhez, mert megszokta, hogy nem lehet azokon változtatni.

Sokáig elégedett voltam vele, hiszen a célközönség (életem értelme) is az volt. A problémák idővel alakultak ki, egyre hosszabb várakozás bekapcsolás után, mire végre hozzányúlhatunk az egérhez, néha (azaz szinte mindig) az MSN Live nevű csoda egy nem túl kedves “nem válaszol” üzenettel szenderült jobb létre, amivel a kedves laikus felhasználó nem tud mit kezdeni. A felháborodása is jogos, hiszen nem az ő dolga működésre bírni a gépet, ő a gép szolgáltatásait akarja használni. Összességében a gép egyre több türelmet ígényelt a felhasználó felöl, amiből természetesen egyre kevesebb lett, és egyszer csak el is fogyott. A Vista-nak mennie kell.

A döntés viszont nehéz. Ugyanis, ha nem Vista, akkor vissza a jól megszokott, a 7 év alatt viszonylag stabillá forott XP-hez. Az XP grafikus felülete azonban már egyszerű és unalmas. Körülbelül annyira, mint 2001-ben a win98 volt. Ne felejtsük el, hogy ez nagyon fontos annak, akinek a számítógép egy fekete doboz (nem rendelkezik logikai modellel a gép belső működéséről). Az egyszerű felhasználó számára a “felhasználói élményt” jelentős részben a látvány határozza meg. Alternatívaként megjelenik még a linux. A félelmetes, ismeretlen linux, ami csak eldugott, ember számára ritkán látott szervereken fut. És persze az én gépemen. A KDE4.1 RC1 felület asztali elemei pedig nagyon látványossá teszik a mindennapi munkát. A fent említett leányzó néhány esetben (bár csak kényszerből) használta a gépemet, így látta, hogy mégsem különbözik annyira a linux a windowstól, mint gondolta. Talán megér egy próbát.

Ahhoz nem volt merszem, hogy fejlesztés alatt álló KDE4-el átkozzam meg, hiszen a felállítandó rendszer legfontosabb követelménye a stabilitás. Így maradt az általam jól ismert Kubuntu KDE3-al, majd ha KDE4 stabilitása a megítélésem szerint eléri a megfelelő szintet, egy kattintással fel tudom neki rakni. Linuxra jellemző, hogy ha egy hardvert azonnal felismer, akkor az tökéletesen működik, de ha bármi probléma van vele, csak rengeteg szenvedés árán, vagy egyáltalán nem lehet értelmes működésre bírni (persze kis idő, és néhány kernel-verzió lépés megoldja). De szerencsém volt, a live CD környezet futtatásával meggyőződtem róla, hogy minden egyes hardverelemet megfelelően kezel a rendszer. A telepítés lefuttatása után leültettem a leendő felhasználót az új szerzemény elé, hogy a segítségemmel megismerkedjen vele. Az első mozdulatok pozitívak voltak, a felület testreszabhatósága valósággal elvarázsolta, négy asztalt hozott létre, mindegyiket más háttérrel, hogy az mindig tükrözze a hangulatát. A “programok telepítése/törlése..” funkció is mosolyt váltott ki, a programok ilyen egyszerű kezelése is kedvére valónak bizonyult. Általában véve a kezdeti félelmeit azonnal elsöpörték a KDE szolgáltatásai.

Az élet persze nem fenékig tejfel, a telepítés után derült ki, hogy a hangkártya mixere nem megy tökéletesen. Ez annyiban nyílvánúl meg, hogy a kmix egyáltalán nem használható, bár a hang megy, a hangerőt csak minden egyes alkalmazásban külön lehet állítgatni, nincs erre globális funkció. Ez volt a kisebbik gond. A nagyobbik az volt, hogy egy kritikus pillanatban a rendszer gondolt egyet, és lefagyott. A kép kimerevedett, és a CAPS-lock LED egyenletes villogása jelezte, hogy a Kernel eltávozott az örök vadászmezőkre. Érezhető volt a csalódottság, és a kétségbeesés a levegőben, ami fokozódott ahogy az eset újra és újra megismétlődött. A rendszer teljesen váratlanul és indeterminisztikusan lefagyott. A dolog érdekessége, hogy a fagyás gyakorisága megritkult azáltal, hogy kilőttem az ALSA hangrendszert (gondolván arra, hogy a fagyás oka az egyetlen hibásan működő hardverelem lehet csak).

A megoldás egy nappal később jött, amikor észrevettem, hogy egy lefagyás után a rendszeróra nullázódott. A gyanút beigazolta, hogy egy másik fagyás utáni induláskor a gép egy pittyegés után kiírta, hogy a CMOS akkumlátor bizony lemerült. Csavarhúzó, aksi ki, boltba el, új aksi be, csavarhúzó, és láss csodát, azóta nem fagy le (lekopogtam). Elképesztő mekkora galibát tud okozni egy ekkora kis bigyó. Persze vannak a gépben sokkal kisebb dolgok is, amik drágább hibát okozhatnak.

Mindenesetre most működik. A menyasszonyom pedig elégedett. Könnyedén, boldogan használja a rendszert, ami reményeink szerint a stabilitását is bizonyítani fogja a továbbiakban.

Java osztályok tárolása MySQL adatbázisban

A cím talán megtévesztő lehet, most nem arról van szó, hogy hogyan lehet szérializált osztálypéldányokat tárolni adatbázisban. Ehelyett magukat a Java osztályokat (típusokat) tárolom ilyen módon. Ez megtehető, hiszen a Java futásidőben tölti be az osztályokat, egészen pontosan az osztály első aktív használata elött. A továbbiakban a cikk feltételezi a Java és a MySQL alapszintű ismeretét, és a [[http://java.sun.com/javase/technologies/database/|JDBC]] használatát se fogom részletezni. Kezdetnek essen pár szó a Java osztálybetöltő mechanizmusáról.

A cím talán megtévesztő lehet, most nem arról van szó, hogy hogyan lehet szérializált osztálypéldányokat tárolni adatbázisban. Ehelyett magukat a Java osztályokat (típusokat) tárolom ilyen módon. Ez megtehető, hiszen a Java futásidőben tölti be az osztályokat, egészen pontosan az osztály első aktív használata elött. A továbbiakban a cikk feltételezi a Java és a MySQL alapszintű ismeretét, és a [[http://java.sun.com/javase/technologies/database/|JDBC]] használatát se fogom részletezni. Kezdetnek essen pár szó a Java osztálybetöltő mechanizmusáról.

A Java ClassLoader osztály használható arra, hogy egy keresett osztályt betöltsön a memóriába. Minden JVM indulásakor létrejön egy alapértelmezett rendszer ClassLoader, és ha nem teszünk semmit ez marad végig az egyetlen ilyen jellegű objektum a JVM-ben. A ClassLoader-ek működésének megértéséhez meg kell ismernünk néhány metódusát:

  • A konstruktor: ClassLoader(ClassLoader parent) : Minden ClassLoader-nek létezik pontosan egy szűlője (kivéve az alapértelmezett ClassLoader-t), ugyanis mielött az adott ClassLoader megpróbálná betölteni a keresett osztályt, megkérdezi a szülőt, hogy ő be tudja-e tölteni, és csak akkor próbálkozik meg, ha a még nincs betöltve, és a szülő sem tudja betölteni.
  • Class loadClass(String name) :ez hívódik meg, amikor felmerül az igény egy osztály betöltésére. Ez a metódus elöször meghívja a szülő ClassLoader hasonló metódusát, és ha az ClassNotFoundException-t dob, akkor meghívja ezen osztály findClass metódusát.
  • Class findClass(String name) :a fentiekből látszik, hogy ez a metódus hívódik meg akkor, ha már ránk hárult az osztály betöltésének a feladata. Ha új ClassLoader-t írunk, rendszerint ezt a függvényt akarjuk felülírni.
  • Class defineClass(String name, byte[] b, int off, int len) :ez a lényeg. Ez a függvény hozza létre az osztályt a memóriában a beolvasott bytecode-ból, aminek meg kell egyeznie egy “.class” fájl tartalmával.

Már látszik a turpisság: a fentiek alapján lehet írni egy ClassLoader-t, ami nem fájlból, hanem adatbázisból olvassa be a megfelelő bytesorozatot. Első lépésként írjuk meg az adatbázis réteget, ami a kérdéses bytecode-ot tudja menteni/kiolvasni az adatbázisból (az adatbázisba a bytecode-ot Base64 kódolásban mentem, amit egy segédosztály végez. Lásd: lent. Ugyanúgy nem térek ki a File beolvasására, annak a megoldásában is a Google segített):


package mysqlclasses;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;

public class ClassSaver {

public static byte[] getBytesFromFile(File file) throws IOException {
.....
}

public static void saveToDB(Connection con, String name, byte[] data){
try{
Statement s = con.createStatement();
s.execute("CREATE TABLE IF NOT EXISTS classes (name VARCHAR(120) NOT NULL,bytecode TEXT)");

String bytecode = new String(Base64Coder.encode(data));

ResultSet rs = s.executeQuery("SELECT * FROM classes WHERE name = '"+name+"'");
if (rs.next()){
//update
s.execute("UPDATE classes SET bytecode = '"+bytecode+"' WHERE name = '"+name+"'");
}else{
//insert
s.execute("INSERT INTO classes SET name = '"+name+"', bytecode = '"+bytecode+"'");
}
}catch(Exception e){
System.err.println(e.getMessage());
}
}

public static byte[] loadFromDB(Connection con, String name){
try{
Statement s = con.createStatement();
ResultSet rs = s.executeQuery("SELECT * FROM classes WHERE name = '"+name+"'");
if (rs.next()){
return Base64Coder.decode(rs.getString("bytecode"));
}else{
return null;
}
}catch(Exception e){
System.err.println(e.getMessage());
return null;
}

}
}

Ezek után következzék a MySQLClassLoader:


package mysqlclasses;

import java.sql.Connection;

/**
* @author balage
*
*/
public class MySQLClassLoader extends ClassLoader {

Connection con;

public MySQLClassLoader(Connection sqlcon) {
con = sqlcon;
}

public MySQLClassLoader(Connection sqlcon,ClassLoader arg0) {
super(arg0);
con = sqlcon;
}

@Override
protected Class< ?> findClass(String name) throws ClassNotFoundException{
System.out.println("Searching for class: "+name);
byte[] data = ClassSaver.loadFromDB(con, name);
if (data != null){
return defineClass(name, data, 0, data.length);
}else{
throw new ClassNotFoundException();
}
}

}

Meglepően egyszerű, nemde? A kipróbálásához létrhozunk pár egyszerű osztályt:

package test;

public interface Test {
public void some();
}


package test;

public class TestA implements Test {

@Override
public void some() {
System.out.println("I'm an A instance.");
}

}


package test;

public class TestB implements Test {

@Override
public void some() {
System.out.println("I'm a B instance.");
}

}

Az egyszerűbb tesztelés érdekében hoztam létre egy Interface-t is, ami fordítás időben ismert, így egyszerűen meghívható a some() metódus. A létrehozott osztályokkat most le kell fordítani, és be lehet írni az adatbázisba:


Class.forName("com.mysql.jdbc.Driver");

Connection con = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "balage", "");

Test a = new TestA();
a.some();
Test b = new TestB();
b.some();

ClassSaver.saveToDB(con, a.getClass().getName(), ClassSaver.getBytesFromFile(new File("bin/test/TestA.class")));
ClassSaver.saveToDB(con, b.getClass().getName(), ClassSaver.getBytesFromFile(new File("bin/test/TestB.class")));

Ezen a ponton érdemes ellenőrizni az adatbázis tartalmát, ahol jól láthatóan szerepel egy “classes” tábla, benne a két osztály bytecode-jával. Ezek után lehet kiovasni onnan őket a következő kóddal (Arra vigyázzunk, hogy a kiolvasandó osztályok NE legyenek elérhetőek a classpath-ban, különben az adatbázis helyett az alapértelmezett ClassLoader fogja őket betölteni!):


Class.forName("com.mysql.jdbc.Driver");

Connection con = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "balage", "");
MySQLClassLoader loader = new MySQLClassLoader(con);

Class< ?> clsa = loader.loadClass("test.TestA");
Test a = (Test)clsa.newInstance();

a.some();

Ha mindent jól csinálunk, a kimeneten a TestA osztály üdvözlőüzenete elött látni fogjuk a MySQLClassLoader-ben elrejtett kiírást is, ami egyértelműen jelzi, hogy valóban ő olvasta be az osztályt. Siker. Mielött még kikiáltanám a cikk végét, had tegyek pár megjegyzést, ami szükséges lehet a módszer komolyabb használatához:

Jól láthatóan szövegként hivatkozunk a betölteni kívánt test.TestA osztályra. Ekkor felmerül a kérdés, mi van azokkal az osztályokkal, amikre a betöltött test.TestA osztály hivatkozik? Például létrehoz egy test.TestB osztályt, ami ugyancsak az adatbázisból érhető el, és nem szerepel a megadott classpath-ban. A válasz egyszerű. Azokat akkor próbálja betölteni, amikor aktívan használni próbáljuk. Akkor az osztály nevét átadja az alapértelmezett ClassLoader-nek, ami megkeresi a classpath-ban és.. ClassNotFoundException. A probléma ott van, hogy arra már nem a mi MySQLClassLoader-ünket használja. A teendő tehát: alapértelmezetté kell tenni a létrehozott MySQLClassLoader-t, amit megtehetünk az aktuális Thread ismeretében:


MySQLClassLoader loader = new MySQLClassLoader(con);
Thread.currentThread().setContextClassLoader(loader);

Ezen sor után a futásidőben felmerült osztályhivatkozásokat már az általunk megadott ClassLoader-rel fogja betölteni. Kiemelném, hogy ekkor is létezik a MySQLClassLoader szűlője, ami prioritást élvez, azaz mindig az próbálja meg betölteni a keresett osztályt elsőként (persze csak akkor, ha nincs még betöltve).

Még egy probléma: az általunk létrehozot ClassLoader-t csak futásidőben tudjuk használni, a javac fordításkor csak a beépített ClassLoader-t tudja használni, azaz fordításkor minden hivatkozott osztálynak kéznél kell lennie, csak fordítás után lehet az egyes osztályokat bepakolni az adatbázisba. Ami után viszont az adatbázisból hivatkozott .class fájlok törölhetőek a classpath-ból. A programunkat indító osztálynak, és a MySQLClassLoader-nek viszont minden esetben elérhetőnek kell lennie a classpath-ban, ugyanis a java indulásakor csak az alapértelmezett ClassLoader áll rendelkezésünkre.

Végezetül pár hasznos link:

  • [[http://java.sun.com/docs/books/tutorial/ext/basics/load.html]] Egy részletesebb leírás a Java ClassLoader-ek működéséről
  • [[http://java.sun.com/j2se/1.4.2/docs/api/java/lang/Class.html]] A Class metaosztály javadoc-ja
  • [[http://java.sun.com/j2se/1.4.2/docs/api/java/lang/ClassLoader.html]] A ClassLoader osztály javadoc-ja
  • [[http://www.source-code.biz/snippets/java/2.htm]] A Base64 kódolást végző osztály
  • [[http://www.java-tips.org/java-se-tips/java.io/reading-a-file-into-a-byte-array.html]] Egy függvény, ami egy fájlból beolvassa a byte-okat tömbbe

Lonely HDR, azaz HDR kép készítése egyetlen képből

Sok mindenre képes az ember, ha van egy kis ideje. Például elgondolkodhat, hogyan tudná a rengeteg közepesen jól sikerült fényképét egyszerűen feltúrbózni, amit már szívesebben mutogat bárkinek. Kis gyakorlással csodát lehet művelni Photoshop (vagy a nyílt forráskód híveinek – küztük nekem – Gimp) segítségével, azonban ez néhány ezer kép nagyságrendnél kicsit idő- és munkaigényes. Fontos tehát, hogy mindezt automatikusan tudjuk csinálni. Már csak egy jó ötlet hiányzik. Ezen segít ha kicsit ránézünk a HDR technológiára.

High Dynamic Range imaging, avagy mi is ez? – dióhéjban

Sok mindenre képes az ember, ha van egy kis ideje. Például elgondolkodhat, hogyan tudná a rengeteg közepesen jól sikerült fényképét egyszerűen feltúrbózni, amit már szívesebben mutogat bárkinek. Kis gyakorlással csodát lehet művelni Photoshop (vagy a nyílt forráskód híveinek – küztük nekem – Gimp) segítségével, azonban ez néhány ezer kép nagyságrendnél kicsit idő- és munkaigényes. Fontos tehát, hogy mindezt automatikusan tudjuk csinálni. Már csak egy jó ötlet hiányzik. Ezen segít ha kicsit ránézünk a HDR technológiára.

High Dynamic Range imaging, avagy mi is ez? – dióhéjban

Kicsit bővebb leíráshoz: http://en.wikipedia.org/wiki/High_dynamic_range_imaging

A HDR célja általában olyan képek létrehozása, amelynek minden része teljes részletességgel látszik, attól függetlenül, hogy az sötét-e vagy világos. Ami azért nehéz, mert ha a világos részt kiemelve rövid ideig exponál a gép, a sötét részek nem fognak látszódni, míg túlexponált képpel előhozhatóak a sötét részletek, de a világos rész lesz kivehetetlen. A HDR alapötlete az, hogy egyetlen kép helyett csináljunk többet, különböző exponálással. Ezen képek alapján aztán összerakható olyan kép, amelynek minden részlete a megfelelő exponálással készült.

HDR-hez több kép kell, de nekünk csak egy van?

Nyílván egyetlen képből nem nyerhető annyi információ, mint akár három különbözőből, azonban nem is ezt akaruk elérni. A cél csupán az, hogy a rendelkezésre álló képből hozzuk ki a lehető legtöbbet, és ehhez használjuk fel a HDR-nél is használt technikát. A módszer nem más, mint a képet visonylag apró szegmensekre bontjuk, és minden szegmensre kiválasztjuk a rendelkezésre álló variánsok közül a legjobbat. A HDR esetén fizikailag van több képünk, ebben az esetben ugyanazt a képet világosítjuk/sötétítjük néhányszor. A világosság változtatásával ugyan nem nyerünk információt, a kép hatását a nézőre azonban jelentősen befolyásolhatjuk. Így tehát minden szegmensre kiválasztjuk az optimálisat, elsimítjuk az illesztéseket, végül kicsit megnöveljük a színtelítettséget, hogy tényleg szép legyen.

A fenti ötlet alapján készült egy kis java program, ami mély elmélkedés árán egész szép eredményt ér el a bemeneti képpel. Letöltés után valami ilyesmit kell beírni egy parancssorba:


java -jar lonelyhdr.jar input.jpg output.jpg

Rövid várakozás után egy kicsinosítgatott kép kacsint vissza ránk:

input.jpg output

Letöltés: lonelyhdr.jar

Fontos megjegyzés: a program értelmes eredményt csak megfelelő méretű (értsd: mai átlagos fényképezőgép által készített) képpel tud elérni. A fenti kép például egy 5 megapixeles szappantartóal készült, és a képet eredeti méretében (2592 x 1944 pixel) eresztettem át a programon.

Distributed Connector

A Linux Programozása nevű tárgyra adtam be egy helyi hálózatra tervezett elosztott kommunikációt megvalósító rendszert, és példaalklamazásként elosztott quicksort algoritmust. A rendszer tökéletesen elosztott, ami annyit tesz, hogy a helyi hálózatra csatolt gépek mindegyikén fut egy szolgáltatás, ami UDP üzenetek segítségével tartja a kapcsolatot a többi számítógéppel, és felületet nyújt a kommunikációhoz a felhasználói programoknak.

Letöltés

A Linux Programozása nevű tárgyra adtam be egy helyi hálózatra tervezett elosztott kommunikációt megvalósító rendszert, és példaalklamazásként elosztott quicksort algoritmust. A rendszer tökéletesen elosztott, ami annyit tesz, hogy a helyi hálózatra csatolt gépek mindegyikén fut egy szolgáltatás, ami UDP üzenetek segítségével tartja a kapcsolatot a többi számítógéppel, és felületet nyújt a kommunikációhoz a felhasználói programoknak.

Letöltés

Linux architektúrán legegyszerűbben a következő parancsok végrehajtásával tölthetjük le a forráskódot, és fordíthatjuk le a rendszert:


wget http://users.hszk.bme.hu/~gb606/disco/Makefile
make all

Dokumentáció

Disco Architektúra

A létrehozott rendszer feladata egy helyi hálózatra csatlakoztatott számítógépek kommunikációjának támogatása, anélkül, hogy ehhez szükség lenne központi kiszolgáló segítségére. A központi kiszolgáló feladatait (felhasználók követése, üzenetek továbbítása) szét kell osztani a résztvevő számítógépek között. A legegyszerűbb megoldás az, ha az összes számítógép ismeri a résztvevőket, és kommunikációra UDP üzeneteket, szükség esetén UDP üzenetszórást alkalmazunk.

DisCo architektúra

Minden résztvevő számítógépen fut egy DisCo kiszolgáló, amely a többi számítógéppel tartja a kapcsolatot, és felületet biztosít a felhasználói programok (továbbiakban szolgáltatások, Service-ek) számára Unix Domain Socket-en keresztül.

Lényeges kikötés, hogy egy gépen egyféle szolgáltatásból csak egy futhat egy időben és az egyes szolgáltatások a többi résztvevő gépen csak azonos szolgáltatással kommunikálhat (ez nem jelent korlátozást, hiszen az egyes szolgáltatások maguk is biztosíthatnak felületet a helyi gépen más szolgáltatások számára).

Disco protokoll

Közös csatorna

A résztvevő számítógépek között meg kell oldani azt, hogy az elosztott résztvevők mindegyike egységes képet kapjon a hálózatban szereplő gépekről, és a rajtuk futó szolgáltatásokról. Ezen felül továbbítani kell a szolgáltatások irányított vagy szórt (single-/broadcast) üzeneteit.

A közös csatornán használt üzenetek a következők. A kommunikáció UDP/IP felett történik, minden üzenetet sorvégjel (‘\n’) zár.

Üzenetformátum Paraméterek Jelentés
(%s Számítógép szöveges azonosítója A küldő fél bejelentkezik a hálózatba
=%d %s A rendelkezésre álló szolgáltatások kódolva; szöveges azonosító Bejelentkezésre válasz: a küldő gép szolgáltatásai, és azonosítója
) Kijelentkezés
+%d Szolgáltatás azonosító A küldő számítógépen új szolgáltatás indult
-%d Szolgáltatás azonosító A küldő számítógépen egy szolgáltatás leállt.
!%d %s Szolgáltatás azonosító, üzenet Irányított üzenet szolgáltatások között
.%d %s Szolgáltatás azonosító, üzenet Szórt üzenet szolgáltatások között

Helyi kommunikáció

Kiszolgáló -> Szolgáltatás

Üzenetformátum Paraméterek Jelentés
(%s Helyi gép szöveges azonosítója A kiszolgáló elküldi a helyi gép azonosítóját
+%d %s Távoli gép szám azonosítója, távoli gép szöveges azonosítója Egy távoli gépen elindult az adottal kompatibilis szolgáltatás
-%d Távoli gép azonosítója A távoli gépen leállították az adott szolgáltatást
!%d %s Távoli gép azonosítója, üzenet Irányított üzenet érkezett
.%d %s Távoli gép azonosítója, üzenet Szórt üzenet érkezett

Szolgáltatás -> Kiszolgáló

Üzenetformátum Paraméterek Jelentés
%d Szolgáltatástípus azonosítója Szolgáltatás azonosítása a helyi kiszolgáló számára
!%d %s Címzett azonosítója, üzenet Üzenet küldése a megjelölt számítógépen lévő azonos szolgáltatás számára
. %s Üzenet Üzenet küldése az összes azonos szolgáltatásnak a hálózatban

Protokoll folyamatok

Bejelentkezés

Amikor egy új számítógépen elindul a DisCo kiszolgáló, egy bejelentkezés (‘(%s’) üzenetet küld minden számítógépnek a hálózatban. Nyilvánvaló, hogy kezdetben semmilyen szolgáltatás nem fut az újonnan indult kiszolgáló mellett. A bejelentkezés hatására minden számítógép visszaküldi a saját azonosítóját és a rajta futó szolgáltatások azonosítóit kódolva.

Szolgáltatás indítása, leállítása

Minden távoli szolgáltatás indulásakor vagy leállításakor az eseményt jelezni kell az esetleges azonos típusú helyi szolgáltatásnak. Illetve a helyi szolgáltatásokban történt változást szórt üzenetként továbbítani kell a többi számítógépnek.

Egy példa alkalmazás: quicksort

A bemutatott példában a 2-es azonosítóhoz rendeljük a sort szolgáltatást, amely gyorsrendezést valósít meg az elosztott rendszerben. A hálózat bármelyik gépén beadott rendezési feladatot a rendszer autonóm módon szétosztja a feladatot, majd a végeredmény ott lép ki, ahol a feladatot beadtuk.

Pontosítva az algoritmust, (a továbbiakban node-nak hívjuk a sort szolgáltatással rendelkező számítógépeket) ha egy node kap egy rendezési feladatot (egy másik node-tól, vagy a rendszeren kívüli felhasználótól) azt két részre osztja, majd kiválaszt véletlenszerűen két node-ot, akiknek elküldi a két részfeladatot. Minden node oda küldi vissza az eredményt, ahonnan kapta. Ha a végeredményről van szó, akkor azt a részeredmények összerakása után a helyi fájlrendszerben output.txt fájlba rakja.

Protokoll

A quicksort szolgáltatás a DisCo kiszolgáló felületét használva, a fent leírt protokollra épít, az üzeneteit tehát DisCo protokoll szerint felcímezve küldi. Alant csak a DisCo protokoll üzenetrészében szereplő tartalmat fejtem ki, bár az is fontos információt tartalmaz, hogy ki az üzenet címzettje.

Üzenetformátum Paraméterek Jelentés
?%d%c%d,%d,… Feladatazonosító (aminek szerepelnie kell a válaszban); részjelölő karakter: ‘< ' vagy '>‘ attól függően, hogy a feladat a teljes feladat alsó, vagy felső részfeladata; majd a rendezési feladatot leíró vesszővel elválasztott számsorozat (első eleme az adatok száma, majd jönnek a számok) A küldő node feladatot ad a címzettnek.
!%d%c%d,%d,… A feladatkiírásban szereplő azonosító; a részjelölő karakter; és a rendezett számsorozat hasonlóan a feladatkiíráshoz A küldő node befejezte a feladatot, és küldi az eredményt.

Rendezés menete

A felhasználó bármelyik a rendszerbe csatlakozott számítógépen létrehoz egy feladatfájlt, benne a rendezésre váró számokkal, sorvégjellel elválasztva. E fájl nevét beírva a sort programba bekerül a feladat a rendszerbe. Ha minden jól megy, pár pillanat múlva a megoldás bekerül az output.txt -be.

Telepítés és futtatás

A DisCo kiszolgálót és a quicksort szolgáltatást a következő parancsok beírásával lehet a leggyorsabban telepíteni (minden előzetes letöltés nélkül):


wget http://users.hszk.bme.hu/~gb606/disco/Makefile
make all

A fent letöltött Makefile letölti az összes szükséges forrásfájlt, és előállítja a szükséges bináris állományokat, és generál egy azonnal használható input.txt -t. A kiszolgálót futtatni a


./disco _név_

-el lehet, ahol a _név_ egy szabadon választott szöveges azonosítója a számítógépnek. A rendezési feladat generálására alkalmas eszköz az


./inputgen _kimenetszám_

, ami stdout-ra a megadott számú véletlenszámot írja ki. Végül a sort szolgáltatást a ./sort paranccsal indíthatjuk el.