VoIP: Telepon Murah Dengan Asterisk

Salah satu topik yang sedang hangat didiskusikan di lingkungan kerjaku adalah VoIP. Yup, dengan VoIP, komunikasi telepon tidak perlu lagi menggunakan kabel T1/E1 (di Indonesia memakai E1). Tidak ada lagi protokol signaling SS7. Tidak ada lagi switching board. Tidak ada lagi routing yang rumit. Semua cukup digantikan dengan jaringan IP yang murah dan dikenal oleh banyak orang!

Tapi karena jaringan telepon konvensional, atau yang lebih dikenal sebagai Plain Old Telephone Service (POTS) sudah mendarah daging dan menjangkau berbagai pelosok, mengganti POTS dengan VoIP bukanlah hal mudah. Salah satu solusi yang ditempuh adalah dengan melakukan bridging antara POTS dan VoIP, yang dikenal dengan istilah PSTN/Internet Interworking (PINT). Disini dibutuhkan gateway untuk menerjemahkan signalling SS7 menjadi SIP, dan untuk mengkonversi suara analog ke dalam format audio digital melalui codec seperti G.723.1.

Hari ini aku akan mencoba menggunakan Asterisk untuk membuat solusi PABX berbasis VoIP. Asterisk adalah software open-source yang saat ini dapat dijalankan di Linux (belum mendukung Windows). Untuk end-point, aku akan menggunakan softphone, yaitu software untuk bertelepon melalui VoIP yang sudah mendukung protokol SIP. Ada banyak softphone yang gratis dan multi-platform, salah satunya adalah X-lite. Tentu saja untuk dunia nyata, aku akan membeli VoIP phone yang bentuknya seperti telepon rumah, tetapi dilengkapi dengan konektor RJ45 dan dapat dikonfigurasi untuk memiliki IP masing-masing.

Langkah pertama yang aku lakukan setelah meng-install Asterisk adalah mendaftarkan setiap endpoint yang ada. Aku dapat melakukannya dengan mengedit file /etc/asterisk/sip.conf dan menambahkan bagian berikut:


[116]
type=friend
context=default
defaultname=116
callerid=Snake
host=dynamic
nat=no
canreinvite=yes
dtfmode=rfc2833
disallow=all
allow=ulaw

[163]
type=friend
context=default
defaultname=163
callerid=Big Boss
host=dynamic
nat=no
canreinvite=yes
dtfmode=rfc2833
disallow=all
allow=ulaw

Setelah itu, aku melakukan konfigurasi dial-plan dengan meng-edit file /etc/asterisk/extensions.conf. Dialplan berisi kumpulan aksi yang akan dikerjakan jika pengguna menekan tombol tertentu di telepon-nya. Aku dapat berkreasi sebebas mungkin disini, seperti membuat sistem IVR (Interactive Voice Response). Btw, saat aku masih baru kerja, rekanku banyak bicara soal IVR yang kami tangani..dan herannya, mereka tidak tahu apa kepanjangan IVR yang sering mereka sebut-sebut. Seorang senior manager dari Korea malah pernah sekali saat menerangkan arsitektur IVR, menyebut kepanjangan IVR sebagai Internet Voice Recognition. Sebegitu tidak pentingkah teori dasar?

Bagian dialplan yang akan dikerjakan secara default adalah bagian [default]. Jika aku meng-install demo dari Asterisk sebelumnya, maka baris pertama yang aku temui adalah:

include => demo

Ini akan memungkinkan kita untuk mendengarkan IVR dari Asterisk dengan menekan tombol 2. Karena aku tidak membutuhkan ini, aku dapat men-comment baris ini dengan menambahkan tanda semicolon (;) di awal baris.

Lalu, aku menambahkan baris berikut:

exten => 163,1,Dial(SIP/163,30,m)
exten => 116,1,Dial(SIP/116,30,m)

Sekarang, langkah terakhir adalah menjalankan Asterisk dengan memberikan perintah:

asterisk

Untuk melihat informasi mengenai server Asterisk, aku dapat memberikan perintah berikut untuk masuk ke Asterisk console:

asterisk -r

Misalnya, untuk melihat protokol SIP yang berlalu-lalang di server, aku dapat memberikan perintah:

sip set debug on

Dan tentu saja, sebelum dapat bertelepon, aku harus melakukan konfigurasi softphone di masing-masing endpoint. Secara umum, aku perlu mengkonfigurasi agar softphone mencari dan mendaftarkan diri di gateway Asterisk. Aku juga mungkin perlu mengubah agar DTMF dikirim sesuai aturan protokol RFC2833 (sesuai dengan konfigurasi yang aku buat di file sip.conf).

Sekarang aku bisa menekan ekstensi 163 untuk menghubungi endpoint 163, dan menekan ekstensi 116 untuk menghubungi endpoint 116. Selamat bertelepon ria :)

Add comment Januari 2, 2010

Linux Startup: Mengawali Tahun Baru 2010

Selamat tahun baru 2010!! Untuk merayakan tahun baru ini, aku pun menyempatkan diri untuk menulis blog setelah sekian lama tidak online. Maklum saja, aku masih harus berpisah dengan notebook. Tapi aku mendapat pelajaran baru: belajar untuk lepas dari kesibukan dan rutinitas-ku, sehingga aku bisa melihat begitu banyak karunia yang begitu berharga di sekelilingku. Sesuatu yang mungkin aku anggap sepele sebelumnya, seperti keluarga yang baik dan adik kecil yang lucu :)

Dan kembali ke laptop, aku akan melanjutkan tulisan sebelumnya dengan proses startup di Linux. Pada tulisan tersebut, aku mematikan services dengan menghapus symbolic link-nya secara manual. Kali ini, aku akan memakai tools bawaan Linux, yang tentunya lebih disarankan ketimbang memakai cara manual.

Untuk melihat daftar services yang dijalankan saat proses startup, aku dapat mengetik:


chkconfig

Untuk melihat apakah services tersebut dijalankan pada setiap run-level, aku dapat menambahkan argument “-l”. Misalnya, untuk melihat run-level apa saja yang menjalankan “cron”, aku dapat mengetik seperti ini:


chkconfig -l cron

Yang hasilnya akan seperti:

cron 0:off 1:off 2:on 3:on 4:off 5:on 6:off

Jika aku hanya ingin “cron” dijalankan pada run-level 5, aku dapat memberikan perintah seperti ini:


chkconfig -s cron 5

Untuk membuat “cron” kembali dijalankan pada run-level 2, 3, dan 5, aku memberikan perintah seperti ini:


chkconfig -s cron 235

Untuk melihat services yang sudah berjalan di sistem pada saat ini, aku dapat memberikan perintah:


service -s

Untuk memeriksa status service tertentu saja, misalnya service “cron”, aku dapat memberikan perintah:


service cron status

Aku juga dapat menjalankan, mematikan dan me-restart service, masing-masing dengan argumen start, stop, dan restart.

2 comments Januari 2, 2010

Startup Di Linux

Untuk sementara selama beberapa bulan ini, aku harus berpisah dengan notebook kesayanganku.  Hari-hari akan terasa sangat berat untuk dilalui tanpa sebuah komputer.  Untung saja masih ada game PSP yang belum aku tamat-kan.  Dan juga ada notebook kantor yang sesekali dapat aku bawa pulang.  Aku tidak terlalu bangga dengan Intel Pentium M dan memori 768 MB di tahun 2009 ini.  Tapi ini lebih baik daripada tidak ada notebook sama sekali.

Notebook tersebut berisi sistem operasi OpenSUSE 11.  Sebagai pengguna Windows, aku harus membiasakan diri dengan Linux/Unix.  Ini adalah tuntutan pekerjaan.  Aku menggunakan gcc dan Eclipse sebagai GUI editor.  Aku memakai libtool untuk membuat shared library dan dynamic library secara otomatis.  Aku melakukan IPC dengan shared memory.  Aku membuat program berbasis GUI dengan XLib…  Semua ini berbeda dengan Windows dan Visual C++ nya…

Mmmh..di hari minggu ini seharusnya aku tidak memikirkan kerjaan.  Lebih baik aku meng-otak-atik sistem operasi Linux di notebook yang aku bawa pulang kemarin.  Aku melihat ada beberapa daemon yang berjalan secara otomatis saat sistem operasi berjalan, seperti mail server dan ssh daemon. Aku tidak membutuhkan mereka, jadi aku bisa mematikannya untuk menghemat memori.  Di Windows, aku dapat menggunakan tool msconfig atau meng-edit registry.  Tapi bagaimana dengan Linux?

Di distro openSUSE, program-program yang dijalankan otomatis memiliki symbolic link yang terletak di direktori /etc/init.d/rcX.d, dimana X bisa berupa angka yang menunjukkan run level, seperti rc0.d, rc1.d, rc2.d, dan seterusnya.  Contoh file symbolic link-nya seperti S11postfix.  Huruf awal “S” menunjukkan program ini akan dikerjakan saat startup sistem operasi.  Jika huruf awal-nya “K”, program tersebut akan dikerjakan saat sistem operasi di-shutdown.  Angka 00-99 setelah huruf “S” atau “K” menunjukkan nilai prioritas.  File yang nilai angka prioritas-nya lebih kecil akan dikerjakan lebih dulu sebelum file yang nilai angka prioritas-nya  lebih besar.

Aku segera menghapus symbol link seperti postfix, sshd dan avahi-daemon.  Aku tidak membutuhkan program-program itu, karena mereka hanya membuka port yang memungkinkan seseorang melakukan koneksi ke sistem operasi.

Add comment Oktober 17, 2009

JavaServer Faces: Melihat Wajah Java

Hari ini aku akan mencoba mempelajari bagaimana menggunakan JSF bersama dengan JSP. JSF masih merupakan bagian dari JEE, dan dibuat berdasarkan projek Apache Struts. Yup! JSF memakai design pattern MVC layaknya Struts. Sayangnya, Tomcat tidak menyertakan JSF dalam paket-nya, sehingga aku harus men-download JAR tambahan di situs Sun. Tapi ada ide yang lebih baik: hari ini aku akan memakai NetBeans IDE 6.5 dan application server GlassFish, soalnya mereka sudah punya JSF sehingga aku tidak perlu repot men-download lagi.

Aku memilih Web Application (Java Web) dari New Project di NetBeans. Setelah itu, pada langkah ke-4, aku men-centang pilihan framework JavaServer Faces. Setelah project dibuat, aku memastikan libraries JSF 1.2 telah disertakan di bagian Libraries, yang terdiri atas commons-beanutils.jar, commons-collections.jar, common-digester.jar, commons-logging.jar, jsf-api.jar, dan jsf-impl.jar.

Pada MVC di JSF, Model diwakili oleh class JavaBean, View diwakili oleh JSP, dan Controller diwakili oleh facet servlet (javax.faces.webapp.FacesServlet) yang dapat di-customize melalui file /WEB-INF/faces-config.xml.

Sebagai latihan, aku akan memakai Model ini:

package com.latihan;

public class User {
    private String firstName;
    private String lastName;
    private String password;

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

Aku harus mendaftarkan Model tersebut pada file konfigurasi faces-config.xml terlebih dahulu. File konfigurasi ini dapat ditemukan di bagian “Configuration Files” dari project. Saat pertama kali membukanya, NetBeans akan menampilkan PageFlow, sebuah fitur yang sangat berguna untuk melihat hubungan antar View dalam bentuk diagram. Untuk meng-edit file ini, klik pada bagian “XML“, lalu aku menambahkan baris berikut:

<managed-bean>
  <managed-bean-name>user</managed-bean-name>
  <managed-bean-class>com.latihan.User</managed-bean-class>

  <managed-bean-scope>session</managed-bean-scope>
</managed-bean>

Sekarang aku dapat memakai Model tersebut di View melalui managed-bean-name, yaitu user. Sebagai latihan, berikut ini adalah View yang aku beri nama registrasi.jsp:

<%@taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<%@taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<html>
    <head>
        <title>Latihan JSF</title>

    </head>
    <f:view>
        <body>
            <h1>User Registration</h1>
            <h:form>

                <table>
                    <tr>
                        <td>First Name:</td>
                        <td><h:inputText value="#{user.firstName}" size="30"/></td>
                    </tr>

                    <tr>
                        <td>Last Name:</td>
                        <td><h:inputText value="#{user.lastName}" size="30"/></td>
                    </tr>
                    <tr>

                        <td>Password:</td>
                        <td><h:inputSecret value="#{user.password}" size="30"/></td>
                    </tr>
                </table>
                <h:commandButton value="Register" action="submit"/>

            </h:form>
        </body>
    </f:view>
</html>

Secara default, View di atas dapat di-akses dengan alamat http://localhost:8080/nama_context/faces/registrasi.jsp. Berikutnya, aku membuat sebuah View lagi, yang aku beri nama userinfo.jsp:

<%@taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<%@taglib uri="http://java.sun.com/jsf/html" prefix="h" %>

<html>
    <head>
        <title>Latihan JSF</title>
    </head>
    <f:view>
        <body>

            Welcome, <h:outputText value="#{user.firstName}"/>
            <h:outputText value="#{user.lastName}"/>.
            <p/>
            Your password is <h:outputText value="#{user.password}"/>.
        </body>
    </f:view>

</html>

Sebagai langkah terakhir, aku akan menentukan navigasi View di dalam Controller dengan menambahkan baris berikut di file faces-config.xml:

<navigation-rule>
   <from-view-id>registrasi.jsp</from-view-id>
   <navigation-case>
      <from-outcome>submit</from-outcome>

      <to-view-id>userinfo.jsp</to-view-id>
   </navigation-case>
</navigation-rule>

Sekarang, jika aku membuka halaman registrasi.jsp dan aku menekan tombol Submit, aku akan dibawa ke halaman userinfo.jsp. Tampilan diagram PageFlow di NetBeans saat membuka faces-config.xml akan memperjelas flow navigasi antar-view.

Add comment September 21, 2009

Sun Oracle Exadata Storage Server: Butuh Storage Kencang Untuk Oracle?

Belakangan ini aku kebetulan sering berhubungan dengan database Oracle di live system.  Aku beberapa kali  mengalami query yang prosesnya sangat lama sekali… sampai-sampai aku khawatir akan menganggu proses transaksi dari user dan segera aku batalkan.  Aku menginginkan database yang di-tuning dengan baik, database yang responsif dan dapat menangani banyak permintaan.

Jika budget bukan masalah, salah satu solusi yang dapat dipakai adalah Sun Oracle Exadata Storage.  Tidak seperti storage server biasa, Sun Oracle Exadata Storage menyediakan fitur khusus untuk meningkatkan kinerja database Oracle.  O ya, storage ini juga dipakai oleh Sun Oracle Database Machine, sebuah server khusus yang dilengkapi dengan database Oracle yang telah ter-install dan ter-konfigurasi.  Ini adalah salah satu ‘hasil’ merge Oracle dan Sun: Software From Oracle, Hardware From Sun.

Salah satu fitur Sun Oracle Exadata Storage adalah ia dapat melakukan SQL processing dan hanya mengembalikan data yang dibutuhkan saja.  Ini berarti akan ada penghematan bandwidth dibanding storage biasa.  Dan juga penghematan CPU, karena CPU dari server database tidak perlu bekerja melakukan SQL processing lagi.  Fitur ini dapat terwujud berkat CELLSRV (Cell Services), software multi-thread yang berjalan di storage server untuk berkomunikasi dengan server database.

Sun Oracle Exadata Storage juga dilengkapi dengan 384 GB Exadata Smart Flash Cache.  Ini adalah solid state storage yang berfungsi sebagai cache untuk transaksi yang sering dilakukan.  Kelebihannya adalah Exadata Smart Flash Cache ini akan bekerja sama dengan database Oracle secara otomatis.  Jika ada tabel atau index tertentu yang dianggap sangat mempengaruhi kinerja, ia akan ditaruh ke dalam cache secara otomatis.

Add comment September 20, 2009

DNS: Mencari Alamat

DNS query ke name resolver dilakukan dengan mengirim packet UDP melalui port standard 53. Berikut ini adalah contoh isi query DNS:

a9 b2 01 00 00 01 00 00 - 00 00 00 0a 64 65 76 65
6c 6f 70 65 72 73 03 73 - 75 6e 03 63 6f 6d 00 00
01 00 01

Dua byte pertama adalah header ID yang bernilai 0xa9b2. Setelah itu adalah dua byte yang berisi flag, yaitu 0×0100. Jika diubah ke dalam bentuk binary, 0×0100 akan menjadi = 0000 0001 0000 0000. Bit pertama, berisi nilai 0, menunjukkan kalau ini adalah query. Bit ini akan bernilai 1 jika packet ini adalah DNS response. Empat bit berikutnya, 0000, menunjukkan ini adalah standard query (nilai 1 untuk inverse query dan 2 untuk server status request). Bit berikutnya secara berurutan menunjukkan nilai flag AA, TC (Truncation), RD (Recursion Desired), RA (Recursion Available), 3 reserved bit yang tidak dipakai, dan 4 bit yang menunjukkan RCODE. Nilai 0 untuk RCODE menunjukkan tidak terjadi kesalahan.

Dua byte berikutnya menujukkan jumlah “pertanyaan” atau alamat yang akan dicari. Nilainya pada contoh diatas adalah 1. Dua byte berikutnya menunjukkan jumlah “jawaban” yang dikirim. Karena ini adalah packet untuk meminta alamat, bukan menjawab, maka nilainya adalah 0. Dua byte berikutnya adalah menunjukkan jumlah server resource records dan dua byte berikutnya menunjukkan jumlah additional resource records. Kedua nilai tersebut umumnya dipakai pada response.

Dua belas byte pertama di atas merupakan bagian dari header. Setelah header, terdapat bagian yang berisi data. Untuk contoh query di atas, bagian ini berisi alamat yang akan dicari. Pada contoh di atas, alamat yang dicari adalah:

0a = 10 desimal
64 65 76 65 6c 6f 70 65 72 73 = developers

03 = 3 desimal
73 75 6e = sun

03 = 3 desimal
63 6f 6d = com

00

Setiap bagian dari nama diawali dengan sebuah byte yang menunjukkan jumlah karakter untuk bagian tersebut. Nama yang akan dicari diakhir dengan byte 00. Pada contoh di atas, nama terdiri atas tiga bagian, yaitu “developers“, “sun” dan “com“, yang jika digabungkan akan menjadi “developers.sun.com

Dua byte berikutnya berisi informasi Query Type. Pada contoh, nilainya adalah 0×0001 yang menunjukkan ini adalah query untuk network address (contoh query lain misalnya query email, dsb). Setelah itu terdapat dua byte yang menunjukkan class, yang nilainya adalah 0×0001 (internet).

Format packet yang berisi respon DNS tidak jauh berbeda dengan query DNS. Pada bagian answers, respon DNS memiliki tiga field tambahan, yaitu empat byte TTL, dua byte data length, dan data (misalnya, IP address yang untuk alamat yang dicari). Selain itu, setelah bagian answer, terdapat banyak bagian authorative nameservers dan additional records. Kedua bagian tersebut memiliki format yang sama seperti bagian answer.

2 comments September 13, 2009

TCP: Pelengkap IP

Internet Protocol (IP) adalah protokol yang tidak reliable. Jika terjadi kesalahan saat mengirim pesan, ia akan mengabaikan packet dan mengembalikan error ICMP saja. Untuk itu, TCP terlahir pada layer transport sebagai protokol yang dapat di-”andal”-kan (reliable). TCP adalah protokol yang rumit, yang terdiri atas sekumpulan software untuk menyediakan layanan komunikasi. Kali ini aku akan mempelajari bagian yang bisa dilihat dengan mata kepala sendiri dari TCP yaitu TCP Protocol Data Unit (PDU). TCP PDU adalah bagian yang dikirim sebagai packet. Berikut ini adalah contohnya:

xx xx 04 11 00 50 68 91   34 ad 00 00 00 00 70 02
40 00 a9 63 00 00 02 04   05 b4 01 01 04 02

Dua byte pertama menunjukkan informasi source port (dalam contoh adalah, 0×0411 atau port 1041) dan dua byte berikutnya menunjukkan informasi destination port (dalam contoh adalah 0×0050 atau port 80). TCP memanfaatkan port untuk membentuk komunikasi yang reliable. Kombinasi antara alamat IP dan nomor port disebut socket. Beberapa port yang berbeda di sebuah host dapat mengakses sebuah destination port yang sama, berkat penggunaan multiplexing. Sebagai contoh, sebuah server web yang membuka port 80 dapat melayani banyak pengunjung tanpa harus membuka port baru yang berbeda untuk setiap pengunjung.

Empat byte berikutnya adalah nilai sequence number. Nilai 0×689134AD disini dihasilkan secara acak. Jika seseorang dalam jaringan dapat menebak dan melanjutkan sequence number, maka ia dapat saja membajak komunikasi TCP kita (oleh sebab itu nilai sequence number diawali dengan angka acak). Empat byte setelah ini adalah acknowledgment number, yang nilainya adalah nilai sequence number untuk packet berikutnya (pada SYN, ini adalah nilai sequence number ditambah dengan 1).

Nilai acknowledgment number pada contoh adalah 0 karena ini adalah sebuah TCP open (awal dari komunikasi/SYN). Berikut ini adalah contoh jawaban dari host tujuan:

xx xx 00 50 04 11 93 cf  35 e5 68 91 34 ae 70 12
83 2c 9c 71 00 00 02 04  05 b4 01 01 01 04 02

Kali ini source port adalah port 80 dan destination adalah port 1041. Disini, host tujuan memiliki nilai sequence number (atau initial sequence number) tersendiri, yaitu 0×93cf35e5. Sementara nilai acknowledgment numbernya adalah 0×689134ae. Ini menunjukkan ia merespon packet yang aku kirim sebelumnya yang memiliki sequence number 0×689134ad.

Kembali ke TCP yang aku kirim pertama kali, empat byte setelah acknowledgment number memiliki nilai 0×7002, atau dalam binary berupa 0111 0000 0000 0010. Empat bit pertama menunjukkan ukuran header dalam satuan 32-bit (4 byte). Nilai disini adalah 0111 (atau 7 desimal) sehingga ukuran header adalah 7 x 4 byte = 28 byte. Empat bit berikutnya harus bernilai 0 (reserved). Setelah itu, setiap bit menunjukkan informasi flag berikut secara berurutan: CWR, ECE, URG, ACK, PSH, RST, SYN, FIN. Pada contoh, bit SYN bernilai 1, menunjukkan bahwa ini adalah awal dari koneksi. Sementara itu, pada contoh packet yang dikirim oleh server web, flag ACK dan SYN bernilai 1.

Dua byte setelah itu adalah ukuran window (range sequence number yang valid untuk diterima). Setelah itu ada dua byte checksum, dan dua byte urgent pointer. Nilai urgent pointer hanya dipakai jika flag URG bernilai 1. Setelah itu ada 8 byte options atau lebih (selalu kelipatan 8) atau tidak ada sama sekali. Setiap options diawali dengan sebuah byte yang disebut option number (nilai 1 untuk NOP, nilai 2 untuk maximum segment size, dsb), kemudian sebuah byte yang menunjukkan jumlah byte untuk option tersebut, lalu diikuti dengan nilai option jika ada.

Untuk membuat sebuah koneksi dari client ke server, TCP menggunakan three-way handshake. Client akan mengirim packet dengan flag SYN bernilai 1 ke server dan nilai sequence number yang acak, misalnya 0×689134ad. Lalu, server akan merespon dengan mengirim packet dengan flag SYN bernilai 1 dan ACK bernilai 1. Selain itu, packet ini juga harus memiliki acknowledgment number yang sesuai, misalnya pada contoh, 0×689134ae. Packet dari server sendiri memiliki sequence number yang acak, misalnya 0×93cf35e5. Berikutnya, client akan merespon dengan mengirim packet dengan flag ACK bernilai 1 ke server. Berdasarkan pada contoh, packet ini memiliki sequence number 0×689134ae dan acknowledgment number 0×93cf35e6. Koneksi baru terbentuk setelah proses handshake ini. Jika digambarkan, mungkin proses handshake akan terlihat seperti ini:

CLIENT                                   SERVER
  | SYN (SEQ=0x689134ad, ACK=0)  ----->   |
  |                                       |
  |                                       |
  | <------   SYN, ACK (SEQ=0x93cf35e5,   |
  |                     ACK=0x689134ae)   |
  |                                       |
  |                                       |
  | ACK (SEQ=0x689134ae,  ------------>   |
  |      ACK=0x93cf35e6)                  |

Setelah ini, proses transfer data dapat dilakukan. Misalnya, seperti pada proses berikut (nilai sequence number disini tidak ada hubungannya dengan yang sebelumnya):

CLIENT                                   SERVER
  |                                       |
  | ACK, PUSH (SEQ=0x38affe14,  ------->  |
  |            ACK=0x114c618c)            |
  |                                       |
  |                                       |
  | <--------- ACK (SEQ=0x114c618c,       |
  |                 ACK=0x38affff3)       |
  |                                       |
  |     DATA                              |
  | <--------- ACK (SEQ=0x114c618c,       |
  |                 ACK=0x38affff3)       |
  |                                       |
  | ACK (SEQ=0x38affff3, ---------------> |
  |      ACK=0x114c66f0)                  |
  |                                       |
  |                                       |
  |     DATA                              |
  | <---------- ACK (SEQ=0x114c66f0,      |
  |                  ACK=0x38affff3)      |
  |                                       |
  | ACK (SEQ=0x38affff3, ---------------> |
  |      ACK=0x114c6c54)                  |
  |                                       |
  |     DATA                              |
  | <---------- ACK (SEQ=0x114c6c54,      |
  |                  ACK=0x38affff3)      |

Koneksi TCP secara normal akan berakhir saat salah satu pihak mengirim packet dengan flag FIN aktif yang meminta penerima agar menutup koneksi. Si penerima akan merespon dengan ACK dan FIN ke pengirim agar sang pengirim juga ikut menutup koneksi.

Add comment September 12, 2009

ASM #5: Memakai Agen

Pada tulisan sebelumnya, class hasil transformasi tidak dapat dipakai secara langsung, melainkan harus memakai interface atau superclass-nya. Bagaimana jika aku tetap ingin memakai seperti biasa tanpa membuat interface atau superclass? Salah satu solusi yang mungkin adalah dengan memakai agent. Class tetap dapat dipakai seperti biasa, bahkan tanpa membuat classloader baru. Hal ini karena agent akan dikerjakan saat JVM dijalankan.

JVM akan mencari dan mengerjakan method milik agent class yang definisinya seperti berikut:

public static void premain(String agentArgs, Instrumentation inst);
atau
public static void premain(String agentArgs);

Berikut ini adalah contoh agent class yang aku pakai:

public class Agent {

  public static void premain(String agentArgs, Instrumentation inst) {
    inst.addTransformer(new ClassFileTransformer(){

      @Override
      public byte[] transform(ClassLoader loader, String className,
          Class<?> classBeingRedefined, ProtectionDomain protectionDomain,
          byte[] classfileBuffer) throws IllegalClassFormatException {

        if (className.equals("latihan/Music")) {
          try {

            ClassReader reader = new ClassReader(classfileBuffer);
            ClassWriter writer = new ClassWriter(reader, 0);
            ToStringAdapter adapter = new ToStringAdapter(writer);

            reader.accept(adapter,0);
            return writer.toByteArray();

          } catch (Exception ex) {
            ex.printStackTrace();
          }
        }
        return classfileBuffer;
      }

    });
  }

}

Agent di atas akan melakukan transformasi melalui ToStringAdapter (turunan dari ClassAdapter) yang sudah aku buat di tulisan sebelumnya. Untuk dapat memakai agent ini, aku masih harus membuat jar dari class ini, serta menambahkan manifest yang isinya sebagai berikut:

Manifest-Version: 1.0
Main-Class: latihan.Main
Premain-Class: latihan.Agent
Boot-Class-Path: lib/asm-3.1.jar lib/asm-commons-3.1.jar lib/asm-util-3.1.jar

Entry yang dibutuhkan untuk agent adalah Premain-Class dan Boot-Class-Path. Fungsinya dapat ditebak dari namanya: Premain-Class sama seperti Main-Class, hanya saja ia menyatakan class mana yang merupakan agent. Boot-Class-Path sama seperti Class-Path, hanya saja ia menyatakan classpath yang khusus dipakai oleh agent.

Sekarang, class Music dapat dipakai layaknya class normal lainnya (tentu saja dengan pengecualian sudah punya definisi method toString() secara otomatis):

public class Main {

  public Main() {
    Music m = new Music();
    m.setMusicID("MID-123");
    m.setTitle("TITLE");
    System.out.println("Music = " + m);
  }

  public static void main(String[] args) throws Exception {
    new Main();
  }

}

Btw, untuk menjalankan program di atas, aku tidak lupa menambahkan argument -javaagent agar JVM mengerjakan agent class, seperti:

java -javaagent:latihanASM.jar -jar latihanASM.jar

Add comment September 5, 2009

ASM #4: Ready For Action

Setelah mempelajari cara kerja JVM dan bytecode Java, aku siap untuk melakukan modifikasi class Java secara dinamis. Sebagai contoh kasus, aku akan mengubah method toString() untuk setiap bean agar menampilkan nilai dari setiap field secara otomatis.

Seperti yang telah aku catat sebelumnya, sebuah class dikenali oleh JVM melalui kombinasi dari classloader dan nama class-nya. Untuk melakukan instrumentasi bytecode, aku pasti membuat classloader sendiri. Sebagai efek sampingnya, class hasil instrumentasi tidak akan bisa dipakai oleh class yang di-load oleh JVM sebelumnya. Yup, tidak dapat dipakai secara langsung, tetapi dapat dipakai melalui superclass atau interface-nya. Aku akan mencoba memakai metode interface di tulisan kali ini. Next time, aku akan mencoba memakai agent untuk melakukan instrumentasi class yang di-load JVM. Dengan agent, aku tidak perlu memakai superclass atau interface, tetapi proses menjalankan program menjadi lebih panjang.

Byte code instrumentation dengan interface memang tidak begitu cocok untuk contoh kasus kali ini, tapi setidaknya ini cara yang paling gampang. Aku akan membuat interface Music dan implementasi-nya MusicImpl.

public interface Music {

  public String getMusicID();
  public void setMusicID(String musicId);
  public String getTitle();
  public void setTitle(String title);

}

Lalu, aku akan membuat classloader yang melakukan instrumentasi untuk MusicImpl seperti berikut:

import java.util.*;
import org.objectweb.asm.*;
import static org.objectweb.asm.Opcodes.*;

public class MyClassLoader extends ClassLoader {

  @Override
  protected Class<?> findClass(String name) throws ClassNotFoundException {
    if (name.endsWith("Impl")) {
      try {
        ClassReader reader = new ClassReader(name);
        ClassWriter writer = new ClassWriter(reader,0);
        ToStringAdapter adapter = new ToStringAdapter(writer);
        reader.accept(adapter,0);
        byte b[] = writer.toByteArray();
        return defineClass(name,b,0,b.length);

      } catch (Exception e) {
        e.printStackTrace();
      }

    }
    return super.findClass(name);
  }

  protected class ToStringAdapter extends ClassAdapter {

    private Map<String,String> mapFields;
    private String className;

    public ToStringAdapter(ClassVisitor cv) {
      super(cv);
      mapFields = new HashMap<String,String>();
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc,
        String signature, String[] exceptions) {
      if (name.equals("toString")) {
        return null;
      }
      return super.visitMethod(access, name, desc, signature, exceptions);
    }

    @Override
    public void visitEnd() {
      MethodVisitor mv = cv.visitMethod(ACC_PUBLIC, "toString", "()Ljava/lang/String;", null, null);
      mv.visitCode();
      mv.visitTypeInsn(NEW, "java/lang/StringBuilder");
      mv.visitInsn(DUP);
      mv.visitLdcInsn(className + ": ");
      mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "(Ljava/lang/String;)V");

      for (String field : mapFields.keySet()) {
        mv.visitLdcInsn(field + "=[");
        mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append",
          "(Ljava/lang/String;)Ljava/lang/StringBuilder;");

        mv.visitVarInsn(ALOAD, 0);
        mv.visitFieldInsn(GETFIELD, className, field, mapFields.get(field));
        mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append",
          "(Ljava/lang/Object;)Ljava/lang/StringBuilder;");

        mv.visitLdcInsn("]; ");
        mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append",
          "(Ljava/lang/String;)Ljava/lang/StringBuilder;");

      }

      mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString",
        "()Ljava/lang/String;");
      mv.visitInsn(ARETURN);
      mv.visitMaxs(3,1);
      mv.visitEnd();
      super.visitEnd();
    }

    @Override
    public FieldVisitor visitField(int access, String name, String desc,
        String signature, Object value) {
      if (access==ACC_PRIVATE) {
        mapFields.put(name,desc);
      }
      return super.visitField(access, name, desc, signature, value);
    }

    @Override
    public void visit(int version, int access, String name,
        String signature, String superName, String[] interfaces) {
      className = name;
      super.visit(version, access, name, signature, superName, interfaces);
    }

  }

}

Pada classloader tersebut, aku akan menambahkan method toString() pada semua class yang namanya diakhiri dengan “Impl”. Jika class tersebut sudah memiliki method toString(), aku akan menghapus method toString() yang sudah ada sebelum menambahkan yang baru. Untuk mempersingkat kode program, aku menganggap bahwa class yang di-transformasi memiliki field turunan dari Object saja, bukan tipe native seperti int, char, dsb. Hal ini karena aku men-hard code method append() yang aku panggil dari class StringBuilder dengan descriptor (Ljava/lang/Object;)Ljava/lang/StringBuilder;

Sekarang aku dapat mencoba memakai classloader di atas, misalnya dengan kode sederhana berikut:

public class Main {

  public Main() throws Exception {
    MyClassLoader myCL = new MyClassLoader();
    Music m = (Music) myCL.findClass("latihan.MusicImpl").newInstance();
    m.setMusicID("MID-123");
    m.setTitle("TITLE");
    System.out.println("Music = " + m);
  }

  public static void main(String[] args) throws Exception {
    new Main();
  }

}

Dan outputnya adalah:

Music = latihan/MusicImpl: title=[TITLE]; musicID=[MID-123];

Hal ini juga berlaku untuk seluruh class lain yang namanya diakhir dengan “Impl”.. Aku tidak perlu lagi membuat method toString() secara manual..

Add comment September 5, 2009

Eclipse: Membuat Aplikasi JEE

Aplikasi JEE yang aku maksud disini adalah aplikasi yang di-deploy dalam bentuk file EAR. Ada banyak cara yang dapat ditempuh dalam membuat program JEE, seperti melalui Ant script. Yang akan aku coba disini adalah cara yang paling otomatis dan paling ter-integrasi dengan Eclipse + plugin JST (J2EE Standard Tool) dan WST (Web Standard Tool). Kedua plug-in tersebut adalah plug-in dasar yang membentuk “Eclipse for JEE”. Jika aku hanya memiliki Eclipse JDT (Java Development Tool), versi standar untuk JSE, aku tetap bisa men-coding program JEE, tentunya harus deploy dengan cara manual: Ant script; serta tanpa syntax highlighting dan editor khusus untuk komponen JEE. Lagipula, semua terserah ke cara yang paling disukai individu masing-masing, mungkin saja ada programmer yang suka men-coding EJB di notepad/vim, men-jar manual, men-deploy (baca: men-copy) dengan mengetik di command prompt, dst… Banyak cara untuk menempuh tujuan; yang membedakan hanya seberapa rumitnya (tingkat kerennya) dan seberapa letihnya :) Tentunya, jika seseorang memilih cara yang paling efisien, ia dapat meningkatkan produktifitasnya dan dapat pulang lebih awal (seharusnya?).

Langkah pertama yang aku lakukan agar pengembangan aplikasi di JEE bisa lebih terintegrasi dengan Eclipse adalah mendefinisikan sebuah server. Caranya adalah dengan berpindah ke perspective “Java EE” (hanya ada jika meng-install plugin WST). Lalu, aku memilih menu “Window“, “Show View“, dan memilih “Servers“. Pada window “Servers” yang muncul, aku men-klik kanan mouse, memilih menu “New“, “Server“. Lalu akan muncul dialog “New Server“. Langkah pertama adalah memilih server JEE. Karena aku memakai JBoss 5, aku memilih “JBoss v5.0“. Setelah itu aku dapat memberi nama pada server, menambah server runtime environment dengan men-klik link “Add…” (dimana aku dapat memilih JRE yang dipakai, dan lokasi instalasi JBoss). Pada halaman berikutnya, aku dapat men-konfigurasi alamat server JBoss, port yang dipakai, dan server configuration yang dipakai (misalnya: default, all, minimal, dsb).

Untuk menguji apakah server JBoss dapat dijalankan, aku men-klik kanan pada nama server yang barusan aku definisikan, lalu memilih menu “Start“. Eclipse akan segera menjalankan run.jar milik JBoss, sama seperti yang dilakukan oleh run.bat saat aku menjalankan JBoss di command prompt. Aku cukup terkejut saat sesaat kemudian, console Eclipse memunculkan banyak pesan kesalahan dan server JBoss gagal dijalankan! Kenapa bisa begitu? Aku men-double click nama server di window “Server” sehingga muncul editor baru di workbench Eclipse. Lalu aku memilih link “Open launch configuration“. Disini aku dapat mengatur pengaturan classpath dan parameter yang di-pass ke JVM saat Eclipse menjalankan run.jar milik JBoss. Aku membandingkan parameter yang ada disini, dengan parameter yang ada di run.bat. Akhirnya aku menemukan satu perbedaan, yaitu pada parameter java.endorsed.dirs. Akupun segera menambahkan baris berikut di bagian “VM Arguments“:

-Djava.endorsed.dirs="C:\jboss-5.0.0.GA\lib\endorsed"

Dimana, “C:\jboss-5.0.0.GA” adalah direktori yang berisi instalasi JBoss. Setelah men-klik tombol “OK”, aku menjalankan ulang server JBoss. Kali ini server dapat dijalankan dengan mulus tanpa masalah. Well done! Sekarang aku dapat menjalankan (dengan “Start“), mematikan (dengan “Stop“), dan mendebug (dengan “Debug“) server JBoss langsung dari dalam Eclipse.

Berikutnya, aku dapat membuat sebuah project EAR dengan memilih menu “File“, “New“, “Enterprise Application Project“. Pada window “New EAR Application Project” yang muncul, aku dapat mengisi nama project, menentukan server JEE yang dipakai (btw, aku tidak dapat memakai Tomcat karena Tomcat tidak mendukung EAR), dan menentukan EAR version. Setelah itu pada halaman berikutnya, aku dapat menentukan module apa saja yang dimiliki oleh project ini. Pilihannya ada empat, yaitu “Application client module” (untuk aplikasi client seperti applet atau swing yang mengakses layanan JEE nantinya), “EJB module“, “Web module“, dan “Connector module“. P.S: Tomcat disebut tidak mendukung JEE secara penuh, karena ia hanya mendukung “Web module” yang outputnya berupa file WAR. Setelah men-klik tombol “Finish“, aku akan mendapatkan lebih dari satu project! Ada sebuah EAR project, dan sisanya adalah project yang berisi facet module untuk EAR project tersebut.

Langkah berikutnya adalah membuat Eclipse secara otomatis men-deploy aplikasi EAR ke server JBoss. Aku kembali membuka window “Servers” dan men-klik kanan pada server JBoss, kemudian memilih menu “Add and Remove…” Pada bagian “Available“, aku memilih EAR project yang barusan aku buat, kemudian men-klik tombol “Add >“. Setelah men-klik “Finish“, aku kembali menjalankan server JBoss. Kali ini, sebelum program JBoss dipanggil, Eclipse akan men-build file EAR terlebih dahulu, kemudian men-copy file tersebut ke folder “deploy” milik JBoss. Semuanya berlangsung secara otomatis!

Sebuah project JEE telah ter-setup dengan baik. Aku tinggal memulai proses development dengan men-koding pada setiap module yang merupakan facet dari EAR project. Misalnya, jika aku sebelumnya memilih “Web module“, aku dapat mulai dengan menambahkan file JSP di folder “WebContent” di module project tersebut. Aku dapat langsung memanggil JSP tersebut dari browser tanpa harus me-restart server, karena Eclipse akan mendeteksi perubahan setiap selang waktu tertentu dan secara otomatis men-deploy EAR yang baru. Proses development pun bisa berlangsung nyaman dan mudah, setidaknya seharusnya begitu :)

Add comment Agustus 29, 2009

Previous Posts


 

Februari 2010
S S R K J S M
« Jan    
1234567
891011121314
15161718192021
22232425262728

Kategori

Tulisan Terakhir

Arsip