วันพุธที่ 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) มาใช้ครับ หลักการก็คือหลังจากที่เซิร์ฟเวอร์รับการติดต่อจากไคลเอนต์เข้ามาแล้วแทนที่จะไปให้บริการไคลเอนต์เองเหมือนที่ผ่านมา ก็จะสร้างเธรด ขึ้นมาให้บริการไคลเอนต์แต่ละตัว ไปลองดูโค้ดกันครับ
  1. //TCPConcurrentServer.java
  2. import java.io.*; 
  3. import java.net.*; 
  4. import java.util.*;
  5. public class TCPConcurrentServer { 
  6.    public static void main(String argv[])  { 
  7.       String clientSentence; 
  8.       String capitalizedSentence; 
  9.       ServerSocket welcomeSocket = null;
  10.       try {
  11.          welcomeSocket = new ServerSocket(6789);
  12.       }
  13.       catch (IOException e) {
  14.          System.out.println("Cannot create a welcome socket");
  15.          System.exit(1);
  16.       }
  17.       while(true) {
  18.          try {  
  19.             System.out.println("The server is waiting ");
  20.             Socket connectionSocket = welcomeSocket.accept(); 
  21.    EchoThread echoThread = new EchoThread(connectionSocket);
  22.             echoThread.start();
  23.          }
  24.          catch (IOException e) {
  25.             System.out.println("Cannot create this connection");
  26.          }
  27.       }
  28.    } 
  29. //EchoThread.java
  30. import java.io.*; 
  31. import java.net.*; 
  32. import java.util.*;
  33. public class EchoThread extends Thread {
  34.     private Socket connectionSocket;
  35.     public EchoThread(Socket connectionSocket) {
  36.         this.connectionSocket = connectionSocket;
  37.     }
  38.     public void run() {
  39.          Scanner inFromClient = null;
  40.          DataOutputStream outToClient = null;
  41.          try {
  42.             inFromClient = new Scanner(connectionSocket.getInputStream());
  43.    outToClient = 
  44.               new DataOutputStream(connectionSocket.getOutputStream()); 
  45.    String clientSentence = inFromClient.nextLine(); 
  46.          String capitalizedSentence = clientSentence.toUpperCase() + '\n'; 
  47.          outToClient.writeBytes(capitalizedSentence);         
  48.             
  49.    }
  50.         catch (IOException e) {
  51.             System.err.println("Closing Socket connection");
  52.         }
  53.         finally {
  54.             try {
  55.                if (inFromClient != null)
  56.                   inFromClient.close();
  57.                if (outToClient != null)
  58.                   outToClient.close();
  59.                if (connectionSocket != null)
  60.                   connectionSocket.close();
  61.                }
  62.             catch (IOException e) {
  63.                e.printStackTrace();
  64.             }
  65.         }
  66.     }
  67. }

สิ่งที่แตกต่างจากโปรแกรม iterative server ก็คือ คลาส EchoThread ในบรรทัดที่ 30-68 ซึ่งเป็นคลาสที่เขียนขึ้นเพื่อเป็นส่วนของการให้บริการไคลเอนต์ในการแปลงตัวอักษรตัวเล็กเป็นตัวใหญ่ จะเห็นว่างานบริการไคลเอนต์ที่เคยอยู่ในตัวโปรแกรม TCPServer จะถูกนำมาเขียนในคลาสนี้ บรรทัดที่ 21 และ 22 ในคลาส TCPConcurrent  จะสร้างออบเจกต์ของ EchoThread และสั่งให้เทรดเริ่มทำงาน ให้สังเกตว่ามีการส่งซ็อกเก็ตเชื่อมต่อที่สร้างขึ้นมาให้ออบเจกต์ของคลาส EchoThread ผ่านทางคอนสตรักเตอร์ หลังจากสร้างออบเจกต์ และสั่งให้เธรดเริ่มทำงานในการบริการไคลเอนต์แล้ว โปรแกรมเซิร์ฟเวอร์ก็สามารถกลับไปรับการติดต่อจากไคลเอนต์ตัวถัดไปได้ โดยตอนนี้การบริการไคลเอนต์จะเป็นหน้าที่ของเธรดที่ถูกสร้างขึ้น และถ้ามีการติดต่อเข้ามาก็ทำแบบเดียวกันคือสร้างเธรดอีกตัวหนึ่งไปให้บริการไคลเอนต์

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

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

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



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

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

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

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

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

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

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

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

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



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


เขียนโปรแกรมเครือข่ายด้วยซ็อกเก็ต (Socket) ตอนที่ 4: TCP Client ด้วยภาษาจาวา (Java)

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


  1. import java.io.*; 
  2. import java.net.*;
  3. import java.util.*; 
  4. class TCPClient { 
  5.     public static void main(String argv[]) throws Exception 
  6.     { 
  7.          String sentence; 
  8.          String modifiedSentence;
  9.          Scanner inFromUser = null;
  10.          Socket clientSocket = null;
  11.          DataOutputStream outToServer = null;
  12.          Scanner inFromServer = null;
  13.          try { 
  14.             inFromUser = new Scanner(System.in);
  15.             clientSocket = new Socket("localhost", 6789); 
  16.             outToServer = 
  17.                new DataOutputStream(clientSocket.getOutputStream()); 
  18.            inFromServer = new Scanner(clientSocket.getInputStream());
  19.            System.out.print("Please enter words: ");
  20.            sentence = inFromUser.nextLine(); 
  21.            outToServer.writeBytes(sentence + '\n');
  22.            modifiedSentence = inFromServer.nextLine(); 
  23.            System.out.println("FROM SERVER: " + modifiedSentence);
  24.          }
  25.          catch (IOException e) {
  26.              System.out.println("Error occurred: Closing the connection");
  27.          }
  28.          finally {
  29.             try {
  30.                 if (inFromServer != null)
  31.                     inFromServer.close();
  32.                 if (outToServer != null)
  33.                     outToServer.close();
  34.                 if (clientSocket != null)
  35.                     clientSocket.close();
  36.             }
  37.             catch (IOException e) {
  38.                e.printStackTrace();
  39.             }
  40.          } 
  41.     } 


บรรทัดที่ 15 เป็นการสร้างซ็อกเก็ตเชื่อมต่อ (Connection Socket) ให้สังเกตว่าเราใช้ชื่อโฮสต์คือ localhost เพราะโปรแกรมตัวอย่างของเราเราจะรันโปรแกรมเซิร์ฟเวอร์และไคลเอนต์บนเครื่องเดียวกัน ในกรณีที่รันโปรแกรมเซิร์ฟเวอร์บนเครื่องอื่น ให้ระบุชื่อหรือหมายเลขไอพีของเครื่องดังกล่าว หมายเลขพอร์ตใช้หมายเลขที่โปรแกรมเซิร์ฟเวอร์รอรับการติดต่ออยู่ ในที่นี้ใช้ 6789 เพราะโปรแกรมเซิร์ฟเวอร์ที่เราเขียนขึ้นในบทความที่แล้วใช้หมายเลขพอร์ตนี้  บรรทัดที่ 16-17 สร้างสตรีมผลลัพธ์สำหรับเขียนข้อมูลลงซ็อกเก็ต บรรทัดที่ 18 สร้างสตรีมเพื่อรับข้อมูลจากซ็อกเก็ต บรรทัดที่ 21 เขียนข้อมูลลงซ็อกเก็ตเพื่อส่งให้เซิร์ฟเวอร์ บรรทัดที่ 22 รอรับข้อมูลที่เซิร์ฟเวอร์จะส่งกลับมา finally clause บรรทัดที่ 28-35 ปิดสตรีมทุกตัว และซ็อกเก็ตเชื่อมต่อ

สำหรับโปรแกรมต้นฉบับ (source code) สามารถโหลดได้จากลิงก์นี้ครับ ถ้าใครโหลดจาก gitlab ไปเมื่อบทความที่แล้ว ก็จะได้โค้ดโปรแกรมไปแล้วนะครับ ไม่ต้องโหลดใหม่ครับ

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

เขียนโปรแกรมเครือข่ายด้วยซ็อกเก็ต (Socket) ตอนที่ 3: TCP Server ด้วยภาษาจาวา (Java)

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

สำหรับแนวคิดของการอ่านและเขียนข้อมูลกับซ็อกเก็ตนั้นจะใช้แนวคิดของสตรีม (Stream) ซึ่งก็คือแนวคิดที่จาวา (Java) ใช้ในการรับส่งข้อมูลระหว่างโปรแกรมที่เขียนด้วยภาษาจาวา กับอุปกรณ์รอบข้าง หรือกับไฟล์ ตัวอย่างเช่น เวลาจะพิมพ์ข้อมูลออกมาทางจอภาพเราจะใช้คำสั่ง System.out.println("string"); ประโยคนี้ out คือสตรีมผลลัพธ์ (output stream) ซึ่งอ้างถึงตัวแสดงผลลัพธ์มาตรฐาน ซึ่งก็คือจอภาพนั่นเอง การเขียนโปรแกรมซ็อกเก็ตของภาษาจาวา (Java) ก็จะใช้แนวคิดนี้ เพียงแต่สตรีมที่เราสร้างขึ้น จะเชื่อมโยงกับซ็อกเก็ตแทนที่จะเป็นอุปกรณ์หรือไฟล์

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


  1. import java.io.*; 
  2. import java.net.*; 
  3. import java.util.*;
  4. class TCPServer { 
  5.    public static void main(String argv[])  { 
  6.       String clientSentence; 
  7.       String capitalizedSentence; 
  8.       ServerSocket welcomeSocket = null;
  9.       Socket connectionSocket = null;
  10.       Scanner inFromClient = null;
  11.    DataOutputStream outToClient = null;
  12.       try {
  13.          welcomeSocket = new ServerSocket(6789);
  14.       }
  15.       catch (IOException e) {
  16.          System.out.println("Cannot create a welcome socket");
  17.          System.exit(1);
  18.       }
  19.       while(true) {
  20.          try {
  21.             System.out.println("The server is waiting ");
  22.              connectionSocket = welcomeSocket.accept(); 
  23.              inFromClient = new Scanner(connectionSocket.getInputStream());
  24.              outToClient = 
  25.               new DataOutputStream(connectionSocket.getOutputStream()); 
  26.              clientSentence = inFromClient.nextLine(); 
  27.             capitalizedSentence = clientSentence.toUpperCase() + '\n'; 
  28.             outToClient.writeBytes(capitalizedSentence);
  29.          }
  30.          catch (IOException e) {
  31.             System.out.println("Error cannot create this connection");
  32.          }
  33.          finally {
  34.             try {
  35.                if (inFromClient != null)
  36.                   inFromClient.close();
  37.                if (outToClient != null)
  38.                   outToClient.close();
  39.                if (connectionSocket != null)
  40.                   connectionSocket.close();
  41.             }
  42.             catch (IOException e) {
  43.                e.printStackTrace();
  44.             }
  45.          }
  46.       }
  47.    } 

สำหรับโปรแกรมนี้ก็มีส่วนที่จะอธิบายดังนี้ครับ บรรทัดที่ 13 เป็นการสร้างเซิร์ฟเวอร์ซ็อกเก็ตโดยใช้หมายเลขพอร์ต 6789 ในการรอรับการติดต่อกับไคลเอนต์  บรรทัดที่ 22 จะเห็นว่ามีการเรียกใช้เมท็อด accept() เมท็อดนี้จะมีผลทำให้โปรแกรมเซิร์ฟเวอร์หยุดรอรับการติดต่อจากไคลเอนต์ เมื่อไคลเอนต์ติดต่อเข้ามาก็จะสร้างซ็อกเก็ตเชื่อมต่อเพื่อใช้ในการแลกเปลี่ยนข้อมูล บรรทัดที่ 23 สร้างสตรีมข้อมูลเข้า (input stream) เพื่ออ่านข้อมูลจากซ็อกเก็ต บรรทัดที่ 24-25 สร้างสตรีมผลลัพธ์ (output stream) เพื่อเขียนข้อมูลลงซ็อกเก็ต บรรทัดที่ 26 หยุดเพื่อรออ่านข้อมูลที่ไคลเอนต์จะส่งผ่านซ็อกเก็ตมา บรรทัดที่ 27 แปลงสตริงที่รับมาให้เป็นตัวอักษรตัวใหญ่ และบรรทัดที่ 28 เขียนข้อมูลผ่านซ็อกเก็ตกลับไปให้ไคลเอนต์ ใน finally clause จะปิด stream ที่สร้างขึ้นมาทั้งหมด และซ็อกเก็ตเชื่อมต่อตัวนี้ เนื่องจากในตัวอย่างนี้เซิฟร์เวอร์จะแปลงสตริงที่ไคลเอนต์ส่งเข้ามา ส่งสตริงที่แปลงกลับไป และตัดการเชื่อมต่อกับไคลเอนต์ทันที  และข้อสังเกตที่น่าสนใจก็คือจะเห็นว่าเราให้โปรแกรมนี้ทำงานอยู่ในลูปไม่รู้จบ ซึ่งเป็นธรรมชาติของโปรแกรมเซิร์ฟเวอร์ที่จะทำงานไปเรื่อย ๆ

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

สำหรับโปรแกรมต้นฉบับ (source code) สามารถโหลดได้จากลิงก์นี้ครับ  ซึ่งจะได้โปรแกรมทั้งฝั่งเซิร์ฟเวอร์และไคลเอนต์เลย แต่บล็อกเกี่ยวกับไคลเอนต์โปรแกรมจะอยู่ในตอนหน้านะครับ

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

วันพฤหัสบดีที่ 28 พฤศจิกายน พ.ศ. 2556

การเขียนเว็บแอพพลิเคชันด้วย Servlet และ JSP ด้วย NetBeans

วันนี้จะนำเสนอการสร้างเว็บแอพพลิเคชันง่าย ๆ โดยใช้ Servlet และ JSP โดยใช้ NetBeans เป็นตัวสร้างนะครับ สำหรับ NetBeans ที่จะใช้นี้ต้องเป็น NetBeans ที่รองรับ Java EE นะครับ ถ้าใครจะดาวน์โหลดก็ใช้อันนี้นะครับ

 

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



เมื่อผู้ใช้ป้อนอุณหภูมิและกดปุ่ม Submit จะแแสดงหน้าจอดังรูปถัดไป


มาเริ่มสร้างกันเลยครับ เริ่มจากสร้างโปรเจกต์กันก่อนดังรูป โดยให้เลือกเป็น Java Web -> Web Application ดังรูป





จากนั้นให้เลือก Server เป็น GlassFish หรือจะเลือกเป็น Tomcat ก็ได้

จะได้โปรเจกต์ ซึ่ง NetBeans จะมีไฟล์ HTML ชื่อ index.html ไว้ให้ ซึ่งให้เราเขียนโค้ดสำหรับฟอร์มรับค่าอุณหภูมิเป็นฟาเรนต์ไฮต์ดังรูป


   ถ้าดูโค้ดในรูปไม่ชัดก็ดูได้จากด้านล่างนี้


<html>
    <head>
        <title>Fahrenheit to Celcius </title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width">
    </head>
    <body>
        <form name="main" action="FahrenheitToCelcius">
            Fahrenheit: <input type="text" name="fahrenheit" value="" size="10" />
            <br/>
            <input type="submit" value="Submit" name="submit" />
        </form>
    </body>
</html>

ให้สังเกตุค่าที่กำหนดให้กับแอททริบิวต์ action ของแท็ก form ไว้ให้ดี เพราะนี่จะเป็นชื่อของ Servlet ที่เราจะสร้าง และขอให้สังเกตุที่กำหนดให้กับแอทริบิวต์ name ของแท็ก input ไว้ด้วยเพราะเราจะต้องใช้เพื่อนำค่าที่ผู้ใช้ป้อนไปประมวลผล

ขั้นต่อไปจะสร้าง Servlet โดยคลิกขวาที่ Source Package ในตัวโปรเจกต์เลือก New -> Servlet



ตั้งชื่อคลาสให้ตรงกับค่าที่อยู่ใน แอททริบิวต์ action ของแท็ก form แล้วคลิก Next ย้ำนะครับว่า Next อย่าเพิ่ง Finish เพราะเราควรจะกำหนดค่านี้ให้อยู่ใน deployment descriptor ด้วย

ให้เลือก Add Information to deployment descriptor (web.xml) ด้วยแล้วจึงคลิก Finish (ขอไม่อธิบายนะครับว่าคืออะไร เพราะมันจะยาวเกินขอบเขตของ Tutorial)

NetBeans จะสร้างคลาสให้ ซึ่งส่วนที่เราจะเขียนโค้ดให้ Servlet คือเมท็อด ProcessRequest() ดังรูป


ถ้าดูไม่ชัดสามารถดูได้จากโค้ดข้างล่างนี้ครับ

protected void processRequest(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
           response.setContentType("text/html;charset=UTF-8");
          String fahrenheitStr = request.getParameter("fahrenheit");
         double fahrenheit = Double.parseDouble(fahrenheitStr);
         DecimalFormat twoDigits = new DecimalFormat("0.00");
        double celcius = (5.0 / 9 ) * (fahrenheit - 32);
        String celciusStr = twoDigits.format(celcius);
        fahrenheitStr = twoDigits.format(fahrenheit);
        request.setAttribute("fahrenheit", fahrenheitStr);
        request.setAttribute("celcius", celciusStr);
        request.getRequestDispatcher("result.jsp").forward(request, response);
     
    }

ตรงนี้ขอขยายความเล็กน้อยครับ ว่าจริง ๆ แล้วตามหลักการจริง ๆ ในคลาส Servlet ที่สร้างขึ้นนี้เราจะต้อง Override เมท็อด doGet() หรือ doPost() ขึ้นอยู่กับว่าตอนที่ส่ง HTTP Request มาใช้เมท็อด Get หรือ Post แต่ NetBeans เลือก Override ทั้งสองเมท็อดนี้ให้มาเรียกเมท็อด processRequest() นั่นคือไม่ว่า HTTP Request ที่ส่งมาจะเป็นเมท็อด Get หรือ Post เราก็จะเขียนโปรแกรมที่เมท็อด ProcessRequest()

ในส่วนของโค้ดที่เราเขียนขออธิบายเป็นบรรทัดต่อบรรทัดดังนี้ครับ

String fahrenheitStr = request.getParameter("Fahrenheit"); รับค่าที่ผู้ใช้ป้อนเข้ามา ซึ่งในพารามิเตอร์ของเมท็อด getParamater() เราจะระบุชื่อให้ตรงกับที่เราตั้งไว้ใน Form และให้เข้าใจนะครับว่าสิ่งที่ผู้ใช้ป้อนเข้ามาจะอยู่ในรูปแบบสตริง

double fahrenheit = Double.parseDouble(fahrenheitStr);  แปลงสตริงให้เป็นตัวเลข
double celcius = (5.0 / 9) * (fahrenheit -32); แปลงฟาเรนต์ไฮต์เป็นเซลเซียส

DecimalFormat twoDigits = new DecimalFormat("0.00"); เป็นการสร้างออบเจ็กต์ที่จะใช้ในการจัดรูปแบบการแสดงผลตัวเลขให้เป็นทศนิยมสองตำแหน่ง

String celciusStr = twoDigits.format(celcius);
fahrenheitStr = twoDigits.format(fahrenheit);
สองบรรทัดข้างบนก็คือการจัดรูปแบบผลลัพธ์ครับทั้งฟาห์เรน"ฮต์และเซลเซียส

request.setAttribute("fahrenheit", fahrenheitStr);
request.setAttribute("celcius", celciusStr);  

สร้างแอททริบิวต์เพื่อส่งต่ออุณหภูมิไปแสดงผลในหน้า JSP ที่เราจะสร้างต่อไป

 request.getRequestDispatcher("/result.jsp").forward(request, response); ส่งต่อการทำงานไปยังหน้า JSP ชื่อ result.jsp

ขั้นสุดท้ายซะทีครับ สร้าง result.jsp เริ่มจากคลิกขวาที่ โฟลเดอร์ Web Pages เลือก New -> JSP  ดังรูป




ในช่อง File Name ให้ใส่ชื่อ result ไม่ต้องใส่ .jsp นะครับ แล้วกดปุ่ม Finish จากนั้น NetBeans จะสร้างไฟล์ชื่อ result.jsp ให้เราเขียนโค้ดดังนี้





<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>JSP Page</title>
    </head>
    <body>
        <% out.print(request.getAttribute("fahrenheit") + " Fahrenheit is " + request.getAttribute("celcius") + " Celcius"); %>
    </body>
</html>


ให้สังเกตุวิธีการเรียกใช้ค่าพารามิเตอร์และแอทริบิวต์ที่ถูกส่งมาจาก Servlet ในขั้นตอนก่อนหน้า และจะเห็นว่าเราสามารถใช้ ตัวแปร request ได้เลย เพราะ request เป็นหนึ่งใน implicit object ของ JSP ครับ

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