MultiThreaded Server
Tujuan: - Mahasiswa mengerti konsep thread dan mampu membuat aplikasi multithread sederhana - Mahasiswa mampu membuat aplikasi server yang dapat menangani multiple client secara konkuren.
Universitas Atma Jaya Yogyakarta Fakultas Teknologi Industri Teknik Informatika Kusnadi@mail.uajy.ac.id
Kode program Data global Memori (heap) Gambar 1. Program sekuensial Kode program Data global Memori (heap) Stack
Gambar 2. Program Konkuren Keuntungan menggunakan sejumlah thread dalam program antara lain: kemampuan melakukan sejumlah tugas secara simultan dan tak bergantung satu sama lain. Misalkan satu thread melakukan penggambaran animasi secara background, sedangkan thread lain menangani inputan user lewat keyboard. Selain itu thread dapat meningkatkan throughput aplikasi, memperbaiki waktu tanggapan (responsiveness) aplikasi dan kemampuan untuk menggunakan sumber daya sistem secara efisien.
kelas yang mengimplementasikan interface Runnable. Cara kedua berguna terutama jika kita hendak menurunkan kelas yang hendak kita buat dari kelas lainnya selain kelas Thread. Java tidak mendukung multiple inheritance, tapi mendukung multiple interface. Perbedaanya adalah obyek yang dibuat dari kelas yang mengimplementasikan interface Runnable haruslah di-casting ke Thread agar dapat memanggil method-method yang ada pada Thread. Sedangkan obyek dari kelas yang mengeksten kelas Thread dapat memanggil method-method Thread secara langsung. Kelas yang mengimplementasikan thread (baik inheritance Thread maupun implementasi interface Runnable) haruslah mendefinisikan method run(). Method inilah yanng sesunguhnya akan dieksekusi sebagai alur eksekusi independen saat obyek thread memanggilkan mehtod start(). Program D2-1 memberikan contoh membuat kelas thread dengan inheritance sedangkan program D2-2 memberikan contoh serupa dengan mengimplementasikan Runnable. Demo D2-1 Salah satu kemungkinan penggunaan soket adalah menciptakan satu thread untuk menunggu inputan dari user dan thread lainnya menunggu data yang dikirim oleh soket komputer lain. Tutorial dibawah ini menggambarkan kemungkinan tersebut walaupun sebatas simulasi.
public static void main(String[] argv){ MyThread t1= new MyThread("Read from Socket"); MyThread t2= new MyThread("Read from Keyboard"); t1.start(); t2.start(); } }
Demo D2-2 Cara lain untuk mengimplementasikan thread adalah dengan membuat suatu kelas yang mengimplemetasikan interface Runnable. Tutorial ini melakukan hal yang sama dengan D2-1, namun dengan menggunakan interface runnable. Perhatikan pada bagian fungsi main, obyek dari kelas yang dibuat haruslah dicasting ke Thread lebih dulu, sebelum method-method Thread (seperti start()) dapat dipanggil 1. Tuliskan program dibawah ini dan simpan sebagai MyThread2.java:
class MyThread2 extends Thread{ private String namaThread; public MyThread2(String nama) { namaThread=nama; } public void run(){ for(int i=0;i<3;i++){ System.out.println(namaThread+": " + i); try{ Thread.sleep(500); }catch(InterruptedException e){ e.printStackTrace(); } } }
public static void main(String[] argv){ MyThread t1= new MyThread("Read from Socket"); MyThread t2= new MyThread("Read from Keyboard"); ((Thread) t1).start(); ((Thread) t2).start(); } }
2.
Latihan L2-1 Modifikasi program D2-2 sehingga jumlah looping cetak tiap Thread ditentukan dari argumen konstruktornya. Misalnya dapat diset t1 mencetak 3 kali, dan t2 mencetak 10 kali.
C. Sinkronisasi Thread
Demo D2-3 Tutorial ini membuat aplikasi multithread. Kelas SleepingAnimal mengimplementasikan obyek yang memiliki alur eksekusi tersendiri. Setiap obyek dari kelas ini akan memiliki unit eksekusi sendiri setelah method start() nya dipanggil. Konstruktor kelas ini memiliki dua argumen yaitu nama binatang dan waktu tidurnya (dlm ms). Begitu method start() nya di panggil, maka obyek binatang tersebut akan tidur sesuai waktu tidurnya. Setiap kali terbangun, maka obyek binatang tersebut akan memanggil method eating(). Waktu makan tiap obyek binatang adalah 5 ms dan menghabiskan 1 makanan (variabel numOfFood) yang disimpan pada atribut statik kelas SleepingAnimal. Dalam contoh ini, fungsi utama akan membuat dua obyek SleepingAnimal dan menginisialisasi jumlah makanan sebanyak 1000. Tiap detik (1000 ms), program akan mengecek jatah makanan yang tersisa. Program akan berjalan selama 3 detik dan setelah itu program akan menampilkan statistik makan dari tiap obyek binatang.
} } public void run(){ while(true) { try{ Thread.sleep(sleepTime); eating(); numTerbangun++; System.out.println(animalName+ " terbangun dan makan"); }catch(InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] argv) throws Exception{ //inisialisasi awal int JumlahMakanan=1000; SleepingAnimal Kucing= new SleepingAnimal("Kucing Betina",15); SleepingAnimal Anjing= new SleepingAnimal("Anjing",40); SleepingAnimal.numOfFood=JumlahMakanan; //kucing dan anjing mulai hidup Kucing.start(); Anjing.start(); //pengawas mulai hidup for(int i=1; i<=3;i++) { Thread.sleep(1000); System.out.println("\n.checking, makananan tinggal=" + SleepingAnimal.numOfFood +"\n\n"); } //mencetak statistik akhir System.out.println("-------------------------------------"); System.out.println("Kucing terbangun : "+ Kucing.numTerbangun+" kali"); System.out.println("Anjing terbangun : "+ Anjing.numTerbangun+" kali"); System.out.println("Total Termakan: "+ (Kucing.numTerbangun + Anjing.numTerbangun)); System.out.println("Sisa makanan : " + SleepingAnimal.numOfFood+ " dari sebelumnya " + JumlahMakanan); System.out.println("-------------------------------------"); System.exit(0); } }
2. Kompilasi dan jalankan program diatas, hasilnya kurang lebih seperti dibawah ini:
Kucing Kucing Anjing Kucing Kucing Anjing Kucing Betina terbangun dan Betina terbangun dan terbangun dan makan Betina terbangun dan Betina terbangun dan terbangun dan makan Betina terbangun dan makan makan makan makan makan
---------------------------------------
Kucing terbangun : 138 kali Anjing terbangun : 65 kali Total Termakan: 203 Sisa makanan : 829 dari sebelumnya 1000 ---------------------------------------
(NB: Hasil statistik akan berbeda pada tiap eksekusi, karean penjadwalan thread ditentukan oleh sistem operasi dan dipengaruhi banyak faktor pada saat eksekusi) Perhatikan baik-baik pada contoh eksekusi diatas, terlihat total termakan (203) dijumlahkan denga sisa makanan(829) adakag 1031 dab tidaklah tepat 1000 (jatah makanan pada kondisi awal). Mengapa hal ini bisa terjadi? Bagaimana tambahan 31 ini dapat terjadi. Ini dikarenakan terjadi race condition saat mengupdate nilai numOfFood pada method eating(). Perhatikan koding pada method tersebut dimulai dengan membaca nilai variabel numOfFood ke variabel lain dan ada tunda waktu sebesar 5 ms, sebelum akhirnya nilai hasil pengurangan disimpan lagi pada variabel numOfFood. Pada program D2-3 sengaja waktu tidur SleepingAnimal dibuat sangat kecil (15 dan 40 ms), sehingga race condition terjadi. Pada saat terjadi race condition, 2 binatang lagi eating(), namun jatah makanan hanya berkurang satu, karena proses pengurangan jatah makanan pada kedua obyek binatang akan saling menimpa. Tentunya koding semacam ini tentunya tidak efisien disini, namun kondisi seperti ini sering terjadi pada penjadwalan thread dan proses pada level prosesor, dimana nilai suatu variabel disalin ke register di prosesor, dan sangat mungkin kemudian prosesor dipindahkan ke proses atau thread lain untuk waktu cukup panjang sebelumnya akhirnya thread yang pertama melanjutkan proses update variabel bersangkutan. Bagaimana kondisi race condition ini dapat diatasi? Solusinya adalah dengan melakukan sinkronisasi. Sinkronisasi terutama dibutuhkan saat sejumlah thread yang berbeda mengakses suatu sumber daya yang sama seperti variable, file, record di basis data maupun device I/O. Sinkronisasi pada java dapat dilakukan pada sebagian kode program maupun pada suatu fungsi. Konsekuensinya adalah jika suatu thread lagi mengeksekusi bagian tersebut, maka thread lain yang juga hendak mengeksekusi bagian tersebut harus menunggu terlebih dulu.
Demo D2-4 1. Untuk melakukan sinkronisasi pada tingkat fungsi, tambahkan keyword synchronized didepan method eating() pada program D2-3 seperti dibawah ini (lihat bagian )
synchronized public static void eating() { //koding lainnya....... }
Kucing terbangun : 131 kali Anjing terbangun : 65 kali Total Termakan: 196 Sisa makanan : 804 dari sebelumnya 1000 ---------------------------------------
Sekarang penjumlah total termakan dan sisa makan sudah sama dengan 1000! 3. Cobalah dijalank berkali-kali untuk memastikan penjumlahannya sudah benar-benar valid. 4. Java juga menyediakan sinkronisasi pada level bagian kode seperti dibawah ini
public static void eating() { synchronized(this) { //koding lainnya....... } }
Demo D2-5 Tutorial akan mencoba meminta dokumen testing.html yang terletak pada suatu root direktori HTTP server yang beralamat di 192.168.29.9 dengan memakai telnet 1. Lakukan koneksi telnet ke komputer target dengan perintah $telnet 192.168.29.9 80 2. Akan muncul layar kosong, ketik perintah dibawah ini dan tekan tombol enter 2 kali GET /testing.html HTTP/1.0 3. Amati hasil balasan dari HTTP server
HTTP/1.1 200 OK Server: Microsoft-IIS/5.1 X-Powered-By: ASP.NET Date: Thu, 19 Jul 2007 01:28:18 GMT Content-Type: text/html Accept-Ranges: bytes Last-Modified: Thu, 19 Jul 2007 01:01:58 GMT ETag: "1e8c6468a0c9c71:c66" Content-Length: 56 <HTML> <BODY> HALAMAN PERCOBAAN </BODY> </HTML> Connection to host lost.
NB: Bagian pertama adalah header pesan yang memberikan informasi versi protokol HTTP yang didukung oleh server, jenis servernya, jenis dokumen yang diakses, dan panjang pesan. Isi dokumen dimulai dengan tag <HTML>. Perhatikan juga koneksi langsung ditutup begitu pesan diterima. 4. Coba juga melakukan akses dokumen di situs lain seperti http://inf.uaj.ac.id/index.php dengan menggunakan telnet. 5. Coba juga melakukan akses ke dokumen yang tidak ada seperti http://inf.uajy.ac.id/sembarang.html dan lihat pesan balasan apa yang dikirim oleh server Selain mengakses langsung ke HTTP server, koneksi http dapat dilakukan melalui proxy server. Proxy server berfungsi sebagai perantara untuk pengaksesan dokumen di situs internet. Penggunaan proxy server melindungi komputer-komputer lokal LAN terhadap eksploitasi luar, karena bagi komputer di internet, yang mengakses http server adalah proxy server. Jika web browser dikonfigurasi dengan menggunakan proxy server
10
maka yang melakukan koneksi ke http server tujuan bukanlah web browser lagi, namun proxy server. Web browser hanya melakukan koneksi ke proxy server. Proxy server mendukung perintah yang sama dengan HTTP server. Perbedaanya adalah saat mengirimkan pesan GET ke proxy server, maka harus disertakan juga alamat komputer HTTP server yang dituju seperti : GET http://www.detik.com/berita.html . Proxy server yang akan membuka koneksi ke www.detik.com dan mengirimkan pesan GET /berita.html HTTP/1.0. Demo D2-6 1. Lakukan koneksi telnet ke proxy server (sesuai dengan proxy server tempat anda) $telnet 192.168.5.1 8080 2. Akan muncul layar kosong, ketik perintah dibawah ini dan tekan tombol enter 2 kali GET http://www.jogja.com/index.php HTTP/1.0 3. Amati hasilnya
HTTP/1.0 200 OK Date: Thu, 19 Jul 2007 02:13:17 GMT Server: Apache X-Powered-By: PHP/4.4.4 Content-Type: text/html X-Cache: MISS from proxy2.uajy.ac.id X-Cache: MISS from proxy.uajy.ac.id Proxy-Connection: close <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org /TR/html4/loose.dtd"> <html> <head> <title>:: All Info Jogja ::</title> <meta http-e quiv="Content-Type" content="text/html; charset=iso-8859-1"> <meta httpequiv="re fresh" content="0;URL=http://w3.jogja.com/"> </head> <body> </body> </html>
11
Gambar 3. Konsep komunikasi soket pada HTTP Server Dalam tutorial D2-7 akan diberikan contoh pembuatan aplikasi HTTP server sederhana. Untuk mengetes aplikasi ini dapat menggunakan web browser biasa maupun menggunakan telnet seperti pada demo D2-5.
12
Demo D2-7 Tutorial ini akan membuat aplikasi HTTP Server sederhana yang hanya memproses pesan GET dari client. Hal yang dilakukan aplikasi ini ini adalah: membuka soket listen pada port 80 dan menunggu koneksi Setiap kali terjadi koneksi membuatkan soket komunikasi untuk client tersebut membuat thread yang terpisah (class Connect) untuk menangani komunikasi datanya. Thread utama akan kembali menuggu koneksi berikutnya. Dokumen-dokumen HTTP server akan diletakkan satu direktori dengan tempat class httpd dijalankan (otomatis sebagai direktori root server). Selain itu aplikasi ini akan mengirim isi dokumen apa adanya. Jadi script-script server tidak akan diproses terlebih dulu Jika tidak ada nama dokumen yang dicantumkan pada dianggap dokumen yang diakses adalah index.html yang ada di direktori root. NB: Pada web server sesungguhnya, lokasi direktori root HTTP server dapat dikonfigurasi. Selain itu script-script server seperti PHP ataupun javascript akan diproses lebih dulu sebelum hasil akhirnya dikirimkan sebagai pesan (format html) ke sisi client (web browser) 1. Buatlah kelas Connect untuk mengimplementasikan thread yang menangani komunikasi client dengan mengetik program dibawah ini dan simpan sebagai Connect.java
import java.net.*; import java.io.*; import java.util.*;
class Connect extends Thread{ Socket client; BufferedReader is; DataOutputStream os; public Connect(Socket s){ client=s; try { is= new BufferedReader( new InputStreamReader(client.getInputStream())); os= new DataOutputStream(client.getOutputStream()); this.start(); }catch(IOException e){ try{ client.close(); }catch(IOException ex){ System.out.println("Error while getting socket streams: " +ex); } } }
13
public void run(){ try{ String request= is.readLine(); System.out.println("Request: " +request); StringTokenizer st= new StringTokenizer(request); if((st.countTokens()>=2) && st.nextToken().equals("GET")){ //memparsing nama dokumennya if((request=st.nextToken()).startsWith("/")) request= request.substring(1); if(request.endsWith("/") || request.equals("")) request= request+"index.html"; File f= new File(request); //baca dokumen dan kirim lewat stream shipDocument(os,f); }else{ try { os.writeBytes("400 Bad Request"); } catch(Exception ex) { ; } } client.close(); } catch(IOException e){ System.out.println("I/O error " + e); }catch(Exception ex){ System.out.println("Exception: "+ ex); } } public static void shipDocument(DataOutputStream out, File f){ try{ DataInputStream in= new DataInputStream(new FileInputStream(f)); int len=(int) f.length(); byte buf[]= new byte[len]; in.readFully(buf); out.writeBytes("HTTP/1.0 200 OK\r\n"); out.writeBytes("Content-Length: "+ buf.length+ "\r\n"); out.writeBytes("Content-Type: text\\html\r\n\r\n"); out.write(buf); out.flush(); in.close(); }catch(FileNotFoundException e){ try { out.writeBytes("404 Not Found"); } catch(Exception ei) {; } }catch(IOException ex){ System.out.println("Error writing ..."+ex); } } }
14
2. Selanjutnya buatlah kelas layanan HTTP server dengan menulis kode dibawah ini dan simpan sebagai httpd.java;
import java.net.*; import java.io.*; public class httpd extends Thread{ public static final int HTTP_PORT=8080; protected ServerSocket listen; public httpd(){ try{ listen=new ServerSocket(HTTP_PORT); }catch(IOException ex){ System.out.println("Exception..."+ex); } this.start(); } public void run() { try{ while(true){ Socket client=listen.accept(); Connect cc= new Connect(client); } }catch(IOException e){ System.out.println("Exception..."+e); } } public static void main(String argv[]) throws IOException{ new httpd(); } }
3. Pastikan httpd.java dan Connect.java berada pada satu direktori. Kemudian lakukan kompilasi pada httpd.java dan jalankan. (Program akan terus hidup menunggu koneksi client)
$ javac httpd.java $ java httpd
4. Buat atau salinlah sejumlah dokumen html ke lokasi direktori httpd.class. Pastikan salah satu dokumen bernama index.html 5. Bukalah browser dan ketikan alamat url dengan format htttp://<computer lokal>:8080/<nama dokumen> misalkan komputer lokal bernama station dan IP=192.168.29.77 maka dapat dicoba http://127.0.0.1:8080/index.html http://station:8080/index.html http://192.168.29.77/index.html
15
Project P2-1 Buatlah aplikasi Telnet server dan telnet Client. Telnet server beroperasi pada port 2121. Pada dasarnya telnet server menerima pesan dari client dari menjalankan pesan tersebut sebagai perintah shell pada komputer lokal. Output dari shell di tampilan (standard ouput maupun standard error shell) ditangkap oleh telnet server dan dikirim balik ke sisi client (termasuk prompt user). Bagi pengguna telnet client dia seakan-akan berada pada komputer server dan menjalankan shell di server.
16