การใช้ Socket Class เพื่อส่งและรับข้อมูลกับ Server HTTP ใน .NET Core 2 และ C# 7

การใช้ Socket Class เพื่อส่งและรับข้อมูลกับ Server HTTP
สำหรับบทความนี้จะกล่าวถึงคุณสมบัติที่ถูกปรับปรุงใหม่ของภาษาซีชาร์ป 7.0 และ .NET Core 2.0 ในประเด็นที่เกี่ยวข้องกับการสื่อสารข้อมูลในเครือข่าย
Socket Class
วิธีพื้นฐานสุดของการเขียนโค้ดเพื่อสี่อสารในเครือข่ายทำได้โดยใช้ Socket Class ซึ่ง
- มี method และ property ที่เกี่ยวข้องกับการสื่อสารอยู่ภายในเป็นจำนวนมาก
- สนับสนุน protocol จำนวนมาก อาทิ IPv4, IPv6, TCP และ UDP
- สนับสนุนการทำงานทั้งแบบผสานจังหวะ (synchronous) และแบบไม่ผสานจังหวะ (asynchronous)
รูปที่ 1 แสดงตัวอย่างโค้ดที่ใช้ Class Socket เพื่อส่งข้อและรับมูลกับ server HTTP โดยรันทดสอบใน .NET Core 2.2 รูปนี้คือโค้ดส่วน method ConnectSocket ซึ่งทำหน้าที่เชื่อมต่อกับ server ด้วย protocol TCP method นี้รับพารามิเตอร์สองตัว ตัวแรกคือชื่อของ server ตัวที่สองคือหมายเลขของพอร์ท TCP ที่ต้องการเชื่อมต่อ
บรรทัด 12 ทำหน้าที่อ่านข้อมูลที่เกี่ยวข้องกับ host ซึ่งเป็น HTTP server
บรรทัด 14-34 วนการทำงานเพื่ออ่านรายการของ address ทั้งหมด เพื่อหารายการของกลุ่ม address ที่สนับสนุนการทำงาน
สาเหตุที่ต้องหารายการเพราะต้องการคัดกรองกลุ่ม address ที่ไม่สนับสนุนการทำงานออกไป
เช่นในกรณีที่เราต้องการทำงานกับ IPv4 เราจะต้องคัดกรองกลุ่ม address ที่เป็น IPv6 ออกไป หากไม่ทำเช่นนั้นจะเกิด errorตอนที่โค้ดทำงาน
สาเหตุที่ต้องหารายการเพราะต้องการคัดกรองกลุ่ม address ที่ไม่สนับสนุนการทำงานออกไป
เช่นในกรณีที่เราต้องการทำงานกับ IPv4 เราจะต้องคัดกรองกลุ่ม address ที่เป็น IPv6 ออกไป หากไม่ทำเช่นนั้นจะเกิด errorตอนที่โค้ดทำงาน

รูปที่ 2 method SocketSendReceive ทำหน้าที่แสดงวิธีร้องของหน้าเว็บไปยัง host ที่เป็น HTTP server จากนั้นก็วนรอรับการตอบสนองจาก host
บรรทัด 45 สร้าง Socket ด้วยการกำหนดชื่อ server แล้วเชื่อมต่อกับ host ด้วยการเรียกหา method ConnectSocket
บรรทัด 51 ส่งการร้องขอไปยัง server โดยใช้ method Send
บรรทัด 52 เริ่มรอรับการตอบสนองจาก server
บรรทัด 56-63 วนการทำงานเพื่ออ่านข้อมูล โปรแกรมจะค้างอยู่ในลูปจนกว่าจะอ่านข้อมูลได้ครบทั้งหน้า
การอ่านข้อมูลจาก server ทำได้โดยใช้ method Receive ซึ่งจะนำข้อมูลมาเก็บไว้ในตัวแปร bytesReceived
และจะได้ตัวเลขขนาดของข้อมูลที่ได้รับมาเก็บไว้ในตัวแปร bytes การทำงานจะซ้ำไม่หยุดตราบเท่านี้ค่าของตัวแปรนี้ยังมากกว่าศูนย์
การอ่านข้อมูลจาก server ทำได้โดยใช้ method Receive ซึ่งจะนำข้อมูลมาเก็บไว้ในตัวแปร bytesReceived
และจะได้ตัวเลขขนาดของข้อมูลที่ได้รับมาเก็บไว้ในตัวแปร bytes การทำงานจะซ้ำไม่หยุดตราบเท่านี้ค่าของตัวแปรนี้ยังมากกว่าศูนย์


รูปที่ 3 คือ method Main มีโค้ดซึ่งทำหน้าที่เรียกใช้ method SocketSendReceive
ซึ่งจะเชื่อมต่อกับ host เว็บไซต์ด้วย method ConnectSocket โดยทำหน้าที่เชื่อมต่อกับ server ด้วย protocol TCP อ่านหน้าโฮมเพจของเว็บไซต์ด้วยโค้ดภายใน method SocketSendReceive
บรรทัด 73 ตรวจสอบว่าผู้ใช้งานป้อนพิมพ์ชื่อ server (เช่น www.google.com) เข้ามาตอนเรียกให้โปรแกรมทำงานหรือไม่ ถ้าป้อนเข้ามาก็จะนำไปใช้ในบรรทัดที่ 78
บรรทัด 74 ถ้าผู้ใช้ไม่ได้ป้อนพิมพ์ชื่อ server มันจะถือว่าคอมพิวเตอร์เครื่องที่กำลังรันโปรแกรมนี้เป็น host เสียเอง
บรรทัด 78 เรียกใช้ method SocketSendReceive ผลลัพธ์ที่ได้คือหน้าเว็บหนึ่งหน้าเก็บอยู่ในตัวแปร result
บรรทัด 79 แสดงสิ่งที่อยู่ใน result ที่คอนโซล หากท่านป้อนพิมพ์ชื่อ host เป็น www.google.com ท่านจะเห็นผลลัพธ์คล้ายในรูปที่ 4
ซึ่งจะเชื่อมต่อกับ host เว็บไซต์ด้วย method ConnectSocket โดยทำหน้าที่เชื่อมต่อกับ server ด้วย protocol TCP อ่านหน้าโฮมเพจของเว็บไซต์ด้วยโค้ดภายใน method SocketSendReceive
บรรทัด 73 ตรวจสอบว่าผู้ใช้งานป้อนพิมพ์ชื่อ server (เช่น www.google.com) เข้ามาตอนเรียกให้โปรแกรมทำงานหรือไม่ ถ้าป้อนเข้ามาก็จะนำไปใช้ในบรรทัดที่ 78
บรรทัด 74 ถ้าผู้ใช้ไม่ได้ป้อนพิมพ์ชื่อ server มันจะถือว่าคอมพิวเตอร์เครื่องที่กำลังรันโปรแกรมนี้เป็น host เสียเอง
บรรทัด 78 เรียกใช้ method SocketSendReceive ผลลัพธ์ที่ได้คือหน้าเว็บหนึ่งหน้าเก็บอยู่ในตัวแปร result
บรรทัด 79 แสดงสิ่งที่อยู่ใน result ที่คอนโซล หากท่านป้อนพิมพ์ชื่อ host เป็น www.google.com ท่านจะเห็นผลลัพธ์คล้ายในรูปที่ 4

การสร้าง object แบบ Socket
การเขียนโค้ดเพื่อสร้าง object แบบ Socket ทำได้ด้วยวิธีปรกติเหมือน object ชนิดอื่น ๆ
นั่นคือใช้ตัวกระทำ new แล้วเรียกหา method พิเศษสำหรับการสร้าง object
ซึ่งก็คือ constructor method ของ Class Socket ที่ overไว้ให้เลือกใช้ 3 แบบได้แก่
บรรทัดที่ 10-15 ประกาศตัวแปรให้เก็บค่าที่จะเขียนไปยัง server
บรรทัดที่ 19 ประกาศตัวแปร s ที่มีไทป์เป็น Socket โดยให้เป็นค่านัลไว้
บรรทัดที่ 20-21 IPAddress และ IPEndPoint คือจุดหมายปลายทางของการเรียกและรับข้อมูล โดยจะใช้ไอพีแรกที่ DNS ส่งมาให้
บรรทัดที่ 19-24 ประกาศและกำหนดค่าให้แก่ตัวแปรทั้งหลายที่จำเป็นต้องใช้ภายในลูปไว้นอกลูป เพื่อไม่ให้เกิดการประกาศซ้ำ ๆ ทุกครั้งของการวน
บรรทัดที่ 23 อ่านข้อมูลจาก DNS ของ host
บรรทัดที่ 24 อ่านไอพี address ทั้งหมดจาก DNS ของ host
บรรทัดที่ 25-41 วนการทำงานเพื่อหาค่า IPAddress และค่า IPEndPoint
บรรทัดที่ 29 สร้าง object ของSocketโดยการเรียกหา method คอนสทรักเตอร์แบบที่ 3 ซึ่งเป็นแบบที่มีพารามิเตอร์ 3 ตัว
บรรทัดที่ 33 เชื่อมต่อกับ host โดยใช้ IPEndPoint ของ host นั้น
บรรทัดที่ 36 การเชื่อมต่อล้มเหลว เลื่อนไปลอง IP address ถัดไป
บรรทัดที่ 40 ส่ง request แบบ GET ไปยัง host
บรรทัดที่ 42-49 การเชื่อมต่อทำได้สำเร็จ วนการทำงานเพื่ออ่านข้อมูลจากหน้าโฮมจนกว่าจะได้รับข้อมูลครบทั้งหมด
นั่นคือใช้ตัวกระทำ new แล้วเรียกหา method พิเศษสำหรับการสร้าง object
ซึ่งก็คือ constructor method ของ Class Socket ที่ overไว้ให้เลือกใช้ 3 แบบได้แก่
- Socket(SocketInformation)
- Socket(SocketType, ProtocolType)
- Socket(AddressFamily, SocketType, ProtocolType)
- AddressFamily: ทำหน้าที่กำหนดประเภทรายการ address ที่ต้องการ เช่น AppleTalk, Ecma, Unix ฯลฯ
- SocketType : ทำหน้าที่กำหนดประเภทของSocketที่ต้องการ เช่น Raw, Stream, Dgram ฯลฯ
- ProtocolType : ทำหน้าที่กำหนดชนิดของ protocol เช่น IP, IPv4, IPv6 ฯลฯ
บรรทัดที่ 10-15 ประกาศตัวแปรให้เก็บค่าที่จะเขียนไปยัง server
บรรทัดที่ 19 ประกาศตัวแปร s ที่มีไทป์เป็น Socket โดยให้เป็นค่านัลไว้
บรรทัดที่ 20-21 IPAddress และ IPEndPoint คือจุดหมายปลายทางของการเรียกและรับข้อมูล โดยจะใช้ไอพีแรกที่ DNS ส่งมาให้
บรรทัดที่ 19-24 ประกาศและกำหนดค่าให้แก่ตัวแปรทั้งหลายที่จำเป็นต้องใช้ภายในลูปไว้นอกลูป เพื่อไม่ให้เกิดการประกาศซ้ำ ๆ ทุกครั้งของการวน
บรรทัดที่ 23 อ่านข้อมูลจาก DNS ของ host
บรรทัดที่ 24 อ่านไอพี address ทั้งหมดจาก DNS ของ host
บรรทัดที่ 25-41 วนการทำงานเพื่อหาค่า IPAddress และค่า IPEndPoint
บรรทัดที่ 29 สร้าง object ของSocketโดยการเรียกหา method คอนสทรักเตอร์แบบที่ 3 ซึ่งเป็นแบบที่มีพารามิเตอร์ 3 ตัว
บรรทัดที่ 33 เชื่อมต่อกับ host โดยใช้ IPEndPoint ของ host นั้น
บรรทัดที่ 36 การเชื่อมต่อล้มเหลว เลื่อนไปลอง IP address ถัดไป
บรรทัดที่ 40 ส่ง request แบบ GET ไปยัง host
บรรทัดที่ 42-49 การเชื่อมต่อทำได้สำเร็จ วนการทำงานเพื่ออ่านข้อมูลจากหน้าโฮมจนกว่าจะได้รับข้อมูลครบทั้งหมด

บรรทัดที่ 51-56 ดัก errorในกรณีที่เป็น error ของตัว Socket
บรรทัดที่ 51-56 ดัก errorในกรณีที่เป็น ArgumentNull
บรรทัดที่ 51-56 ดัก errorในกรณีที่เป็น NullReference
บรรทัดที่ 51-56 ดัก errorในกรณีที่เป็น error อื่น ๆ
บรรทัดที่ 77-80 method Main ทำหน้าที่เรียก method DoSocketGet เพื่อทดสอบการทำงาน โดยส่งชื่อ host Google ไปใช้ในการทดสอบ
บรรทัดที่ 51-56 ดัก errorในกรณีที่เป็น ArgumentNull
บรรทัดที่ 51-56 ดัก errorในกรณีที่เป็น NullReference
บรรทัดที่ 51-56 ดัก errorในกรณีที่เป็น error อื่น ๆ
บรรทัดที่ 77-80 method Main ทำหน้าที่เรียก method DoSocketGet เพื่อทดสอบการทำงาน โดยส่งชื่อ host Google ไปใช้ในการทดสอบ

พารามิเตอร์ addressFamily ทำหน้าที่ระบุลักษณะของ address ที่ object จะนำไปใช้
พารามิเตอร์ socketType เป็นตัวกำหนดชนิดของ object Socket และ
พารามิเตอร์ protocolType ทำหน้าที่กำหนดชนิดของ protocol
พารามิเตอร์ทั้ง 3 ตัวนี้มีความสัมพันธ์กัน ยกตัวอย่างเช่น
หากเรากำหนด addressFamily ให้เป็นแบบหนึ่ง protocol ที่ใช้ก็จะต้องเป็นแบบที่สนับสนุน addressFamily ชนิดนั้นด้วย ถ้ากำหนดไว้ไม่สัมพันธ์กันจะเกิด error
พารามิเตอร์ socketType เป็นตัวกำหนดชนิดของ object Socket และ
พารามิเตอร์ protocolType ทำหน้าที่กำหนดชนิดของ protocol
พารามิเตอร์ทั้ง 3 ตัวนี้มีความสัมพันธ์กัน ยกตัวอย่างเช่น
หากเรากำหนด addressFamily ให้เป็นแบบหนึ่ง protocol ที่ใช้ก็จะต้องเป็นแบบที่สนับสนุน addressFamily ชนิดนั้นด้วย ถ้ากำหนดไว้ไม่สัมพันธ์กันจะเกิด error

การสื่อสารแบบไร้การเชื่อมต่อ
การสื่อสารในเครือข่ายด้วย Socket แบ่งออกเป็น 2 แบบ
แบบแรก คือ การสื่อสารที่ใช้ protocol แบบมีการเชื่อมต่อ (connection-oriented protocol เช่นการสื่อสารที่ใช้ protocol TCP) และ
แบบที่สองคือ การสื่อสารที่ใช้ protocol แบบไร้การเชื่อมต่อ (connectionless protocol เช่นการสื่อสารที่ใช้ protocol UDP)
ในกรณีที่เป็นการสื่อสารแบบไร้การเชื่อมต่อ เราไม่จำเป็นต้องเขียนโค้ดซึ่งทำหน้าที่คอยรอรับฟังการเชื่อมต่อ
การรับข้อมูลสามารถทำได้โดยใช้ method ReceiveFrom และการส่งข้อมูลไปยัง host สามารถทำได้โดยใช้ method SendTo
รูปที่ 8 แสดงโค้ดตัวอย่างการใช้ method ReceiveFrom และ SendTo เพื่อรับส่งข้อมูลกับ host
บรรทัดที่ 7-21 คือ method ReceiveForm2 ทำหน้าที่เรียกใช้งาน method ReceiveFrom เพื่อการรับข้อมูล
บรรทัดที่ 14 สร้าง IpEndPoint เพื่อทำหน้าที่ใช้สอบยันตัวตนของ host
บรรทัดที่ 16 คือโค้ดทำหน้าที่ผูก object Socketเข้ากับเอ็นด์พอยท์ด้วย method Bind
บรรทัดที่ 19 คำสั่งบรรทัดนี้จะทำให้โปรแกรมค้างจนกว่าจะได้รับข้อมูลครบ
method ReceiveFrom จะอ่านข้อมูลไปเก็บไว้ในพารามิเตอร์ buffer ส่งค่ากลับเป็นจำนวนไบต์ที่อ่านได้สำเร็จ
method นี้เหมาะนำมาใช้เมื่อต้องการรับข้อมูลจาก host เดียวหรือเหลาย ๆ host โดยไม่ต้องเชื่อมต่อไว้ก่อนล่วงหน้า
method นี้มีพารามิเตอร์สามตัวได้แก่
เมื่อใช้ method ReceiveFrom กับการสื่อสารแบบไร้การเชื่อมต่อ มันจะอ่านข้อมูลจากคิวเข้ามาใส่บัฟเฟอร์ของเครือข่าย
ถ้าจำนวนข้อมูลมีปริมาณมากกว่าขนาดของบัฟเฟอร์ มันจะอ่านมาจนเต็มบัฟเฟอร์แล้วแจ้ง error
ถ้า protocol ที่ใช้เป็นแบบเชื่อถือไม่ได้ (unreliable protocol) ข้อมูลส่วนที่ล้นบัฟเฟอร์จะหายไป
แต่ถ้า protocol ที่ใช้เป็นแบบเชื่อถือได้ (reliable protocol) host จะกักข้อมูลส่วนที่ล้นบัฟเฟอร์ไว้
ทำให้เราสามารถเรียก method ReceiveFrom อ่านข้อมูลเข้ามาใหม่ได้ โดยกำหนดขนาดขอวงบัฟเฟอร์ให้ใหญ่กว่าเดิม
ในกรณีที่ host ไม่มีข้อมูลจะส่ง method ReceiveFrom จะทำให้โปรแกรมค้างจนกว่าจะได้รับข้อมูล
แต่ถ้าโปรแกรมทำงานในโหมดป้องกันการค้าง method ReceiveFrom จะจบการทำงานทันทีพร้อมแจ้ง error
เพื่อป้องกันปัญหานี้เราสามารถตรวจสอบก่อนได้ว่ามีข้อมูลอยู่ภายในสแตกเฟรมหรือไม่ ด้วยการดูที่ค่าของ property Available ที่จะมีค่าเป็นศูนย์หากไม่มีข้อมูล
บทความตอนนี้พูดถึงคุณสมบัติที่ถูกปรับปรุงใหม่ของภาษาซีชาร์ป 7.0 และ .NET Core 2.0 ที่เกี่ยวข้องกับการสื่อสารข้อมูลในเครือข่าย เช่นClass Socket การสร้าง object แบบSocket และการสื่อสารแบบไร้การเชื่อมต่อ ทั้งนี้ท่านผู้อ่านน่าจะได้ทดลองใช้ประโยชน์จาก Class Socket ดูนะครับ
แบบแรก คือ การสื่อสารที่ใช้ protocol แบบมีการเชื่อมต่อ (connection-oriented protocol เช่นการสื่อสารที่ใช้ protocol TCP) และ
แบบที่สองคือ การสื่อสารที่ใช้ protocol แบบไร้การเชื่อมต่อ (connectionless protocol เช่นการสื่อสารที่ใช้ protocol UDP)
ในกรณีที่เป็นการสื่อสารแบบไร้การเชื่อมต่อ เราไม่จำเป็นต้องเขียนโค้ดซึ่งทำหน้าที่คอยรอรับฟังการเชื่อมต่อ
การรับข้อมูลสามารถทำได้โดยใช้ method ReceiveFrom และการส่งข้อมูลไปยัง host สามารถทำได้โดยใช้ method SendTo
รูปที่ 8 แสดงโค้ดตัวอย่างการใช้ method ReceiveFrom และ SendTo เพื่อรับส่งข้อมูลกับ host
บรรทัดที่ 7-21 คือ method ReceiveForm2 ทำหน้าที่เรียกใช้งาน method ReceiveFrom เพื่อการรับข้อมูล
บรรทัดที่ 14 สร้าง IpEndPoint เพื่อทำหน้าที่ใช้สอบยันตัวตนของ host
บรรทัดที่ 16 คือโค้ดทำหน้าที่ผูก object Socketเข้ากับเอ็นด์พอยท์ด้วย method Bind
บรรทัดที่ 19 คำสั่งบรรทัดนี้จะทำให้โปรแกรมค้างจนกว่าจะได้รับข้อมูลครบ
method ReceiveFrom จะอ่านข้อมูลไปเก็บไว้ในพารามิเตอร์ buffer ส่งค่ากลับเป็นจำนวนไบต์ที่อ่านได้สำเร็จ
method นี้เหมาะนำมาใช้เมื่อต้องการรับข้อมูลจาก host เดียวหรือเหลาย ๆ host โดยไม่ต้องเชื่อมต่อไว้ก่อนล่วงหน้า
method นี้มีพารามิเตอร์สามตัวได้แก่
- buffer: เป็นหน่วยเก็บข้อมูลที่จะอ่านมา มีชนิดเป็นไบต์อาร์เรย์
- socketFlags: เป็นอีนัมทำหน้าที่กำหนดวิธีทำงานของการรับส่งข้อมูล
- remoteEP: เป็นตัวแปรเก็บค่าค่าอ้างอิงไปยัง host
เมื่อใช้ method ReceiveFrom กับการสื่อสารแบบไร้การเชื่อมต่อ มันจะอ่านข้อมูลจากคิวเข้ามาใส่บัฟเฟอร์ของเครือข่าย
ถ้าจำนวนข้อมูลมีปริมาณมากกว่าขนาดของบัฟเฟอร์ มันจะอ่านมาจนเต็มบัฟเฟอร์แล้วแจ้ง error
ถ้า protocol ที่ใช้เป็นแบบเชื่อถือไม่ได้ (unreliable protocol) ข้อมูลส่วนที่ล้นบัฟเฟอร์จะหายไป
แต่ถ้า protocol ที่ใช้เป็นแบบเชื่อถือได้ (reliable protocol) host จะกักข้อมูลส่วนที่ล้นบัฟเฟอร์ไว้
ทำให้เราสามารถเรียก method ReceiveFrom อ่านข้อมูลเข้ามาใหม่ได้ โดยกำหนดขนาดขอวงบัฟเฟอร์ให้ใหญ่กว่าเดิม
ในกรณีที่ host ไม่มีข้อมูลจะส่ง method ReceiveFrom จะทำให้โปรแกรมค้างจนกว่าจะได้รับข้อมูล
แต่ถ้าโปรแกรมทำงานในโหมดป้องกันการค้าง method ReceiveFrom จะจบการทำงานทันทีพร้อมแจ้ง error
เพื่อป้องกันปัญหานี้เราสามารถตรวจสอบก่อนได้ว่ามีข้อมูลอยู่ภายในสแตกเฟรมหรือไม่ ด้วยการดูที่ค่าของ property Available ที่จะมีค่าเป็นศูนย์หากไม่มีข้อมูล
บทความตอนนี้พูดถึงคุณสมบัติที่ถูกปรับปรุงใหม่ของภาษาซีชาร์ป 7.0 และ .NET Core 2.0 ที่เกี่ยวข้องกับการสื่อสารข้อมูลในเครือข่าย เช่นClass Socket การสร้าง object แบบSocket และการสื่อสารแบบไร้การเชื่อมต่อ ทั้งนี้ท่านผู้อ่านน่าจะได้ทดลองใช้ประโยชน์จาก Class Socket ดูนะครับ