วันพฤหัสบดีที่ 30 เมษายน พ.ศ. 2558

Java RMI ตอนที่ 3: การเขียนโปรแกรมฝั่งไคลเอนต์และการรันโปรแกรม

มาถึงตอนสุดท้ายซะที ส่วนใครที่ยังไม่ได้อ่านตอน 1 และตอน 2 ก็เชิญไปอ่านกันก่อนนะครับ ในตอนนี้เราจะมาเขียนโปรแกรมฝั่งไคลเอนต์และลองรันโปรแกรมกันครับ มาดูโปรแกรมฝั่งไคลเอนต์กันก่อนครับ

 1 import java.rmi.*;
 2 import java.net.*;
 3 import java.rmi.registry.LocateRegistry;
 4 import java.rmi.registry.Registry;
 5
 6
public class MyMathClient
 7 {
 8    public static void main(String[] args)
 9    {
10       if (args.length == 0)
11       {
12          System.err.println("Usage java MyMathClient <server-hostname>");
13       }
14       try
15       {
16          Registry registry = LocateRegistry.getRegistry(args[0]);
17          MyMath stub = (MyMath) registry.lookup("MyMathServer");
18          System.out.println(stub.add(3,2));

19         
20       }
21       catch (Exception e)
22       {
23          System.err.println("System Exception" + e);
24       }
25    }
26 }

จากโปรแกรมในบรรทัดที่ 16  เป็นการค้นหาอาร์เอ็มไอรีจิสตรี (RMI registry) ในเครื่องที่อ็อบเจกต์เซิร์ฟเวอร์รันอยู่ เราจะรันโปรแกรมนี้โดยระบุชื่อเครื่องเซิร์ฟเวอร์มาเป็นพารามิเตอร์บรรทัดคำสั่ง (command line parameter) และส่งชื่อเครื่องเซิร์ฟเวอร์นี้ให้กับเมท็อด getRegistry() บรรทัดที่ 17 ค้นหาอ็อบเจกต์โดยระบุชื่ออ็อบเจกต์เป็นชื่อเดียวกันกับที่ฝั่งเซิร์ฟเวอร์ลงทะเบียนไว้ ในที่นี้คือ MyMathServer ซึ่งที่เราจะได้กลับมาจากการเรียกเมท็อด lookup() ในบรรทัดนี้ก็คืออ็อบเจ็กต์เรฟเฟอร์เรนซ์ (object reference) ไปยังอ็อบเจกต์บนฝั่งเซิร์ฟเวอร์ ให้สังเกตว่าเราใช้ชื่อของอินเทอร์เฟซ (interface) คือ MyMath ไม่ใช่ชื่อของคลาสผู้รับใช้ (servant class) MyMathImpl หลังจากนี้เราก็สามารถเรียกใช้เมท็อดของอ็อบเจกต์บนฝั่งเซิร์ฟเวอร์ได้ราวกับอ็อบเจกต์นั้นอยู่บนเครื่องเดียวกับไคลเอนต์ ดังแสดงในบรรทัดที่ 18 ซึ่งเป็นการเรียกใช้เมท็อด add()

คราวนี้ก็มารันโปรแกรมกันครับ เริ่มจากรัน rmiregistry ก่อน วันนี้ขอแสดงการรันบน Mac นะครับ คำสั่งที่ใช้ก็คือ rmiregistry & เพื่อให้โปรแกรม rmiregistry ไปทำงานอยู่หลังฉาก ดังแสดงในรูปต่อไปนี้ครับ



ถ้าใช้ Windows ก็เปิดคอมมานด์พรอมพท์ของวินโดวส์แล้วก็ใช้ start rmiregistry ได้ครับ 

หลังจากนั้นก็รันโปรแกรมเซิร์ฟเวอร์ก็เหมือนการรันโปรแกรมปกติครับคือ 

java MyMathServer

ก็จะได้ผลลัพธ์ตามรูป





และจากนั้นก็รันไคลเอนต์ java MyMathClient ซึ่งก็จะได้ผลลัพธ์ตามรูปครับ 



เอาล่ะครับจบแล้ว ก็หวังว่าจะเป็นความรู้เบื้องต้นให้ทำตามแล้วนำไปต่อยอดกันต่อได้นะครับ...

สำหรับใครที่ไม่อยากป้อนโปรแกรมเองก็สามารถดาวน์โหลดได้จากที่นี่ครับ 









วันพุธที่ 29 เมษายน พ.ศ. 2558

Java RMI ตอนที่ 2: การเขียนโปรแกรมฝั่งเซิร์ฟเวอร์

หลังจากเข้าใจว่าจาวาอาร์เอ็มไอ (Java RMI) คืออะไรแล้วจากตอนที่ 1 ในตอนนี้เราจะเริ่มมาพัฒนาโปรแกรมกันครับ โดยเราจะเริ่มจากฝั่งเซิร์ฟเวอร์กันก่อน โดยเราจะต้องคิดว่าอ็อบเจกต์ที่เราจะพัฒนาขึ้นจะให้บริการอะไรบ้าง โดยเราจะต้องเขียนรายการแอ็บแสตรกท์เมท็อด (abstract method) ที่จะให้บริการข้ามเครื่องในรูปแบบอินเทอร์เฟซ (interface) และไม่ใช่อินเทอร์เฟซธรรมดาจะต้องเป็นอินเทอร์เฟซระยะไกล (remote interface) ด้วย การประกาศอินเทอร์เฟซระยะไกลทำได้ง่ายมากด้วยการใช้รูปแบบการประกาศอินเทอร์เฟซของจาวาตามปกติ แต่ให้อินเทอร์เฟซนั้นเอ็กซ์เทนด์ (extend) อินเทอร์เฟซที่จาวาเตรียมไว้ให้แล้วที่ชื่อว่า Remote สรุปก็คือรูปแบบการประกาศอินเทอร์เฟซระยะไกลมีรูปแบบดังนี้

interface <interface name> extends Remote {...}

ไม่เอาดีกว่าอธิบายแบบนี้อาจไม่เข้าใจ มาดูตัวอย่างกันดีกว่า สมมติเราจะสร้างอ็อบเจกต์ที่จะให้บริการขึ้นมาสักตัวหนึ่ง เราจะสร้างอะไรกันดีครับ เอา Hello Wolrd ดีไหม... ไม่ดีนะครับน่าเบื่อ เอาอย่างนี้แล้วกันสร้างอ็อบเจกต์ที่ให้บริการบวกลบเลขดีไหมครับ น่าเบื่อน้อยลงหน่อย เอาตามนี้นะครับ มาสร้างกันเลย จากอ็อบเจกต์ที่เราต้องการสามารถสร้างอินเทอร์เฟซระยะไกลได้ดังนี้

1 import java.rmi.Remote;
2 import java.rmi.RemoteException;
3 interface MyMath extends Remote {
  public int add(int num1, int num2) throws RemoteException;
  public int subtract(int num1, int num2) throws RemoteException;
6 }


จากโค้ดตัวอย่างก็คือเราสร้างอินเทอร์เฟซระยะไกลชื่อ MyMath ซึ่งมีแอ็บสแตรกท์เมท็อดสองตัวคือ add() และ subtract() ให้สังเกตว่าแต่ละเมท็อดที่เราจะให้ถูกเรียกใช้จากระยะไกลได้จะต้องโทรว์ (throw) เอ็กซ์เซปชัน (Exception) ที่ชื่อว่า RemoteException

ตอนนี้เราจะเริ่มสร้างคลาสกันครับ ซึ่งคลาสที่เราจะสร้างบนฝั่งเซิร์ฟเวอร์นี้เรามักจะสร้างแบ่งเป็นสองคลาสครับ คือคลาสที่เรียกว่าคลาสผู้รับใช้ (servant class) และคลาสผู้บริการ (server class) คลาสผู้รับใช้เป็นคลาสที่เราอิมพลีเมนต์อินเทอร์เฟซ หรือพูดง่าย ๆ ก็คือมันเป็นคลาสที่สร้างอ็อบเจกต์ที่จะถูกเรียกใช้จากไคลเอนต์ นี่คือเหตุผลที่เราเรียกมันว่าคลาสผู้รับใช้ นอกจากนี้สตับและสเกเลตันจะถูกสร้างจากคลาสนี้เช่นกัน ส่วนคลาสผู้บริการจะเป็นคลาสที่ใช้ในการลงทะเบียนคลาสผู้รับใช้เข้ากับอาร์เอ็มไอรีจีสตรี (RMI Registry) เอาละครับมาเขียนคลาสผู้รับใช้กันดีกว่า

 1 import java.rmi.RemoteException;
 2 import java.rmi.server.UnicastRemoteObject;
 3 public class MyMathImpl extends UnicastRemoteObject implements MyMath  {
 4    public MyMathImpl() throws RemoteException {
 5      super();
 6    }
 7    public int add(int num1, int num2) throws RemoteException  {
 8       return num1+num2;
 9    }
10    public int subtract(int num1, int num2)throws RemoteException {
11       return num1-num2;
12    }
13
14
}

ข้อสังเกตสำหรับคลาสนี้มีสามเรื่องครับ เรื่องแรกก็คือชื่อของคลาส (บรรทัดที่ 3) นักเขียนโปรแกรมที่พัฒนาจาวาอาร์เอ็มไอนิยมตั้งกันคือคลาสจะมีชื่อเดียวกับอินเทอร์เฟซตามด้วย Impl จากโปรแกรมที่เรากำลังพัฒนาอินเทอร์เฟซของเรามีชื่อว่า MyMath ดังนั้นเราจึงตั้งชื่อคลาสนี้ว่า MyMathImpl เรื่องที่สองก็คือคลาสนี้สืบทอดคุณสมบัติมาจากคลาส UnicastRemoteObject ซึ่งเป็นคลาสที่จาวาเตรียมเอาไว้เพื่อสนับสนุนการเรียกอ็อบเจกต์ข้ามเครื่อง เรื่องที่สามก็คือเราต้องมีคอนสตรักเตอร์ (constructor) ที่โทรว์ RemoteException และเรียกคอนสตรักเตอร์ของคลาส UnicastRemoteObject เอาละครับเรียบร้อยครับ เห็นไหมครับว่าง่ายมาก

หลังจากเราเขียนคลาสผู้รับใช้เสร็จแล้วเราก็คอมไพล์เพื่อให้ได้ MyMathImpl.class ครับ ถ้าเราใช้จาวาก่อนเวอร์ชัน 5 เราก็ใช้คำสั่งนี้ในการสร้างสตับและสเกเลตันได้เลยครับ

rmic MyMathImpl

เราจะได้ไฟล์ชื่อ MyMathImpl_Stub.class ซึ่งไฟล์นี้จะทำหน้าที่เป็นทั้งสตับและสเกเลตัน ซึ่งผมว่าตอนนี้เราคงไม่ใช้ภาษาจาวาเก่าแบบนั้นแล้วนะครับ ดังนั้นข้ามไปก็ได้เพราะในจาวารุ่นใหม่จาวารันไทม์ (Java Runtime) จะจัดการให้

คราวนี้มาดูคลาสผู้ให้บริการกัน

 1 import java.net.*;
 2 import java.rmi.*;
 3 import java.rmi.registry.Registry;
 4 import java.rmi.registry.LocateRegistry;
 5 public class MyMathServer 
 6 {
 7    public static void main(String[] args)
 8    {
 9      
10       try
11       {
12          MyMathImpl myMathObj = new MyMathImpl();
13          Registry registry = LocateRegistry.getRegistry();
14          registry.bind("MyMathServer", myMathObj);
15          System.out.println("MyMath Server ready");
16       }
17       catch (Exception re) {
18          System.out.println("Execption in MyMathServer: " + re);
19       }
20    }
21 }

จากโปรแกรมจะเห็นว่าเราสร้างอ็อบเจกต์เรฟเฟอร์เรนซ์ (object referennce) ของคลาส MyMathImpl ในบรรทัดที่ 12 จากนั้นบรรทัดที่ 13 ประโยค

Registry registry = LocateRegistry.getRegistry();

เป็นการค้นหาอาร์เอ็มไอรีจิสตรีที่รันอยู่ในระบบ นั่นหมายความว่าก่อนจะรันโปรแกรมเซิร์ฟเวอร์เราต้องรันอาร์เอ็มไอรีจิสตรีก่อนซึ่งจะรันยังไงนั้นจะอยู่ในตอนต่อไปครับ บรรทัดที่ 14 ประโยค

 registry.bind("MyMathServer", myMathObj);

เป็นการลงทะะเบียนอ็อบเจกต์เรฟเฟอร์เรนซ์ดังกล่าวในชื่อ MyMathServer ซึ่งโปรแกรมฝั่งไคลเอนต์ที่เราจะเขียนในตอนต่อไปจะค้นหาอ็อบเจกต์นี้โดยใช้ชื่อนี้

เอาล่ะครับตอนนี้เราก็เสร็จงานด้านเซิร์ฟเวอร์แล้ว ตอนต่อไปเราจะไปเขียนไคลเอนต์ แล้วก็ลองรันกันดูครับ โปรดอดใจสักนิดหนึ่งนะครับ ขอพักหน่อย ถ้าใครไม่อยากพิมพ์โปรแกรมเองสามารถดาวน์โหลดโค้ดได้จากลิงก์นี้ครับ


วันอังคารที่ 28 เมษายน พ.ศ. 2558

Java RMI ตอนที่1: Java RMI คืออะไร

การเรียกใช้ฟังก์ชันที่อยู่บนคอมพิวเตอร์เครื่องอื่นในปัจจุบันกลายเป็นเรื่องที่เราใช้กันแทบจะเป็นปกติในปัจจุบันกันแล้ว เช่นเราเขียนโปรแกรมเรียกใช้ฟังก์ชันของ Google Maps  เพื่อนำแผนที่มาใช้กับโปรแกรมของเราโดยเราไม่ต้องรู้เลยว่า แผนที่นั้นจัดเก็บอย่างไร ฟังก์ชันที่เราเรียกใช้เขียนขึ้นมาด้วยภาษาอะไร และโปรแกรมแผนที่ทำงานอยู่บนระบบปฏิบัติการอะไร ซึ่งเทคโนโลยีที่เราใช้เป็นพื้นฐานของการทำงานดังกล่าวในปัจจุบันก็คือเทคโนโลยีที่เรียกว่าเว็บเซอร์วิส (web service) นั่นเอง ซึ่งผมก็หวังว่าจะหาเวลามาเขียนให้ได้อ่านกันสักวันหนึ่ง 

จริง ๆ แล้วการเรียกใช้ฟังก์ชันที่ทำงานอยู่บนเครื่องคอมพิวเตอร์เครื่องอื่นในเครือข่ายไม่ได้เพิ่งมาเกิดขึ้นเพราะเว็บเซอร์วิส แต่แนวคิดดังกล่าวมีมานานแล้ว และก็มีเทคโนโลยีต่าง ๆ ที่ถูกพัฒนาขึ้นเพื่อนำมาใช้ในงานดังกล่าว ซึ่งเว็บเซอร์วิสก็ถูกพัฒนาขึ้นมาโดยใช้เทคโนโลยีเหล่านี้เป็นฐาน เทคโนโลยีดังกล่าวเริ่มมาตั้งแต่ การเรียกโพรซีเยอร์จากระยะไกล (Remote Procedure Call) หรือเรียกย่อ ๆ ว่าอาร์พีซี (RPC)  การเรียกใช้เมท็อดจากระยะไกล (Remote Method Invocation) หรือเรียกย่อ ๆ ว่าอาร์เอ็มไอ (RMI) และ Common Object Request Broker Architecture  (CORBA) (อันนี้ขอไม่แปลแล้วกันนะครับ ยังคิดคำแปลที่ถูกใจไม่ได้:)) เป็นต้น ซึ่งผมหวังว่าจะมีโอกาสได้เขียนให้ได้อ่านกันต่อไป แต่ในวันนี้ผมจะมาพูดถึง อาร์เอ็มไอครับ 

อาร์เอ็มไอก็คือเทคโนโลยีที่พัฒนาขึ้นมาเพื่อให้เราเขียนโปรแกรมเพื่อเรียกใช้เมท็อดจากออบเจ็กต์ที่อยู่บนคนละเครื่องกับโปรแกรมของเรา โดยอาร์เอ็มไอจะช่วยให้การเรียกใช้ออบเจ็กต์ดังกล่าวทำได้ในลักษณะที่เหมือนกับการเรียกใช้เมท็อดจากอ็อบเจกต์ที่อยู่บนเครื่องเดียวกันกับโปรแกรมของเรา นั่นคือ อาร์เอ็มไอช่วยให้เราไม่ต้องไปยุ่งเกี่ยวกับเรื่องพื้นฐานของระบบเครือข่าย เช่นซ็อกเก็ตไม่ต้องยุ่งเกี่ยวกับการแปลงข้อมูลเพื่อให้อยู่ในรูปแบบที่ส่งผ่านเครือข่ายได้ ไม่ต้องยุ่งเกี่ยวกับการที่จะต้องเขียนโปรแกรมเพื่อค้นหาและเรียกใช้เมท็อดจากออกเจ็กต์ที่อยู่ในเครือข่ายด้วยตัวเราเอง (ซึ่งถ้าต้องทำเองนี่ไม่สนุกแน่ครับ) ดังนั้นเราจึงสามารถที่จะมุ่งความสนใจไปในการเขียนโปรแกรมเพื่อแก้ปัญหาที่เราต้องการเพียงอย่างเดียว   

แน่นอนครับ ภาษาจาวา (Java) ซึ่งถูกพัฒนาขึ้นมาให้เป็นภาษาที่รองรับการเขียนโปรแกรมผ่านเครื่อข่ายย่อมไม่พลาดที่จะสนับสนุนแนวคิดนี้ และก็มีเทคโนโลยีที่เรียกว่า จาวาอาร์เอ็มไอ (Java RMI) มาให้เราได้ใช้กันจาวาอาร์เอ็มไอ จะทำให้อ็อบเจกต์ที่ถูกเขียนขึ้นมาด้วยภาษาจาวาแต่ทำงานกันอยู่บนคนละเครื่องในเครือข่าย สามารถที่จะค้นหากันและเรียกใช้กันได้เหมือนกับว่ามันทำงานอยู่บนเครื่องเดียวกัน โดยสถาปัตยกรรมของจาวาอาร์เอ็มไอแสดงได้ดังรูปต่อไปนี้ครับ 


RMI Architecture


จากรูปขออธิบายคร่าว ๆ ดังนี้นะครับไคลเอนต์ (Client)  คือโปรแกรมที่เรียกใช้อ็อบเจกต์ที่อยู่บนอีกเครื่องหนึ่ง เซิร์ฟเวอร์ (Server) ก็คืออ็อบเจกต์ที่ต้องการจะเรียกใช้ สตับ (Stub) คือส่วนที่ทำหน้าที่เป็นตัวแทนของอ็อบเจกต์ที่เราจะเรียกใช้ คือไคลเอนต์จะเรียกใช้อ็อบเจกต์บนฝั่งเซิร์ฟเวอร์ผ่านทางสตับ ส่วนสเกเลตัน (Skeleton) คือตัวแทนของไคลเอนต์บนฝั่งเซิร์ฟเวอร์ สเกเลตันจะเป็นส่วนที่เรียกใช้อ็อบเจกต์บนฝั่งเซิร์ฟเวอร์ ถ้าจะสรุปให้เข้าใจง่าย ๆ ก็คือไคลเอนต์เรียกใช้อ็อบเจกต์ที่ต้องการผ่านทางสตับ สตับจะติดต่อกับสเกเลตันเพื่อให้เรียกใช้อ็อบเจกต์ดังกล่าว 

ทั้งสตับและสเกเลตันยังมีหน้าที่ในการแปลงข้อมูลการเรียกใช้ และค่าที่ส่งกลับมาจากการทำงานของอ็อบเจกต์ให้อยู่ในรูปที่ส่งผ่านเครือข่ายได้ (ข้อมูลที่ส่งผ่านเครือข่ายจะต้องอยู่ในรูปของสายธารตัวอักษร (stream of byte)) และแปลงข้อมูลที่ได้รับจากเครือข่ายให้กลับมาในรูปแบบที่โปรแกรมเข้าใจ ซึ่งการทำงานดังกล่าวเรียกว่า มาร์แชล (Marshal) และ อันมาร์แชล (Unmarshal) ตามลำดับ  

ตัวอย่างการทำงานของสตับและสเกเลตันแสดงได้ดังตัวอย่างนี้ สมมติโปรแกรมฝั่งไคลเอนต์เขียนประโยคต่อไปนี้เพื่อเรียกใช้อ็อบเจกต์บนฝั่งเซิร์ฟเวอร์ 

obj.add(1, 2);  

สตับจะแปลงประโยคดังกล่าวให้อยู่ในรูปสายธารของตัวอักษรเพื่อส่งผ่านเครือข่ายไป เมื่อการเรียกใช้ดังกล่าวไปถึงฝั่งเซิร์ฟเวอร์ สเกเลตันจะแปลงประโยคดังกล่าวกลับมาและเรียกใช้เมท็อด add()  ถ้าเมท็อด add() ต้องส่งค่ากลับให้ไคลเอนต์ สเกเลตันจะแปลงค่าที่จะส่งกลับนั้นให้อยู่ในรูปของสายธารตัวอักษร และส่งผ่านเครือข่ายไปเมื่อข้อมูลมาถึงฝั่งไคลเอนต์ สตับก็จะแปลงข้อมูลกลับให้อยู่ในรูปที่โปรแกรมเข้าใจและส่งผ่านให้ไคลเอนต์ต่อไป 

อีกสองส่วนจากรูปที่ยังไม่ได้กล่าวถึงคือ รีจิสตรีอินเทอร์เฟซ (registry interface) และอาร์เอ็มไอรีจิสตรี (RMI registry) ซึ่งจะขอเรียกสั้น ๆ ว่ารีจิสตรี รีจิสตรีถ้าพูดง่าย ๆ ก็คือโปรแกรมที่ช่วยลงทะเบียนและค้นหาอ็อบเจ็กต์ กล่าวคืออ็อบเจกต์ที่ต้องการจะให้บริการผ่านเครือข่ายจะต้องลงทะเบียนกับรีจิสตรี ซึ่งการลงทะเบียนกับรีจิสตรีนั้นก็คือการเชื่อมชื่อของอ็อบเจกต์เข้ากับอ็อบเจกต์เรฟเฟอร์เรนซ์ (object reference) ซึ่งเมื่อไคลเอนต์ต้องการค้นหาอ็อบเจกต์ ก็จะเอาชื่ออ็อบเจกต์ที่ต้องการมาค้นในรีจิสตรี  และรีจิสตรีก็จะส่งอ็อบเจกต์เรฟเฟอร์เรนซ์กลับไปให้กับไคลเอนต์ เพื่อให้ไคลเอนต์เรียกใช้อ็อบเจกต์ที่ต้องการได้ ไคลเอนต์และเซิร์ฟเวอร์จะติดต่อกับรีจิสตรีผ่านทางรีจิสตรีอินเทอร์เฟซ 

ถึงตรงนี้ถ้ายังงงอยู่ไม่เป็นไรครับ ผมคิดว่าน่าจะเห็นภาพชัดเจนขึ้นเมื่อเราได้เขียนโปรแกรมกันจริง ๆ ในตอนต่อไป แต่ตอนนี้ผมขอตอบคำถามที่หลายคนอาจจะสงสัยกันนะครับว่า สรุปแล้วการเขียนโปรแกรมจาวาอาร์เอ็มไอนี่เราต้องเขียนโปรแกรมตรงไหนบ้าง เราต้องเขียนโปรแกรมสตับและสเกเลตันเองหรือเปล่า อาร์เอ็มไอรีจิสตรีจะไปหามาจากไหน คำตอบคืออย่างนี้ครับ โปรแกรมอาร์เอ็มไอรีจิสตรีจะมากับจาวาเจดีเค  (Java JDK) ที่เราโหลดมาใช้ในการพัฒนาโปรแกรมด้วยภาษาจาวาอยู่แล้ว ส่วนสตับและสเกเลตันเราก็ไม่ต้องเขียนเองครับ ถ้าเราใช้จาวาก่อนเวอร์ชัน 5 เราจะใช้โปรแกรมที่ชื่อว่าอาร์เอ็มไอซี (rmic) ช่วยสร้างให้เรา ซึ่งโปรแกรมนี้ก็มากับจาวาเจดีเคอีกเช่นกัน แต่ตั้งแต่จาวาเวอร์ชัน 5.0 เป็นต้นไปเราไม่ต้องใช้ rmic แล้วนะครับเพราะสตับและสเกเลตันจะถูกจัดการโดนจาวารันไทม์ (Java Runtime)

สรุปก็คือในการเขียนจาวาอาร์เอ็มไอเราแทบจะเขียนโปรแกรมภาษาจาวาไปตามปกติ ผมใช้คำว่าแทบจะก็เพราะมันอาจจะมีบางส่วนที่ต้องศึกษาเพิ่มเติม เช่นการติดต่อกับรีจิสตรีอินเทอร์เฟซ การเขียนอินเทอร์เฟซเพื่อให้รองรับการเรียกใช้แบบข้ามเครื่องเป็นต้น 

เมื่อถึงตรงนี้ก็หวังว่าจะเห็นภาพรวมของจาวาอาร์เอ็มไอกันแล้วนะครับ ผมก็ขอจบแต่เพียงเท่านี้ก่อน เขียนมานานจนเมื่อยแล้ว พบกันใหม่เมื่อชาติต้องการ เอ๊ยไม่ใช่พบกันใหม่ในตอนที่สองครับ เราจะมาสร้างอ็อบเจกต์บนฝั่งเซิร์ฟเวอร์กัน


วันพุธที่ 25 มีนาคม พ.ศ. 2558

การใช้งาน make เบื้องต้น

สำหรับวันนี้ผมจะมาแนะนำโปรแกรมอรรถประโยชน์ (utiliies program) ตัวหนึ่ง ที่ชื่อว่า make ครับ make เป็นโปรแกรมที่จะช่วยให้เราคอมไพล์โปรแกรมที่ประกอบด้วยไฟล์โปรแกรมหลาย ๆ ไฟล์ได้ง่าย และมีประสิทธิภาพมากขึ้น โดยเราจะระบุรายชื่อไฟล์ต้นฉบับและเงื่อนไขการคอมไพล์โปรแกรมไว้ใน make file ซึ่งโครงสร้างแบบง่ายที่สุดของ make file จะเป็นดังนี้ครับ 


target: dependencies


[tab] command

ในการอธิบายให้เข้าใจถึงโครงสร้างของ make file จะขอใช้ตัวอย่าง make file ต่อไปนี้ครับ 

 all: testPeterson

testPeterson: testPeterson.o peterson.o
            gcc -o testPeterson testPeterson.o peterson.o

testPeterson.o: testPeterson.c
            gcc -c testPeterson.c  

peterson.o: peterson.c
            gcc -c peterson.c

clean:
            rm -rf *.o testPeterson


จากตัวอย่างนี้จะเห็นว่ามี target อยู่ 5 ตัวคือ 
all:,  testPeterson:,  testPeterson.o, peterson.o และ clean: 

โดยปกติแล้ว target จะใช้เป็นแบ่งการคอมไพล์โปรแกรมออกเป็นส่วน ๆ  โดยเราจะระบุ  target ที่สัมพันธ์กันผ่านทาง dependencies จากตัวอย่างจะเห็นว่า เราแบ่งการคอมไพล์โปรแกรมของเราออกเป็น 3 ส่วนตาม target สามตัวคือ 
testPeterson:, testPeterson.o และ peterson.o 

ให้สังเกตว่า target ทั้งสามตัวนี้คือชื่อไฟล์ โดย testPeterson คือชื่อไฟล์โปรแกรมที่เราจะได้ ส่วน testPeterson.o และ peterson.o จะเป็นชื่อไฟล์ของ object code (ซึ่งความสำคัญของเรื่องนี้จะได้กล่าวถึงต่อไป) 

target all: เป็นชื่อโดยปริยาย (default) ที่จะถูกเรียกทำงานในกรณีที่เราเรียกใช้ make file โดยไม่ระบุ  target ส่วน target clean: เป็น target ที่ไม่มี dependencies มีแต่คำสั่ง ซึ่งจุดประสงค์ของ target clean: นี้ไม่เกี่ยวกับการคอมไพล์โปรแกรม แต่เป็นการสั่งให้ลบไฟล์ .o ทั้งหมดและไฟล์ testPeterson ทิ้งไป เราจะเรียกให้ target clean: ทำงานในกรณีที่เราต้องการ จะลบ object code และโปรแกรมทิ้งไปเพื่อเริ่มต้นสร้างใหม่ทั้งหมด

dependencies จะเป็นส่วนที่ระบุ ว่า target นี้ขึ้นอยู่กับ target อะไรบ้าง จากตัวอย่างจะเห็นว่า all: ขึ้นอยู่กับ testPeterson นั่นคือเมื่อเราเรียก make file มาทำงานโดยเริ่มที่ target all: มันจะไปทำที่ target testPeterson:เพราะ all: ระบุ dependencies คือ testPeterson และเมื่อ make มาทำที่ target testPeterson: จะพบว่ามันมี dependencies สองตัวคือ testPeterson.o และ peterson.o มันก็จะไปทำงานที่  target ทั้งสองตามลำดับ ซึ่งการไปทำงานที่ว่านี้ก็คือการไปเรียก คำสั่ง (command) ที่ถูกระบุใน target นั้น เช่นคำสั่งของ peterson.o ก็คือ gcc –c petertson.c  นั่นคือถ้าเราเรียก make file นี้ทำงานโดยให้เริ่มทำงานที่  target all: ก็เท่ากับว่าเราได้เรียกใช้คำสั่งตามลำดับดังนี้ 

gcc –c testPeterson.c     (จาก target testPeterson.o)
gcc -c peterson.c   (จาก target peterson.o)
gcc -o testPeterson testPeterson.o peterson.o (จาก  target testPeterson)

หมายเหตุ: 
1. ในการเขียนคำสั่งให้กับแต่ละ target จะต้องกดแป้น Tab บนแป้นพิมพ์ เพื่อให้คำสั่งเยื้องเข้าไป ใช้การเคาะ Space Bar  หลาย ๆ ครั้งแทนไม่ได้
2. ในกรณีที่ชื่อของ target เป็นชื่อไฟล์ make จะทำงานโดยเปรียบเทียบวันเวลาที่สร้างไฟล์ที่เป็น target กับวันเวลาที่สร้างไฟล์ที่เป็น depencencies ถ้ามันพบว่าไฟล์ที่อยู่บนฝั่ง target ใหม่กว่า มันก็จะไม่ทำ target นั้น เช่น target peterson.o  มี dependencies คือ peterson.c ถ้า peterson.o ใหม่กว่า peterson.c ก็หมายความว่าหลังจากที่ได้คอมไพล์โปรแกรม peterson.c ไปในครั้งล่าสุดแล้วไม่ได้มีการแก้ไข อีกเลย peterson.c  จึงไม่ต้องเสียเวลาคอมไพล์ใหม่ 

คำสั่งในการเรียก  make file มีรูปแบบดังนี้ 

make [–f  ชื่อ make file] [target] 
เช่น
 make –f  peterson.mk 
จะเป็นการเรียก make file ชือ peterson.mk ให้มาทำงาน โดยจะทำงานที่ target all:
make –f  peterson.mk peterson.o 
จะเป็นการเรียก make file ชือ peterson.mk ให้มาทำงาน โดยจะทำงานที่ target peterson.o:
make –f  peterson.mk clean 
จะเป็นการเรียก make file ชือ peterson.mk ให้มาทำงาน โดยจะทำงานที่ target clean:

หมายเหตุ: ถ้าเราตั้งชื่อ make file ว่า makefile เราสามารถใช้คำสั่ง make โดยไม่ต้องระบุชื่อ make file เลยก็ได้ 
เช่น make clean จะหมายถึงให้ไปทำงานใน target clean: ของ make file ที่ชื่อ makefile

เอาล่ะครับ คิดว่าบทความนี้จะเป็นการเกริ่นนำให้รู้จักกับ make และสามารถนำไปใช้งานกันได้นะครับ แต่จริง ๆ แล้ว make มีความสามารถมากกว่านี้มากครับ ซึ่งใครที่สนใจก็ขอแนะนำให้ลองค้นคว้าเพิ่มเติมดูนะครับ ซึ่งลิงก์หนึ่งที่ผมแนะนำก็คือ http://www.gnu.org/software/make/manual/make.html  ขอให้สนุกกับการเริ่มใช้ make นะครับ ...

วันศุกร์ที่ 17 มกราคม พ.ศ. 2557

การสร้าง Administered Object สำหรับ JMS Application บน GlassFish Server

ก่อนที่เราจะเขียนโปรแกรมในพาราไดม์ที่เป็น Messaging System ด้วย Java หรือที่เราจะเรียกว่า JMS Application สิ่งที่ต้องทำก่อนก็คือการสร้าง Administered Object ซึ่งอ็อบเจกต์เหล่านี้ไม่ได้ถูกสร้างในโปรแกรมที่เราเขียนขึ้น แต่จะต้องถูกสร้างผ่านเครื่องมือที่โปรแกรมเว็บเซิร์ฟเวอร์ที่รองรับ JMS Application เตียมไว้ให้ และอ็อบเจกต์เหล่านี้จะต้องถูกสร้างก่อนที่เราจะเขียนโปรแกรม ในบทความนี้จะแสดงให้เห็นขั้นตอนการสร้างอ็อบเจกต์ดังกล่าว ซึ่งอ็อบเจกต์ดังกล่าวแบ่งเป็นสองกลุ่มคือ connection factories และ destination มาดูขั้นตอนการสร้างอ็อบเจกต์เหล่านี้บน GlassFish Server กันครับ

1. เข้าสู่หน้า Admin Console ของ Glass Fish แล้วคลิกที่เมนู Resources


2. สร้าง Connection Factory Object โดยคลิกที่ Connection Factories


3. คลิกที่ปุ่ม new เพื่อสร้าง Connection Factory Object


4. ป้อนชื่อ JNDI (JNDI Name) และประเภททรัพยากร (Resource Type) สำหรับชื่อเราจะใส่อะไรก็ได้ แต่
รูปแบบที่เป็นที่นิยมกันคือใช้ jms/ชื่อที่ต้องการ ส่วนประเภททรัพยากรเราเลือกเป็น ConnectionFactory ถ้าเราต้องการจะใช้อ็อบเจกต์นี้เพื่อสร้าง destination ที่เป็นได้ทั้ง topic และ queue จากนั้นคลิกที่ปุ่ม OK


5. สร้าง Destination อ็อบเจกต์ โดยกลับไปที่ JMS Resources และเลือก Destination Resources

6. สร้าง Destination Object โดยคลิกที่ปุ่ม New



 7. ป้อนข้อมูลเกี่ยวกับ Destination อ็อบเจกต์ที่ต้องการ โดยเลือกประเภทของทรัพยากรให้ตรงตามที่ต้องการว่าจะให้เป็น topic หรือ queue จากนั้นคลิกที่ปุ่ม OK


ก็เป็นอันว่าเรียบร้อยครับตอนนี้เราก็ได้ Administered Object เพื่อใช้กับ JMS Application เรียบร้อยแล้ว ตอนนี้ก็สามารถลงมือเขียน JMS Application กันได้แล้ว

วันอังคารที่ 24 ธันวาคม พ.ศ. 2556

การเขียนโปรแกรมเครือข่ายด้วย Socket ตอนที่ 6 : TCP Concurrent Server

จากโปรแกรมเซิร์ฟเวอร์ในตอนที่ 3 จัดเป็นเซิร์ฟเวอร์แบบ iterative นั่นคือให้บริการไคลเอนต์ได้ครั้งละหนึ่งตัว ถ้ามีมากกว่าหนึ่งตัวตัวที่เข้ามาทีหลังจะต้องเข้าคิวรอจนกว่าจะถึงคิวตัวเอง ซึ่งเซิร์ฟเวอร์แบบนี้ไม่เหมาะสมกับงานที่ไคลเอนต์และเซิร์ฟเวอร์ต้องติดต่อกันเป็นเวลานาน ลองนึกภาพว่าถ้า Gmail ให้บริการแบบนี้ดูนะครับว่ามันจะเป็นยังไง ดังนั้นงานแบบนี้จะต้องเขียนเซิร์ฟเวอร์ที่ทำงานแบบพร้อมกัน (Concurrent) ครับ

ซึ่งการเขียนก็ไม่ได้ยากอะไร แนวคิดที่ผ่านมาในตอนต่าง ๆ ยังใช้ได้หมด แต่สิ่งที่เราจะเพิ่มเข้ามาคือเราจะนำ Thread มาใช้ครับ หลักการก็คือหลังจากที่เซิร์ฟเวอร์รับการติดต่อจากไคลเอนต์เข้ามาแล้วแทนที่จะไปให้บริการไคลเอนต์เองเหมือนที่ผ่านมา ก็จะสร้าง Thread ขึ้นมาให้บริการไคลเอนต์แต่ละตัว ไปลองดูโค้ดกันครับ

  1. import java.net.*;
  2. import java.io.*;
  3. public class EchoConcurrentServer
  4. {
  5.   public static final int PORT = 50000;
  6.   public static void main(String[] args)
  7.   {
  8.     ServerSocket serverSocket = null;
  9.     Socket serviceSocket = null;
  10.     int port = PORT;
  11.     try
  12.     { // create socket
  13.       if (args.length == 1) // user specify port number
  14.             port = Integer.parseInt(args[0]);
  15.         else if (args.length > 1) { // error 
  16.             System.out.println("Usage: Java EchoServer [port number]");
  17.             System.exit(1);
  18.         }
  19.         if (port <= 1023 || port > 65535)
  20.             throw new NumberFormatException();  
  21.       serverSocket = new ServerSocket(PORT);
  22.     }
  23.     catch (IOException e)
  24.     {
  25.       System.err.println("Error Creating Socket");
  26.       System.exit(1);
  27.     }
  28.     catch (NumberFormatException e) {
  29.         System.out.println("Port number must be integer between 1024 and 65535");
  30.         System.exit(3);
  31.     }

  32.     while (true)
  33.     {
  34.       try
  35.       {

  36.         // wait for connection then create streams
  37.         System.out.println("Waiting for client connection at port number " + port);
  38.         serviceSocket = serverSocket.accept();
  39.         EchoThread echoThread = new EchoThread(serviceSocket, port);
  40.         echoThread.start();
  41.         
  42. } catch (IOException e)
  43.        {
  44.               System.err.println("Closing Socket connection");
  45.               System.exit(1);
  46.        }
  47.     }
  48.   }
  49. }
  50. class EchoThread extends Thread {
  51.     private Socket serviceSocket;
  52.     private int port;
  53.     public EchoThread(Socket serviceSocket, int port) {
  54.         this.serviceSocket = serviceSocket;
  55.         this.port = port;
  56.     }
  57.     public void run() {
  58.         OutputStreamWriter output = null;
  59.         BufferedReader input = null;
  60.         try {
  61.             output = new OutputStreamWriter(serviceSocket.getOutputStream());
  62.             input = new BufferedReader(new InputStreamReader(serviceSocket.getInputStream()));
  63.             while(true) {
  64. String inputWord = input.readLine();
  65. if (!inputWord.equals(""))
  66. {
  67. output.write(inputWord.toUpperCase() + "\r\n");
  68. output.flush();
  69. }
  70. else {
  71. output.close();
  72. input.close();
  73. serviceSocket.close();
  74. break;
  75.                 }
  76.             }
  77.         }
  78.         catch (IOException e) {
  79.             System.err.println("Closing Socket connection");
  80.             
  81.         }
  82.         finally {
  83.             try {
  84.                 if (input != null)
  85.                     input.close();
  86.                 if (output != null)
  87.                     output.close();
  88.                 if (serviceSocket != null)
  89.                     serviceSocket.close();
  90.             }
  91.                 catch (IOException e) {
  92.                     e.printStackTrace();
  93.                 }
  94.         }
  95.     }
  96. }

คลาส EchoThread ในบรรทัดที่ 52-99 เป็นคลาสที่เขียนขึ้นเพื่อเป็นส่วนของการให้บริการไคลเอนต์ บรรทัดที่ 41 และ 42 สร้างออบเจกต์ของ EchoThread และสั่งให้ Thread เริ่มทำงาน หลังจากทำส่วนนี้เสร็จโปรแกรมเซิร์ฟเวอร์ก็สามารถกลับไปรับการติดต่อจากไคลเอนต์ตัวถัดไป และถ้ามีการติดต่อเข้ามาก็ทำแบบเดียวกันคือสร้าง Thread อีกตัวหนึ่งไปให้บริการไคลเอนต์

ส่วนโปรแกรมไคลเอนต์แสดงได้ดังต่อไปนี้

  1. import java.net.*;
  2. import java.io.*;
  3. import java.util.*;
  4. public class EchoClient
  5. {
  6.   public static final int PORT = 50000;  // default port
  7.   public static void main(String[] args)
  8.   {
  9. Socket clientSocket = null;
  10.         int port = PORT;
  11.         String hostName = null;
  12.         if (args.length < 1) {
  13.             System.out.println("Usage: java EchoClient <host name> [Port Number]");
  14.             System.exit(1);
  15.         }
  16.     try
  17.     { // create socket
  18.         if (args.length == 2) {
  19.             port = Integer.parseInt(args[1]);
  20.             if (port <= 1023 || port > 65535) 
  21.                 throw new NumberFormatException();
  22.         }
  23.       clientSocket = new Socket(args[0], port);
  24.     }
  25.     catch (IOException e)
  26.     {
  27.       System.err.println("Error Creating Socket");
  28.       System.exit(2);
  29.     }
  30.     catch (NumberFormatException e) {
  31.         System.out.println("Port number must be integer between 1024 and 65535");
  32.         System.exit(3);
  33.     }
  34.     OutputStreamWriter output = null;
  35.     Scanner input = null;
  36.     try
  37.       {
  38.             output = new OutputStreamWriter(clientSocket.getOutputStream());
  39.             input = new Scanner(clientSocket.getInputStream());
  40.    Scanner inputData = new Scanner(System.in);     
  41.    while(true) {
  42. System.out.println("Enter your word: ");
  43. String inputWord = inputData.nextLine();
  44.                         output.write(inputWord + "\r\n");
  45.                         output.flush();
  46. if (inputWord.equals(""))
  47. {
  48.                                 break;
  49. }
  50. String outputWord = input.nextLine();
  51. System.out.println(outputWord);
  52. }
  53.                 input.close();
  54. output.close();
  55. clientSocket.close();
  56. } catch (IOException e)
  57.        {
  58.               System.err.println("Closing Socket connection");
  59.        }
  60.         finally {
  61.             try {
  62.                 if (input != null)
  63.                     input.close();
  64.                 if (output != null)
  65.                     output.close();
  66.                 if (clientSocket != null)
  67.                     clientSocket.close();
  68.            } 
  69.             catch (IOException e) {
  70.                 e.printStackTrace(); 
  71.             }
  72.         }
  73.     }
  74. }

ซึ่งจะเห็นว่าโปรแกรมฝั่งไคลเอนต์นี้เราเขียนโดยใช้แนวคิดที่ได้กล่าวมาแล้ว ไม่ต้องทำอะไรเป็นพิเศษ ในการรันโปรแกรมไคลเอนต์ให้รันโดยระบุชื่อโฮสต์ที่เซิร์ฟเวอร์รันอยู่ด้วย เช่นถ้าจะรันไคลเอนต์บนเครื่องเดียวกับเซิร์ฟเวอร์ก็ให้ใช้คำสั่งดังนี้

java EchoClient localhost

และขอแนะนำให้ลองรันไคลเอนต์หลาย ๆ ตัวติดต่อกับเซิร์ฟเวอร์พร้อม ๆ กันด้วย สำหรับใครที่ไม่อยากพิมพ์โค้ดเองสามารถดาวน์โหลดได้จากลิงก์นี้ครับ EchoClient.java EchoConcurrentServer.java

สุดท้ายสำหรับวันนี้ก็ขอสุขสันต์วันคริสต์มาสมายังทุกคนครับ...

วันเสาร์ที่ 7 ธันวาคม พ.ศ. 2556

เขียนโปรแกรมเครือข่ายด้วย Socket ตอนที่ 5: การรันโปรแกรมเพื่อทดสอบการทำงาน

หลังจากที่เราเขียนโปรแกรมเซิร์ฟเวอร์และไคลเอนต์เรียบร้อยแล้ว ในตอนนี้เราจะมาลองรันโปรแกรมเพื่อทดสอบการทำงานกันดู  โดยเราจะรันทั้งสองโปรแกรมบนเครื่องเดียวกัน เริ่มจากรันโปรแกรมเซิร์ฟเวอร์ดังนี้ start java TCPServer

เราใช้คำสั่ง start นำหน้าคำสั่งที่ใช้รันโปรแกรมเซิร์ฟเวอร์เพราะเราต้องการเปิด จะเปิดหน้าจอ command prompt ชึ้นมาใหม่ในการรันโปรแกรมเซิร์ฟเวอร์ เพื่อใช้หน้าจอ command prompt เดิมสำหรับรันไคลเอนต์ต่อไป

การรันไคลเอนต์ใช้คำสั่งดังนี้ java TCPClient

หลังจากนั้นเราสามารถป้อนข้อความอะไรก็ได้ จากนั้นโปรแกรมฝั่งเซิร์ฟเวอร์จะส่งข้อความที่เราป้อนเข้าไปกลับมาเป็นตัวอักษรภาษาอังกฤษตัวใหญ่

สำหรับใครที่อ่านบทความแล้วยังไม่เข้าใจว่าจะรันโปรแกรมยังไง สามารถดูได้จากวีดีโอต่อไปนี้ครับ



หมายเหตุ: ในกรณีที่รันโปรแกรมเซิร์ฟเวอร์ไม่ได้ส่วนใหญ่ปัญหาจะเกิดจากการที่ใช้หมายเลขพอร์ตซ้ำกับโปรแกรมอื่นที่รันอยู่ ให้แก้หมายเลขพอร์ตในโค้ดโปรแกรมของทั้งเซิร์ฟเวอร์และไคลเอนต์ จากนั้นคอมไพล์โปรแกรมและรันใหม่