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

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

วันศุกร์ที่ 28 สิงหาคม พ.ศ. 2563

นิยามของค่าสัมบูรณ์เรื่องง่าย ๆ ที่หลายคนยังงง ๆ อยู่

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

1. |x| = x เมื่อ x >= 0 
2. |x| = -x เมื่อ x < 0 

ในวิชาที่ผมสอนวิชาหนึ่งคือ Automata Theory ผมก็จะทบทวนการพิสูจน์ทางคณิตศาสตร์ให้นักศึกษานิดหน่อย และก็ให้นิยามนี้ เพื่อให้นักศึกษาได้ใช้ในการทบทวนทำแบบฝึกหัดเรื่องการพิสูจน์ ซึ่งแทบทุกครั้งที่สอนก็มักจะมีคำถามจากนักศึกษาว่า นิยามผิดหรือเปล่า เพราะเคยเรียนมาแต่ว่าค่าสัมบูรณ์ต้องมีค่าเป็นบวก ทำไม |x| = -x ซึ่งผมก็มักจะบอกให้ดูนิยามดี ๆ แต่หลายคนดูแล้วก็ยังไม่เข้าใจอยู่ดี ก็เลยต้องอธิบายให้ฟัง แล้วก็ทำซ้ำ ๆ มาแบบนี้หลายปีแล้ว ก็เลยคิดว่ามาเขียนบล็อกไว้หน่อยแล้วกัน ต่อไปถ้าถามอีกก็จะให้มาอ่านในบล็อก และก็คิดว่าน่าจะเป็นประโยชน์กับคนที่ยังไม่เข้าใจด้วย 
 
ผมขออธิบายง่าย ๆ ด้วยการยกตัวอย่างครับ คือทุกคนรู้ว่า |3| = 3 และ |-3| = 3 

มาดูกรณี  |3|  ในที่นี้ x = 3 ดังนั้น x > 0 
เพราะฉะนั้น |x| = x จากนิยามในข้อ 1 
นั่นคือ  |3| = 3 (เพราะ  x = 3) 

ในกรณี |-3| ในที่นี้ x = -3 ดังนั้น x < 0 
เพราะฉะนั้น |x| = -x จากนิยามในข้อ 2 
นั่นคือ  |-3| = -(-3) (เพราะ  x = -3) 
และ -(-3) = 3 
ดังนั้น |-3| = 3

ก็หวังว่าจะเข้าใจกันถึงนิยามของค่าสัมบูรณ์กันมากขึ้นแล้วนะครับ
 
 

วันศุกร์ที่ 31 กรกฎาคม พ.ศ. 2563

แนะนำวิชาบล็อกเชนสำหรับคนที่ไม่ใช่นักพัฒนาบน Coursera

วันนี้ผมจะมาแนะนำวิชาเรียนออนไลน์บน coursera.org ในวิชาที่เกี่ยวกับบล็อกเชน (blockchain) แต่ไม่ได้เกี่ยวกับการพัฒนาโปรแกรมนะครับ แต่จะเป็นเนื้อหาบล็อกเชนที่ให้คนทั่ว ๆ ไป เข้าใจ โดยเฉพาะคนที่อยู่ในภาคธุรกิจที่อาจเคยได้ยินคำว่าบล็อกเชนมา แต่ไม่รู้ว่ามันจะมาใช้ในทางธุรกิจยังไงบ้าง หรือคนที่อาจจะรู้จักแต่บิตคอยน์ (bitcoin) และอาจเข้าใจว่าบล็อกเชนกับบิตคอยน์เป็นเรื่องเดียวกัน ถ้าได้เรียนวิชาพวกนี้ก็จะเข้าใจบล็อกเชนมากขึ้น ถึงหลายคนที่ตอนนี้อาจรู้อยู่แล้ว หรืออาจบอกว่าหาอ่านเอาบนอินเทอร์เน็ต ดูบน YouTube ก็ได้ แต่ถ้าได้เรียนฟรี ๆ และอาจมีประกาศนียบัตรติดตัวด้วย อันนี้ก็น่าสนใจใช่ไหมครับ 

Coursera | Build Skills with Online Courses from Top Institutions
Coursera Logo

สำหรับคนที่ยังไม่รู้ว่า Coursera คืออะไร ก็ขอสรุปง่าย ๆ ว่า Coursera เป็นแพลตฟอร์มเรียนออนไลน์รายใหญ่ของโลก มีวิชาที่น่าสนใจจากมหาวิทยาลัยชั้นนำ และบริษัทชั้นนำอย่างเช่น Google มาเปิดให้เลือกเรียนมากมายครับ โดยในช่วง COVID-19 นี้ เขาเปิดโอกาสให้บุคคลากรและนักศึกษาของมหาวิทยาลัยที่เข้าร่วมโครงการ เรียนได้ฟรีและได้ประกาศนียบัตรด้วย โดยขยายขอบเขตให้สมัครเข้าเรียนได้ถึง 30 กันยายน 2563 นี้ครับ ตามปกติ  Coursera ก็มีวิชาให้เรียนฟรีอยู่แล้ว แต่จะไม่ได้ประกาศนียบัตร ถ้าอยากได้ประกาศนียบัตรต้องจ่ายเงิน แต่เขาก็ยังให้ทางเลือกนะครับคือถ้าเราสมัครเป็นครั้งแรกเขา จะให้ทดลองเรียนฟรี  7 วัน คือเราต้องให้ข้อมูลบัตรเครดิตเขาไปก่อน และเราสามารถยกเลิกได้ภายใน 7 วันนี้ นั่นหมายความว่าถ้าภายใน 7 วันนี้เราเรียนจบวิชาอะไรก็จะได้ประกาศนียบัตรด้วยครับ และจะเข้ามาดูเนื้อหาวิชาที่เราเคยลงเรียนไว้แล้วได้ตลอด     

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

วิชาที่ผมจะนำมาแนะนำก็เป็นวิชาที่สอนโดยวิทยากรสองคน ที่เป็นคนเขียนหนังสือบล็อกเชนที่ขายดีระดับโลกคือ Don Tapscott และ Alex Tapscott หนังสือที่เขาเขียนก็คือ Blockchain Revolution ครับ 

[ภาพปกหนังสือ Blockchain Revolution. Credited: amazon.com]

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

วิชาที่ผมจะแนะนำ เพราะเป็นวิชาที่ให้ความรู้ทั่ว ๆ ไป  มี 2 วิชาครับคือ Introduction to Blockchain for Financial Services กับ Cryptoassets, and Decentralized Finance

[Introduction to Blockchain for Financial Services course; Coursera]


มาเริ่มที่วิชาแรกคือ Introduction to Blockchain for Financial Services กันครับ วิชานี้แบ่งเป็น 5 โมดูล โดยเขาจะให้เรียนหนึ่งโมดูลต่อหนึ่งสัปดาห์ นั่นคือถ้าเราเรียนตามโปรแกรมเขาก็จะใช้เวลาเดือนเศษ ๆ ครับ โดย 5 โมดูลมีดังนี้ครับ

  • The Second Era of the Internet 
  • Blockchain Design Principles
  • Public and Private Ledger
  • The Blockchain Ecosystem
  • Blockchain Implementation Challenges
ในโมดูลแรก The Second Era of the Internet จะเป็นการเปรียบเทียบให้เห็นว่าบล็อกเชนกับอินเทอร์เน็ตที่เราใช้กันมีความสัมพันธ์กัน และมีความแตกต่างกันอย่างไร 

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

โมดูลที่ 3 Public and Private Ledger ในโมดูลนี้จะพูดถึงประเภทของบล็อกเชน พวกเราอาจเคยได้ยินแต่บิตคอยน์ ซึ่งจริง ๆ แล้ว เป็น public blockchain แต่จริง ๆ แล้ว มันยังมีประเภทอื่น ๆ ของบล็อกเชนอีก และในโมดูลนี้ยังได้พูดถึงการรักษาความเป็นส่วนตัวบนบล็อกเชนด้วยครับ

โมดูลที่ 4 The Blockchain Ecosystem จะพูดถึงประเภทของผู้ที่เกี่ยวข้อง และบทบาทของผู้เกี่ยวข้องในบล็อกเชน โดยเขาได้แบ่งผู้เกี่ยวข้องออกเป็น 9 ฝ่ายครับ ซึ่งแน่นอนว่าพวกเราส่วนใหญ่จะอยู่ในส่วนของ user ส่วนตัวผมเองก็จะอยู่ในส่วนของภาคการศึกษา (education) ด้วย แต่มันจะมีฝ่ายอะไรอีกก็ลองไปดูกันครับ 

โมดูลที่ 5 Blockchain Implementation Challenges ในโมดูลนี้จะพูดถึงปัญหาและอุปสรรคที่เกิดกับการพัฒนาการของบล็อกเชนครับ ซึ่งก็เหมือนกับเทคโนโลยีที่เพิ่งเปิดตัวแหละครับ ก็มักจะมีปัญหา และมีความไม่เข้าใจจากฝ่ายที่อยู่บนเทคโนโลยีเดิม ๆ อย่างที่พวกเราคงได้ข่าวกันว่ารัฐบาลหลาย ๆ ประเทศยังไม่รองรับการใช้เงินคริปโตเป็นต้น ในโมดูลนี้ก็จะแสดงให้เห็นถึงปัญหา และแนวทางการแก้ปัญหาครับ 

เอาจริง ๆ แล้ว เรียนแค่วิชานี้ ก็เข้าใจบล็อกเชนได้ในระดับหนึ่งแล้วครับ แต่ถ้าใครยังอยากรู้เรื่องแอปพลิเคชันของบล็อกเชน อย่างเช่นพวกเงินคริปโต อยากรู้จักว่า Smart Contracts มันคืออะไร หรืออาจเคยได้ยินคำว่า DApps แต่ไม่รู้ว่ามันคืออะไร ก็เรียนวิชานี้ต่อเลยครับ  Blockchain, Cryptoassets, and Decentralized Finance โดยวิชานี้แบ่งเป็นแค่ 4 โมดูล ดังนี้ครับ 

  • Cryptoassets
  • Smart Contracts
  • Identity
  • DApps and Distributed Business Models
[Blockchain, Cryptoassets, and Decentralized Finance; Coursera]



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

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

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

โมดูลที่ 4 DApps and Distributed Business Models จะพูดถึงแอปพลิเคชันที่ต่อไปเราจะใช้กัน ซึ่งเบื้องหลังการทำงานของแอปพลิเคชันเหลานี้ก็คือบล็อกเชนครับ โดยเขายกตัวอย่างใกล้ตัวที่เรารู้จักกันดีอย่าง Airbnb ซึ่งตอนนี้ไม่ได้ทำงานบนบล็อกเชน ถ้าต่อไปเรามี bAirbnb ซึ่งทำงานบนบล็อกเชนจะมีข้อดีกว่ายังไง และก็ยังพูดถึงการใช้บล็อกเชนในเรื่องการป้องกันทรัพย์สินทางปัญญา ซึ่งเป็นปัญหาใหญ่ของอินเทอร์เน็ตในปัจจุบัน 

ในส่วนตัวถ้าเรียนสองวิชานี้จบ ผมเชื่อว่าน่าจะเข้าใจภาพรวมของบล็อกเชนในมุมมองของผู้ใช้ทั่วไปได้ค่อนข้างดีมากเลยครับ แต่ถ้าใครอยู่ในวงการการเงินและบัญชี ผมก็ขอเสริมแนะนำวิชานี้ Blockchain Transformation of Financial Services  อีกวิชาครับ 

[Blockchain Transformation of Financial Services; Coursera]

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

[ตัวอย่างประกาศนียบัตรจาก Coursera]


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

สำหรับคนที่เรียนสองวิชาที่วิทยากรสองคนนี้สอนแล้วรู้สึกอย่างเรียนเพิ่มกับสองคนนี้ เท่าที่ผมสำรวจดู
วิชาด้านบล็อกเชนที่สอนโดยสองคนนี้ มี 8 วิชาครับ โดยเขาจัดกลุ่มวิชาออกเป็นสิ่งที่ Coursera เรียกว่า Specialization โดย Specialization ที่เก่ากว่าชื่อว่า Blockchain Revolution เหมือนชื่อหนังสือของเขาเลยเลยครับ  โดย Specialization นี้จะมี  4 วิชาคือ  

  • Introduction to Blockchain Technologies  
  • Transacting on the Blockchain
  • Blockchain and Business: Applications and Implications 
  • Blockchain Opportunity Analysis  

Specialization ที่สองชื่อว่า  Blockchain Revolution in Financial  Services ครับ อันนี้จะใหม่กว่า มีการปรับปรุง และพูดถึงหนังสือเล่มใหม่ที่เขียนด้วย ใน Specialization นี้ก็มี 4 วิชาครับคือ 

  • Introduction to Blockchain for Financial Services
  • Blockchain, Cryptoassets, and Decentralized Finance
  • Blockchain Transformations of Financial Services 
  • Blockchain in Financial Services: Strategic Action Plan 

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

และจากที่ผมสำรวจมาอีกก็พบครับว่า วิชา Introduction to Blockchain Technologies กับ Introduction to Blockchain for Financial Services  นั้นเนื้อหาแทบจะเหมือนกันเลย และข้อสอบก็เหมือนกัน ดังนั้นถ้าเรียน Introduction to Blockchain for Financial Services ผ่านแล้ว เราสามารถสมัครเรียนวิชา Introduction to Blockchain Technologies แล้วไปทำข้อสอบได้เลยครับ ไม่ต้องไปเสียเวลาดูวีดีโออีก เราก็จะได้ประกาศนียบัตรมาอีกใบหนึ่งเลยครับ ถ้าใครชอบล่าประกาศนียบัตรเท่ากับเรียนหนึ่งได้ถึงสอง และเช่นเดียวกันครับวิชา Transacting on the Blockchain กับวิชา Blockchain, Cryptoassets, and Decentralized Finance ก็แบบเดียวกันเลยครับ นั่นคือถ้าวางแผนกันดี ๆ นี่เราจะได้ประกาศนียบัตร 4 ใบเลยนะครับ เรียน 2 แถม 2 :) 

ส่วนวิชาที่เหลืออีก 3 วิชา ผมยังไม่ได้ลองเรียนดูนะครับ เหตุผลหลัก ๆ คือยังมีวิชาอื่นที่ผมสนใจมากกว่า และมันมีส่วนที่ผมไม่ชอบที่สุดใน Coursera คือข้อสอบที่เป็น Peer Review ครับ ข้อสอบแบบ Peer Review ก็คือ เราทำแล้วคนที่เรียนกับเราเป็นคนตรวจ โดยเราก็จะต้องไปตรวจคนอื่นด้วย 2-3 คน ดูตามแนวคิดมันก็ดีใช่ไหมครับ แต่จากประสบการณ์ที่ผมเจอเมื่อผมไปตรวจคนอื่นคือ หลายคนไม่รู้เรื่องที่เรียนเลย บางคนตอบไม่ตรงคำถาม บางคนไป copy อะไรไม่รู้ ไม่เกี่ยวกับเรื่องที่เรียนด้วยซ้ำมาตอบ บางคนตอบ Good คำเดียว แล้วคนแบบนี้ต้องมารีวิวเรา บางคนตอบมั่ว ๆ มา เพื่ออะไรรู้ไหมครับ เพราะใน Coursera บอกว่าเราต้องตอบก่อน ถึงจะไปรีวิวคนอื่นได้ บางคนตอบมั่วเพื่อที่จะได้เห็นคำตอบคนอื่น แล้วก็ copy คำตอบคนอื่นมาเป็นของตัวเอง ตอนผมตรวจให้คนอื่นนี่เห็นเลยครับว่าลอกต่อ ๆ กันมา ซึ่งผมว่าถ้า Coursera ยังใช้โมเดลนี้ต่อไป มันจะทำให้ประกาศนียบัตรลดความน่าเชื่อถือลงในวิชาที่เป็นแบบนี้ แต่เท่าที่เห็นตอนนี้ Coursera เริ่มมีระบบเชิญคนที่เรียนผ่านไปแล้วและมีความรู้มาช่วยเป็น mentor แล้ว แต่ไม่รู้ว่าเขาจะเอามาช่วยตรวจไหมนะครับ

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

วันพฤหัสบดีที่ 23 กรกฎาคม พ.ศ. 2563

ปัญหาเบสคลาสแตกง่ายเชิงความหมาย (Semantic Fragile Base Class Problem)

บล็อกนี้ก็เป็นบล็อกที่สามในชุดของปัญหาเบสคลาสแตกง่าย (Fragile Base Class Problem) โดยผมจะขอเรียกสั้น ๆ ว่า FBC ถ้าใครยังไม่ได้อ่านจะกลับไปอ่านก่อนก็ได้นะครับ FBCSyntactic FBC แต่ถ้าจะไม่อ่านก็ไม่เป็นไรนะครับอ่านบล็อกนี้ก่อนได้

สำหรับปัญหา Semantic FBC เป็นปัญหาที่มีผลกระทบมากกว่า Syntactic ครับ เพราะเป็นปัญหาระดับการออกแบบ ไม่ใช่ปัญหาระดับภาษาโปรแกรม นั่นหมายความว่าไม่ว่าเราจะเขียนโปรแกรมด้วยภาษา object-oriented ใด ๆ ถ้ามีการใช้ inheritance และ polymorphism มีโอกาสมีปัญหาได้หมด 

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

เพื่อให้เข้าใจปัญหานี้มาเริ่มดูโค้ดภาษา Java ต่อไปนี้กันครับ ถ้าใครไม่อยากพิมพ์โค้ดเองดาวน์โหลดได้จากที่นี่เลยครับ 

public class SemanticFBCSuperclass {
private int number;
public SemanticFBCSuperclass(int val) {
number = val;
}
public void f1() {
System.out.println("In f1() of SemanticFBCSuperClass");
//number++;
setNumber(number+1);
}
public void setNumber(int val) {
number = val;
}
}

คลาสนี้เป็นคลาสแม่นะครับ ให้สังเกตเมท็อด f1() นะครับมีการพิมพ์ข้อความหนึ่งบรรทัด และเพิ่มค่า number ซึ่งเป็น attribute ในคลาสนี้ขึ้น 1โดยใช้เมท็อด setNumber()

ต่อไปไปดูคลาสลูกของคลาสแม่ตัวนี้กันครับ

public class SemanticFBCSubclass extends SemanticFBCSuperclass {
public SemanticFBCSubclass(int val) {
super(val);
}
@Override
public void setNumber(int val) {
System.out.println("setNumber() overridden version");
super.setNumber(val);
}
}

จะเห็นว่าในคลาสลูกนี้ โอเวอร์รายด์ (override) เมท็อด setNumber() ของคลาสแม่ โดยพิมพ์ข้อความหนึ่งบรรทัด ก่อนที่จะเรียกเมท็อด setNumber() ของคลาสแม่ 

คราวนี้มาดู Main โปรแกรมกันครับ 

*
* @author sarun
*/
public class FBC {
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
SemanticFBCSubclass obj = new SemanticFBCSubclass(5);
obj.f1();

}
}

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

sarunintakosum@Saruns-MacBook-Pro semanticfbc % javac *.java
sarunintakosum@Saruns-MacBook-Pro semanticfbc % java FBC    
In f1() of SemanticFBCSuperClass
setNumber() overridden version
sarunintakosum@Saruns-MacBook-Pro semanticfbc % 

มาดูที่ผลัพธ์กันครับ ผลลัพธ์สองบรรทัดมาจากเมื่อเรียกเมท็อด f1() ก็จะเริ่มต้นทำงานโดยพิมพ์คำว่า 
In f1() of SemanticFBCSuperClass

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

setNumber() overridden version

จากนั้นจะกลับไปเรียก setNumber() ของคลาสแม่ต่อไป ถ้าตอนนี้ยังไม่เข้าใจก็เอาเป็นแค่ว่ามีผลลัพธ์สองบรรทัดนะครับ 

คราวนี้สมมติว่ามีการทบทวนคลาสแม่กันใหม่ แล้วก็เห็นว่าใน f1() ถ้าจะเพิ่มค่า number ขึ้น 1 จะทำงานเร็วขึ้น ถ้าเพิ่มค่าตรง ๆ เลย ไม่ต้องไปเรียกเมท็อด setNumber() ดังนั้นคลาสแม่จึงถูกแก้เป็นดังนี้ครับ 

public class SemanticFBCSuperclass {
private int number;
public SemanticFBCSuperclass(int val) {
number = val;
}
public void f1() {
System.out.println("In f1() of SemanticFBCSuperClass");
number++;
//setNumber(number+1);
}
public void setNumber(int val) {
number = val;
}
}

จะเห็นนะครับว่าใน f1() ไม่เรียก setNumber() แล้ว 

จากนั้นคอมไพล์คลาสแม่และรันโปรแกรมครับ

sarunintakosum@Saruns-MacBook-Pro semanticfbc % javac SemanticFBCSuperclass.java
sarunintakosum@Saruns-MacBook-Pro semanticfbc % java FBC                        
In f1() of SemanticFBCSuperClass
sarunintakosum@Saruns-MacBook-Pro semanticfbc % 

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

ดังนั้นคงเห็นแล้วนะครับว่าปัญหา FBC โดยเฉพาะ Semantic FBC ทำให้เกิดปัญหาถ้าต้องมีการเปลี่ยนแปลงโปรแกรม เนื่องจากมีความขึ้นต่อกันระหว่างคลาสแม่และคลาสลูก ดังนั้นในการพัฒนาโปรแกรมโดยใช้แนวคิดของคอมโพเนนต์ จึงหลีกเลี่ยงการใช้งาน inheritance และ polymorphism และใช้การประกอบแทน 

ก็เป็นอันว่าจบชุดของปัญหา FBC กันไว้ตรงนี้นะครับ โอกาสหน้าจะเขียนเรื่องอะไร ถ้าสนใจก็คอยคิดตามได้นะครับ

วันพฤหัสบดีที่ 16 กรกฎาคม พ.ศ. 2563

ปัญหาเบสคลาสแตกง่ายเชิงไวยากรณ์ (Syntactic Fragile Base Class Problem)

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

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

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

 เริ่มจาก C++ ก่อนแล้วกันนะครับ 

สมมติมีคลาส foo แบบนี้นะครับ


//foo.hpp
class foo {
private:
int a;
public:
void setA(int val);
int getA();
void f1();
//void f3();
};

//foo.cpp
#include "foo.hpp"
#include <iostream>
using namespace std;
void foo::setA(int val) {
a = val;
}
int foo::getA() {
return a;
}
void foo::f1() {
cout << "I am f1" << endl;
}

/*void foo::f3() {
cout << "I am f3" << endl;
}*/

ให้สังเกตที่ผมคอมเมนต์ไว้นะครับ ตอนนี้คลาส foo ยังไม่มีเมท็อด f3() 

ต่อไปมาดูคลาส bar ซึ่งเป็นคลาสลูกของ foo ครับ

//bar.hpp
#include "foo.hpp"
class bar: public foo {
public:
void f2();
void f3();
};

//bar.cpp
#include "bar.hpp"
#include <iostream>
using namespace std;
void bar::f2() {
f1();
cout << "I am f2" << endl;
}
void bar::f3() {
cout << "I am f3" << endl;
}

จะเห็นนะครับว่า เมท็อด f3() ตอนนี้ถูกอิมพลีเมนต์ในคลาส bar

สมมติว่าสองคลาสนี้เราไม่ได้เขียนเอง มีคนส่ง object code มาให้เรา พร้อมทั้ง header file นั่นคือสองคลาสนี้ถูกคอมไพล์ด้วยคำสั่ง 

g++ -c foo.cpp 
g++ -c bar.cpp

ซึ่งจะทำให้ได้ objce file ชื่อ foo.o และ bar.o ตามลำดับ ดังตัวอย่างด้านล่าง และไฟล์ .o ถูกส่งมาให้เรา พร้อมทั้ง foo.hpp และ bar.hpp

sarunintakosum@Saruns-MacBook-Pro cpp % g++ -c bar.cpp
sarunintakosum@Saruns-MacBook-Pro cpp % g++ -c foo.cpp
sarunintakosum@Saruns-MacBook-Pro cpp % ls *.o
bar.o   foo.o
sarunintakosum@Saruns-MacBook-Pro cpp % 

ต่อไปสมมติว่าเราเขียนคลาส dar ให้เป็นลูกของคลาส bar ดังนี้ครับ

//dar.hpp
#include "bar.hpp"
class dar: public bar {
public:
void f4();
};


//dar.cpp
#include "dar.hpp"
#include <iostream>
using namespace std;
void dar::f4() {
f3();
cout << "I am f4" << endl;
}

ให้สังเกตว่าเมท็อด f4() เรียกใช้ เมท็อด f3() ซึ่งในเวอร์ชันนี้อยู่ในคลาส bar

และสมมติ main program เป็นดังนี้ 

#include <iostream>
#include "dar.hpp"
using namespace std;
int main() {
dar *obj = new dar();
obj->f4();
return 0;
}

จากนั้นใช้คำสั่งดังนี้ 

sarunintakosum@Saruns-MacBook-Pro cpp % g++ -c main.cpp      
sarunintakosum@Saruns-MacBook-Pro cpp % g++ -c dar.cpp
sarunintakosum@Saruns-MacBook-Pro cpp % g++ -o main main.o foo.o bar.o dar.o
และรันโปรแกรม
sarunintakosum@Saruns-MacBook-Pro cpp % ./main
I am f3
I am f4
sarunintakosum@Saruns-MacBook-Pro cpp % 

จะเห็นว่าคอมไพล์ผ่าน และรันได้ผลลัพธ์ถูกต้อง

สมมติว่ามีการแก้คลาส  foo และ bar โดยการเลื่อนเมท็อด f3() จาก bar ขึ้นไป foo ดังนี้ 

//foo.hpp
class foo {
private:
int a;
public:
void setA(int val);
int getA();
void f1();
void f3();
};

//foo.cpp
#include "foo.hpp"
#include <iostream>
using namespace std;
void foo::setA(int val) {
a = val;
}
int foo::getA() {
return a;
}
void foo::f1() {
cout << "I am f1" << endl;
}
void foo::f3() {
cout << "I am f3" << endl;
}

//bar.hpp
#include "foo.hpp"
class bar: public foo {
public:
void f2();
//void f3();
};

//bar.cpp
#include "bar.hpp"
#include <iostream>
using namespace std;
void bar::f2() {
f1();
cout << "I am f2" << endl;
}
/*void bar::f3() {
cout << "I am f3" << endl;
}*/

จากนั้นทั้งสองคลาสถูกคอมไพล์และ ไฟล์ foo.o และ bar.o และ foo.hpp กับ bar.hpp  ตัวใหม่ถูกส่งกลับมาที่เรา คราวนี้สมมติว่าเราเห็นว่าเราไม่ได้แก้โปรแกรม dar.cpp และ main.cpp เลย เราก็เลยใช้คำสั่งต่อไปนี้ลิงก์โปรแกรมเข้าด้วยกัน 
sarunintakosum@Saruns-MacBook-Pro cpp % g++ -o main main.o foo.o bar.o dar.o
Undefined symbols for architecture x86_64:
  "bar::f3()", referenced from:
      dar::f4() in dar.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
sarunintakosum@Saruns-MacBook-Pro cpp % 

ซึ่งจะเห็นนะครับว่ามีข้อผิดพลาดเกิดขึ้นจากการลิงก์ เพราะจากข้อมูลเดิมของ dar.o  f3() ต้องอยู่ใน bar ซึ่งจริง ๆ แล้วตามแนวคิดของ object-oriented คลาส dar ซึ่งเป็นคลาสลูก ไม่มีความจำเป็นต้องรู้นะครับว่า f3() อยู่ที่ไหนกันแน่จะอยู่ที่คลาสแม่ หรือคลาสยาย :) ก็ควรจะเรียกได้เหมือนกัน ตราบใดที่ยังอยู่ในลำดับชั้นของครอบครัวเดียวกัน แต่จากข้อผิดพลาดนี้ C++ บอกเราว่าเราต้องรู้ครับ  

ซึ่งข้อผิดพลาดนี้แก้ได้ด้วยการคอมไพล์คลาส dar แล้วก็ลิงก์ใหม่ครับ

sarunintakosum@Saruns-MacBook-Pro cpp % g++ -c dar.cpp
sarunintakosum@Saruns-MacBook-Pro cpp % g++ -o main main.o foo.o bar.o dar.o
sarunintakosum@Saruns-MacBook-Pro cpp % ./main                              
I am f3
I am f4
sarunintakosum@Saruns-MacBook-Pro cpp % 

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

แต่จะเรียกว่าข่าวดีได้หรือเปล่าไม่รู้นะครับ เพราะภาษา object-oriented ที่ยังมีปัญหานี้อยู่เท่าที่รู้ก็คือ C++ เท่านั้น ซึ่งเหตุผลที่ C++ ใช้ในการไม่แก้ก็คือเรื่องของประสิทธิภาพของโปรแกรมครับ คือถ้าใช้วิธีที่ใช้อยู่นี้จะได้โปรแกรมที่ทำงานเร็วกว่าการไปแก้ปัญหานี้ ดังนั้นถ้าใช้ C++ ต้องขยันคอมไพล์ครับ :) 

คราวนี้ลองมาดูภาษาที่ไม่มีปัญหานี้กันบ้างนะครับ ตัวอย่างที่จะมาดูกันก็คือภาษา Java ครับ ซึ่งก็ขอใช้โปรแกรมที่ทำงานเหมือนกับ C++ นะครับ เริ่ม จากคลาส Foo และ Bar 

public class Foo {
int a;
public void setA(int val) {
a = val;
}
public int getA() {
return a;
}
void f1() {
System.out.println("I am f1");
}
/*void f3() {
System.out.println("I am f3");
}*/
}


public class Bar extends Foo {
public void f2() {
System.out.println("I am f2");
}
public void f3() {
System.out.println("I am f3");
}
}
 
ซึ่งในเวอร์ชันแรกนี้ให้สังเกตว่าเมท็อด f3() อยู่ในคลาส Bar นะครับ และก็สมมติเหมือนเดิมว่าสองคลาสนี้เราไม่ได้เขียนเองแต่มีคนคอมไพล์แล้วส่งไฟล์ .class มาให้เรา 

sarunintakosum@Saruns-MacBook-Pro java % javac Foo.java
sarunintakosum@Saruns-MacBook-Pro java % javac Bar.java
sarunintakosum@Saruns-MacBook-Pro java % ls *.class
Bar.class       Foo.class
sarunintakosum@Saruns-MacBook-Pro java %


และเราก็เขียนคลาส Dar ขึ้นมาดังนี้ครับ 

public class Dar extends Bar {
public void f4() {
f3();
System.out.println("I am f4");
}
}
  
ก็เหมือนใน เวอร์ชัน C++ นะครับ คือเป็นลูกของ Bar มีเมท็อด f4() ซึ่งเรียกใช้เมท็อด f3() ส่วน Main โปรแกรมก็เป็นดังนี้ครับ 
public class Main {
public static void main(String[] args) {
Dar obj = new Dar();
obj.f4();
}
}
 
จากนั้นเราก็คอมไพล์ Main และ Dar และรันโปรแกรมครับ 

sarunintakosum@Saruns-MacBook-Pro java % javac Dar.java
sarunintakosum@Saruns-MacBook-Pro java % javac Main.java
sarunintakosum@Saruns-MacBook-Pro java % java Main
I am f3
I am f4
sarunintakosum@Saruns-MacBook-Pro java % 

ต่อไปก็จะสมมติเหมือนเดิมนะครับว่ามีการย้ายเมท็อด f3() จาก Bar ไป Foo และมีการส่ง .class ตัวใหม่มาให้เรา

public class Foo {
int a;
public void setA(int val) {
a = val;
}
public int getA() {
return a;
}
void f1() {
System.out.println("I am f1");
}
void f3() {
System.out.println("I am f3");
}
}

public class Bar extends Foo {
public void f2() {
System.out.println("I am f2");
}
/*public void f3() {
System.out.println("I am f3");
}*/
}


sarunintakosum@Saruns-MacBook-Pro java % javac Foo.java 
sarunintakosum@Saruns-MacBook-Pro java % javac Bar.java 
sarunintakosum@Saruns-MacBook-Pro java % 

แต่เมื่อได้ .class ใหม่มาแล้ว เนื่องจากไม่มีการแก้ไขใด ๆ ใน Dar และ Main ดังนั้นผมก็จะรันโปรแกรม Main เลยนะครับ 

sarunintakosum@Saruns-MacBook-Pro java % java Main
I am f3
I am f4
sarunintakosum@Saruns-MacBook-Pro java %


ซึ่งก็จะเห็นว่าโปรแกรมทำงานได้ถูกต้อง ก็แสดงว่า Java ไม่มีปัญหานี้นะครับ 

เมื่ออ่านมาถึงตรงนี้ ก็หวังว่าจะได้เข้าใจ Syntactic Fragile Base Class Problem กันแล้วนะครับ โดยสรุปปัญหานี้เป็นปัญหาระดับตัวภาษา ซึ่งภาษาที่ยังมีปัญหานี้ก็เช่น C++ 

ในบล็อกต่อไป จะเขียนถึงปัญหา FBC ที่ไม่ได้ขึ้นกับภาษาโปรแกรม แต่เป็นปัญหาในด้านการออกแบบของแนวคิด object-oriented เอง นั่นคือปัญหา Semantic Fragile Base Class Problem ครับ

วันศุกร์ที่ 10 กรกฎาคม พ.ศ. 2563

ปัญหาเบสคลาสแตกง่าย (Fragile Base Class Problem)

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

วันนี้มาเริ่มด้วยเรื่องทางด้าน object-oriented development ที่ผมได้สอนให้นักศึกษาฟังในวิชา  Component-Based Software Development มาหลายปีแล้ว ซึ่งเรื่องนี้เกี่ยวข้องกับคุณสมบัติที่ทำให้ object-oriented development เป็นที่นิยมก็คือคุณสมบัติ  inheritance และ polymorphism นั่นเอง 

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

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

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

คงเข้าใจปัญหานี้กันแล้วนะครับ โดยสรุปแล้วปัญหา FBC แบ่งเป็น 2 ประเภทคือ
  • Syntatic FBC หรือเรียกอีกอย่างว่า Fragile Binary Interface Problem
  • Sematic FBC  
โดย Syntactic ก็คือไวยากรณ์ ตัวอย่างของคลาส A B C เป็นตัวอย่างของกลุ่มนี้ ส่วน Semantic ก็คือความหมาย ปัญหาของการแก้ไขโค้ดในคลาสแม่แล้ว ทำให้พฤติกรรมของคลาสลูก เปลี่ยนไปเป็นปัญหาในกลุ่มนี้ 

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

สำหรับบล็อกนี้ขอจบแค่นี้นะครับ บล็อกต่อ ๆ ไปจะมาแสดงตัวอย่างของ Syntactic และ Semantic เพื่อให้เห็นภาพชัดเจนขึ้นครับ

วันศุกร์ที่ 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 ครับ