แสดงบทความที่มีป้ายกำกับ Operating Systems แสดงบทความทั้งหมด
แสดงบทความที่มีป้ายกำกับ Operating Systems แสดงบทความทั้งหมด

วันศุกร์ที่ 25 กันยายน พ.ศ. 2563

การเขียนโปรแกรมเครือข่ายด้วยซ็อกเก็ตตอนที่ 12 เขียนโปรแกรม TCP Concurrent Server ด้วยภาษา Python

ในบทความวันนี้จะมาปรับปรุงโปรแกรม TCP Iterative Server ที่เขียนด้วยภาษา Python ให้เป็น Concurrent Server ครับ ซึ่งหลักการก็เหมือนกับการเขียนโปรแกรมด้วยภาษา Java นั่นคือใช้ เธรด (thread) มาให้บริการไคลเอนต์ที่ติดต่อเข้ามา โปรแกรมที่ปรับปรุงแล้วเป็นดังนี้ครับ 

01: # tcpserver.py

02: import socket

03: import threading

04: def echo_thread_function(connectionSocket):

05:     clientSentenceBytes = connectionSocket.recv(4096)

06:     clientSentence = clientSentenceBytes.decode("utf-8")

07:     connectionSocket.send(str.encode(clientSentence.upper()))

08:     connectionSocket.close()

09: 

10: welcomeSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

11: welcomeSocket.bind(("localhost", 6789))

12: welcomeSocket.listen(5)

13: while True:

14:     print("The server is waiting")

15:     connectionSocket, address = welcomeSocket.accept()

16:     echoThread = threading.Thread(target=echo_thread_function, args=(connectionSocket,))

17:     echoThread.start()


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

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

สำหรับโค้ดโปรแกรมโหลดได้จากลิงก์นี้ครับ  

วันพฤหัสบดีที่ 3 กันยายน พ.ศ. 2563

การเขียนโปรแกรมเครือข่ายด้วยซ็อกเก็ตตอนที่ 11 เขียนโปรแกรม TCP Iterative Server และ TCP Client ด้วยภาษา Python

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

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

โปรแกรมฝั่งเซิร์ฟเวอร์แสดงได้ดังนี้ครับ 

01: # tcpserver.py

02: import socket

03: welcomeSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

04: welcomeSocket.bind(("localhost", 6789))

05: welcomeSocket.listen(5)

06: while True:

07:     print("The server is waiting")

08:     connectionSocket, address = welcomeSocket.accept()

09:     clientSentenceBytes = connectionSocket.recv(4096)

10:     clientSentence = clientSentenceBytes.decode("utf-8")

11:     connectionSocket.send(str.encode(clientSentence.upper()))

12:     connectionSocket.close()

บรรทัดที่ 2 คือการอิมพอร์ต socket โมดูล บรรทัดที่ 3 สร้างเซิร์ฟเวอร์ซ็อกเก็ตเพื่อรอรับการติดต่อ โดยพารามิเตอร์ที่ใช้ในการสร้างมี 2 ตัว ตัวแรกคือ socket.AF_INET ซึ่งหมายถึงเราระบุว่าจะใช้ IP เวอร์ชัน 4 ตัวที่สอง socket.SOCK_STREAM หมายถึงโปรโตคอล TCP 

บรรทัดที่ 4 ระบุชื่อโฮสต์และพอร์ตที่จะรอบรับการติดต่อ ใช้ localhost เป็นชื่อโฮสต์ เพราะต้องการจะให้การติดต่ออยู่ภายในเครื่องเดียวกันเท่านั้น ถ้าต้องการให้เครื่องภายนอกติดต่อเข้ามาได้ให้ใช้ สตริงว่าง ("") แทน ส่วนพอร์ตที่จะรอรับการติดต่อคือ 6789   

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

จากนั้นเซิร์ฟเวอร์ก็จะเข้าสู่ลูปไม่รู้จบตามการทำงานของเซิร์ฟเวอร์ทั่ว ๆ ไป โดยในลูปนี้ บรรทัดที่ 8 เซิร์ฟเวอร์จะบล็อกรอรับการติดต่อจากไคลเอนต์  โดยเมื่อมีไคลเอนต์ติดต่อเข้ามา จะมีการสร้างซ็อกเก็ตเชื่อมต่อ (connection socket) อ้างถึงโดยตัวแปร connectionSocket และหมายเลขไอพี (IP Address) ของเครื่องไคลเอนต์ที่ติดต่อเข้ามา อ้างถึงโดยตัวแปร address 

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

บรรทัดที่ 10 เป็นการ decode ข้อมูลที่อ่านมาจากซ็อคเก็ตที่อยู่ในรูปสตรีมของไบต์ (stream of bytes) ให้เป็นสตริง เพื่อที่จะได้เรียกใช้ฟังก์ชัน upper() เพื่อแปลงข้อมูลที่รับมาให้เป็นตัวอักษรภาษาอังกฤษตัวใหญ่ 

บรรทัดที่ 11 จะซับซ้อนนิดหนึ่งนะครับ เพราะทำหลายอย่าง ในส่วน clientSentence.upper() ก็คือการแปลงข้อมูลเป็นตัวอักษรตัวใหญ่ ในส่วน str.encode(clientSentence.upper()) ก็คือการทำให้สตริงที่จะส่งไป (ในที่นี้คือสตริงที่เราแปลงเป็นตัวใหญ่) อยู่ในรูปสายธารของไบต์ จากนั้นก็ใช้ฟังก์ชัน send() ส่งข้อมูลผ่านซ็อกเก็ตกลับไปให้ไคลเอนต์ 

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

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

01: # tcpclient.py

02: import socket

03: sentence = input("Please enter words: ")

04: clientSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

05: clientSocket.connect(("localhost", 6789))

06: clientSocket.send(str.encode(sentence))

07: modifiedSentenceBytes = clientSocket.recv(4096)

08: modifiedSentence = modifiedSentenceBytes.decode("utf-8")

09: clientSocket.close()

10: print(modifiedSentence)


บรรทัดที่ 2 คือการอิมพอร์ต socket โมดูล บรรทัดที่ 3 รอรับข้อมูลที่ผู้ใช้ป้อน โดยแสดงข้อความ Please enter words เพื่อบอกผู้ใช้ ค่าที่ผู้ใช้ป้อนจะเก็บในตัวแปร sentence 

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

บรรทัดที่ 5 สร้างการเชื่อมต่อกับโปรแกรมเซิร์ฟเวอร์โดยใช้หมายเลขพอร์ต 6789 ตามที่โปรแกรมเซิร์ฟเวอร์ใช้ 

บรรทัดที่ 6 ส่งข้อมูลผ่านซ็อกเก็ต โดยแปลงข้อมูลให้อยู่ในรูปสายธารของไบต์ 

บรรทัดที่ 7 รออ่านข้อมูลที่เซิร์ฟเวอร์จะตอบกลับมา บรรทัดที่ 8 แปลงข้อมูลจากสายธารของไบต์เป็นสตริง 

บรรทัดที่ 9 ปิดซ็อกเก็ตเชื่อมต่อ และบรรทัดที่ 10 พิมพ์ผลลัพธ์ออกมางจอภาพ จากนั้นโปรแกรมไคลเอนต์จะจบการทำงาน 

ครางนี้มาลองรันโปรแกรมกันดูครับ ผมจะรันโปรแกรมจากเทอร์มินอลที่เปิดในโปรแกรม VS Code นะครับ แต่ใครจะรันผ่านเทอร์มินัลที่ตัวเองใช้อย่าง command prompt ของ Windows หรือเทอร์มินัลของ Mac หรือ Linux ก็ได้ครับ คำสั่งที่ใช้รันโปรแกรมเซิร์ฟเวอร์ก็คือ python tcpserver.py ดังรูปครับ 


เมื่อรันเสร็จจะเห็นว่าเซิร์ฟเวอร์จะพิมพ์คำว่า  The server is waiting และหยุดรอรับการติดต่อจากไคลเอนต์

ต่อไปไปรันไคลเอนต์กันครับ ก็ให้เปิดเทอร์มินัลขึ้นมาอีกหน้าต่างหนึ่งครับ และใช้คำสั่ง python tcpclient.py ดังรูปครับ 


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

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

สำหรับโค้ดของโปรแกรมโหลดได้จากที่นี่ครับ 

วันศุกร์ที่ 1 กุมภาพันธ์ พ.ศ. 2562

การเขียนโปรแกรมเครือข่ายด้วยซ็อกเก็ต (Socket) ตอนที่ 10: ทดสอบการทำงานของ UDP Server & Client

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



 จากนั้นรันไคลเอนต์ด้วยคำสั่ง java UDPClient จะได้ผลัพธ์ดังนี้



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



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

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


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



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

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










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

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


  1. import java.io.*; 
  2. import java.net.*;
  3. import java.util.*; 
  4. class UDPClient { 
  5.    public static void main(String args[]) throws Exception { 
  6.       Scanner inFromUser = new Scanner(System.in);
  7.       DatagramSocket clientSocket = new DatagramSocket(); 
  8.       InetAddress IPAddress = InetAddress.getByName("localhost"); 
  9.       byte[] sendData = new byte[1024]; 
  10.       byte[] receiveData = new byte[1024]; 
  11.       System.out.print("Please enter words: ");
  12.       String sentence = inFromUser.nextLine();
  13.       sendData = sentence.getBytes();  
  14.       DatagramPacket sendPacket = 
  15.          new DatagramPacket(sendData, sendData.length, IPAddress, 9876); 
  16.       clientSocket.send(sendPacket); 
  17.       DatagramPacket receivePacket = 
  18.          new DatagramPacket(receiveData, receiveData.length); 
  19.       clientSocket.receive(receivePacket); 
  20.       String modifiedSentence = new String(receivePacket.getData()); 
  21.       System.out.println("FROM SERVER:" + modifiedSentence.trim()); 
  22.    clientSocket.close(); 
  23.    } 

บรรทัดที่ 7 สร้างซ็อกเก็ตเพื่อใช้ในการแลกเปลี่ยนข้อมูลกับเซิร์ฟเวอร์ บรรทัดที่ 8 แปลงชื่อโดเมนของเซิร์ฟเวอร์เป็นหมายเลขไอพี ในที่นี้เรารันโปรแกรมไคลเอนต์และเซิร์ฟเวอร์บนเครื่องเดียวกันชื่อโดเมนจึงเป็น localhost บรรทัดที่ 14-15 เตรียมแพ็กเก็ตที่ใช้ส่งข้อมูล โดยนอกจากข้อมูลที่จะส่งไปแล้ว ก็ต้องระบุหมายเลขไอพี และหมายเลขพอร์ตของโปรแกรมเซิร์ฟเวอร์ และถึงแม้เราจะไม่ได้ระบุหมายเลขไอพี และหมายเลขพอร์ตของไคลเอนต์ที่เป็นผู้ส่ง ข้อมูลนี้จะถูกใส่ลงไปในแพ็กเก็ตโดยอัตโนมัติ บรรทัดที่ 19 ส่งข้อมูลผ่านซ็อกเก็ตไปให้เซิร์ฟเวอร์ บรรทัดที่ 20 รอรับข้อมูลที่เซิร์ฟเวอร์จะตอบกลับมา

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

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

จากตอนที่ 7  ก็คงเข้าใจความแตกต่างระหว่างทีซีพี (TCP) กับยูดีพี (UDP) กันแล้วนะครับ คราวนี้เราจะมาเขียนโปรแกรมฝั่งเซิร์ฟเวอร์กัน โดยโปรแกรมเซิร์ฟเวอร์นี้จะทำงานเหมือน TCPServer คือรับข้อมูลสตริงจากไคลเอนต์ เอามาแปลงเป็นตัวอักษรตัวใหญ่ แล้วส่งกลับไปให้ไคลเอนต์ มาดูโค้ดกันเลยครับ


  1. import java.io.*; 
  2. import java.net.*; 
  3. class UDPServer { 
  4.   public static void main(String args[]) throws Exception 
  5.     { 
  6.     DatagramSocket serverSocket = new DatagramSocket(9876); 
  7.       byte[] receiveData = new byte[1024]; 
  8.       byte[] sendData  = new byte[1024];
  9.       while(true) 
  10.       { 
  11.             System.out.println("The server is waiting ");
  12.             DatagramPacket receivePacket = 
  13.              new DatagramPacket(receiveData, receiveData.length); 
  14.             serverSocket.receive(receivePacket);
  15.             String sentence = new String(receivePacket.getData());
  16.             InetAddress IPAddress = receivePacket.getAddress(); 
  17.             int port = receivePacket.getPort(); 
  18.             String capitalizedSentence = sentence.toUpperCase();
  19.             sendData = capitalizedSentence.getBytes(); 
  20.             DatagramPacket sendPacket = 
  21.                   new DatagramPacket(sendData, sendData.length, IPAddress, port); 
  22.               serverSocket.send(sendPacket); 
  23.         } 
  24.     } 
  25. }  
การสร้างซ็อกเก็ตจะใช้คลาส DatagramSocket ตามที่เขียนไว้ในบรรทัดที่ 6 ในที่นี้โปรแกรมเซิร์ฟเวอร์จะรอรับการติดต่ออยู่ที่พอร์ตหมายเลข 9876  บรรทัดที่ึ 7 และ 8 จะสร้างบัฟเฟอร์สำหรับรับและส่งข้อมูลตามลำดับ โดยในโปรแกรมนี้ระบุขนาดของการรับส่งข้อมูลไว้ที่ไม่เกิน 1024 ไบต์ บรรทัดที่ 12-13 สร้างแพ็กเก็ตรับข้อมูล โดยระบุว่าให้เอาข้อมูลมาเก็บไว้ในบัฟเฟอร์คือ receiveData ด้วยความยาวตามขนาดของบัฟเฟอร์ที่ประกาศไว้ ในตัวอย่างนี้คือ 1024 ไบต์ บรรทัดที่ 4 รออ่านข้อมูลที่ไคลเอนต์จะส่งมา โดยข้อมูลจะถูกเก็บลงแพ็กเก็ตที่ประกาศไว้ในบรรทัดที่ 12 

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

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

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

การเขียนโปรแกรมเครือข่ายด้วยซ็อกเก็ต (Socket) ตอนที่ 7: โปรโตคอลทีซีพี (TCP) กับ ยูดีพี (UDP)

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

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

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

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

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

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

วันพฤหัสบดีที่ 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 ซึ่งก็จะได้ผลลัพธ์ตามรูปครับ 



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

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









วันพุธที่ 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 5  ขึ้นมาก็ข้ามไปได้เลยนะครับ เพราะในจาวารุ่นใหม่จาวารันไทม์ (Java Runtime) จะจัดการเรื่อง Stub-Skeleton ให้

]

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

 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 ซึ่งโปรแกรมฝั่งไคลเอนต์ที่เราจะเขียนในตอนต่อไปจะค้นหาอ็อบเจกต์นี้โดยใช้ชื่อนี้

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


วันอังคารที่ 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 นะครับ ...

วันอังคารที่ 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) สามารถโหลดได้จากลิงก์นี้ครับ  ซึ่งจะได้โปรแกรมทั้งฝั่งเซิร์ฟเวอร์และไคลเอนต์เลย แต่บล็อกเกี่ยวกับไคลเอนต์โปรแกรมจะอยู่ในตอนหน้านะครับ

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

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

เขียนโปรแกรมเครือข่ายด้วยซ็อกเก็ต (Socket) ตอนที่ 2: รู้จักกับหมายเลขพอร์ต

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

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

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

ดังนั้นในการเลือกหมายเลขพอร์ตให้กับโปรแกรมเซิร์ฟเวอร์ นักเขียนโปรแกรมควรจะมีความเข้าใจในเรื่องการจัดการหมายเลขพอร์ต โดยหน่วยงานที่มีหน้าที่ในการจัดการหมายเลขพอร์ตก็คือ Internet Assigned Numbers Authority (IANA)  ซึ่งเป็นแผนกหนึ่งของ Internet Corporation for Assigned Names and Numbers (ICANN)

การจัดการหมายเลขพอร์ตจะมีการแบ่งหมายเลขพอร์ตออกเป็นช่วง ช่วงแรกคืออ 0-1,023 จะไม่นำมานำมาใช้กับโปรแกรมเรา และระบบปฎิบัติการหลายตัวก็จะไม่ให้เราลงทะเบียนโปรแกรมเราโดยใช้พอร์ตในช่วงนี้นี้ เพราะหมายเลขพอร์ตในช่วงนี้จะเป็นส่วนที่โปรแกรมเซิร์ฟเวอร์ที่เป็นมาตรฐานใช้ เช่นเว็บเซิร์ฟเวอร์ใช้หมายเลข 80 อีเมลเซิร์ฟเวอร์ใช้พอร์ตหมายเลข 25 เป็นต้น 

หมายเลข 1,024-49,151 เป็นช่วงหมายเลขพอร์ตที่ IANA กำหนดให้เป็นพอร์ตจดทะเบียน (registered port)  คือถ้าหน่วยงานใดที่ต้องการจะจดทะเบียนหมายเลขพอร์ตให้กับโปรแกรมของตัวเอง IANA ก็สามารถนำหมายเลขพอร์ตในช่วงนี้มาจดทะเบียนให้ได้ โปรแกรมเซิร์เวอร์ที่เราเขียนอาจจะใช้หมายเลขพอร์ตในช่วงนี้ก็ได้ แต่ก็ไม่ได้มีการบังคับหรือควบคุมใด ๆ ถ้าเราอยากเขียนโปรแกรมโดยใช้พอร์ตในช่วงนี้  

ส่วนช่วงที่เหลือคือ 49,152 ถึง 65,535 เรียกว่าเป็นไดนามิกพอร์ต (dynamic port) เป็นหมายเลขพอร์ตที่ IANA ไม่รับจดทะเบียน และไม่ได้มีการควบคุมใด ๆ คือตั้งใจให้หมายเลขในช่วงนี้ใช้เป็นหมายเลขพอร์ตชั่วคราว หรือใช้สำหรับโปรแกรมหรือบริการที่ใช้ภายในเครื่อง หรือเป็นช่วงหมายเลขพอร์ตที่สุ่มให้โปรแกรมฝั่งไคลเอนต์ได้ใช้   

ข้อมูลเพิ่มเติมของพอร์ตต่าง ๆ ที่มีการใช้งานจริงสามารถดูเพิ่มเติมได้จากลิงก์นี้ http://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers

ก็หวังว่าจะเข้าใจเรื่องหมายเลขพอร์ตมากขึ้นนะครับ แต่ไม่ต้องซีเรียสมาก เอาเป็นว่าโปรแกรมเซิร์ฟเวอร์ที่เราจะเขียนขึ้นต่อไปจะใช้พอร์ตตั้งแต่หมายเลข 1,024 ขึ้นไป ที่ว่างในเครื่องเราแล้วกันนะครับ โดยผมจะเลือกหมายเลขพอร์ตในช่วงของพอร์ตจดทะเบียน (1,024 - 49,151) เป็นหลักนะครับ พบกันต่อไปบทความหน้าครับ  

วันเสาร์ที่ 9 พฤศจิกายน พ.ศ. 2556

เขียนโปรแกรมเครือข่ายด้วยซ็อกเก็ต (Socket) ตอนที่ 1 รู้จักกับซ็อกเก็ต

แม้ว่าในปัจจุบันการพัฒนาโปรแกรมบนเครือข่ายเราจะพัฒนาในลักษณะที่เป็นเว็บแอพลิเคชัน หรือใช้มิดเดิลแวร์อย่างจาวาอีอี (Java EE) หรือ ไมโครซอฟท์ดอตเน็ต (Microsoft .Net) เป็นหลัก แต่พื้นฐานของโปรแกรมที่ทำงานบนเครือข่ายจะอยู่บนแนวคิดของซ็อกเก็ต (Socket) ยิ่งไปกว่านั้นแอพพลิเคชันหลัก ๆ บนอินเทอร์เน็ตที่เราใช้กันอยู่ในปัจจุบันนี้เช่นการติดต่อเพื่อขอหน้าเว็บระหว่างเว็บเบราว์เซอร์หรือเว็บเซิร์ฟเวอร์ หรือการรับส่งไฟล์โดยใช้โปรโตคอลเอฟทีพี (FTP)  ก็ใช้เพียงแค่แนวคิดของซ็อกเก็ต ในการทำงาน ดังนั้นการรู้จักและเข้าใจการเขียนโปรแกรมเครือข่ายโดยใช้ซ็อคเก็ต จึงยังจัดว่ามีประโยชน์อยู่ ในบทความตอนแรกนี้ผมจะกล่าวถึงว่า  ซ็อกเก็ตคืออะไร และในตอนต่อ ๆ ไป จะแสดงตัวอย่างของการเขียนโปรแกรมในภาษาต่าง ๆ อย่างเช่นภาษาจาวา (Java) ภาษาไพธอน (python) และอาจมีภาษาอื่น ๆ ด้วย ตามความขยันของผม แต่จริง ๆ แล้วถ้าเข้าใจแนวคิดในบทความนี้ผมคิดว่าก็สามารถที่จะนำไปประยุกต์กับภาษาอะไรก็ได้ครับ  

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

Photo by Greg Rosenke on Unsplash


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

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

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

ส่วนหมายเลขที่อยู่ของโปรแกรมเราจะใช้หมายเลขพอร์ต (port number) ครับ ซึ่งหมายเลขพอร์ตนี้ก็คือเลขจำนวนเต็มธรรมดานี่เองครับ โดยจะเป็นเลขจำนวนเต็มที่เริ่มจาก 0 ถึง 65,535 ซึ่งก็เท่ากับจำนวน 65,536 หรือพูดง่าย ๆ ก็คือ ตามทฤษฎีแล้ว คอมพิวเตอร์หนึ่งเครื่องสามารถมีโปรแกรมเครือข่ายทำงานอยู่ได้พร้อมกัน 65,536 โปรแกรม ถ้าจะเทียบกับตัวอย่างคอนโด ก็คือคอนโดมีจำนวนห้อง 65,536 ห้อง 

ช่วงของตัวเลขที่ได้นี้ก็มาจากจำนวนบิตในฟิลด์ที่ใช้เก็บหมายเลขพอร์ตในโปรโตคอลระดับชั้นทรานส์ปอร์ตอย่าง ทีซีพี (TCP) หรือ ยูดีพี (UDP) ที่มีจำนวน 16 บิตนั่นเองครับ 2 ยกกำลัง 16 มีค่าเท่ากับ 65,536 นั่นเอง  (ไม่รู้เรื่องก็ข้ามไปก่อนครับ ไปทำความเข้าใจเพิ่มเอาทีหลัง) รายละเอียดมากกว่านี้ของหมายเลขพอร์ตจะพูดถึงในตอนต่อไปครับ เอาเป็นว่าจะขอยกตัวอย่างหมายเลขพอร์ตตัวหนึ่งที่เป็นมาตรฐานสำหรับโปรแกรมเว็บเซิร์ฟเวอร์ก็แล้วกันนะครับนั่นก็คือพอร์ตหมายเลข 80  ดังนั้นสมมติว่าถ้าเว็บเบราว์เซอร์ต้องการติดต่อกับโปรแกรมเว็บเซิร์ฟเวอร์ที่ทำงานอยู่บนเครื่องที่มีหมายเลข IP 161.246.123.123 ก็ต้องระบุทั้งหมายเลขไอพีดังกล่าว และหมายเลขพอร์ตซึ่งคือ 80

Photo by Kevin Horvat on Unsplash
 

ถึงตอนนี้หลายคนอาจบอกว่าไม่เคยเห็นต้องใส่เลขไอพีและเลขพอร์ตเลยเวลาเข้าถึงเว็บไซต์ เหตุผลก็คือในอินเทอร์เน็ตจะใช้ DNS (Doamin Name Server) เป็นกลไกในการแปลง URL ของเว็บไซต์ที่เราป้อนเป็นหมายเลขไอพีของเครื่องเซิร์ฟเวอร์ให้เราครับ ส่วนพอร์ต 80 เป็นพอร์ตมาตรฐาน ซึ่งถ้าเว็บเซิร์ฟเวอร์ใช้พอร์ตนี้อยู่แล้วก็ไม่จำเป็นต้องระบุ 

สำหรับการใช้งานซ็อกเก็ตเอพีไอนั้นแต่ละโปรแกรมก็ต้องระบุที่อยู่ทั้งสองตัวเพื่อสร้างเต้ารับของตัวเอง ซึ่งในการใช้งานซ็อกเก็ตเอพีไอจะเรียกเลขที่อยู่ทั้งสองว่าที่อยู่ซ็อกเก็ต (Socket Address) หรือจะแปลว่าที่อยู่เต้ารับดี :)  พูดง่าย ๆ ก็คือ 

Socket Address = IP Address + Port Number นั่นเองครับ 

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