You are on page 1of 18

เอกสารประกอบการสอนวิชา 01074201 Network Programming

อ. วัจนพงศ เกษมศิริ
ภาควิชาวิศวกรรมคอมพิวเตอร
สถาบันเทคโนโลยีพระจอมเกลาเจาคุณทหารลาดกระบัง
4. Connectionless Socket
Connectionless Sockets เปน Socket ซึ่งใช UDP Protocol เปน Protocol ในการสื่อสาร ดังนั้นเนื้อหาใน
บทนี้จะแบงเปน 2 สวนดวยกันคือ UDP Protocol โดยสรุป และการสรางและทํางานกับ Connectionless Socket

4.1 UDP Protocol


ในปจจุบันระบบปฏิบัติการของเครื่องคอมพิวเตอร ยอมใหโปรแกรมประยุกตหลายโปรแกรมสามารถ
ทํางานพรอม ๆ กันได (Multitasking) ดังนั้นในการสื่อสารขอมูลผานโครงขายอาจกลาวไดวาจุดหมายปลายทาง
ของขอมูล (Massage) คือ task หรือ process ซึ่งกําลังทํางานอยูนั่นเอง แตทั้งนี้ในการสงขอมูลหากจะระบุ
ปลายทางของ Massage ที่จะสงใหเปนชื่อ task หรือ ชื่อ process และชื่อคอมพิวเตอรปลายทาง (destination
machine) ก็จะไมเหมาะสม ทั้งนี้เพราะ
i. task หรือ process นั้นถูกสรางและทําลายอยาง dynamic
ii. การอางอิงถึง task หรือ process เปนไปอยางลําบาก และ task หรือ process number อาจเปลี่ยนแปลงเมื่อมี
การ reboot เครื่อง
นอกจากนั้นจะเปนการเหมาะสมมากกวาหากวาจะสามารถระบุปลายทางไดโดยไมตองทราบวา process
ใดในเครื่องปลายทางนั้นเปนผูใหบริการหรือผูรับขอมูล ดังนั้นจึงเกิดแนวคิดของการสรางจุดหมายปลายทางที่มี
ลักษณะเปน abstract ขึ้น ซึ่งจุดหมายปลายทางที่เปน abstract นี้ถูกเรียกวา "protocol port" โดยแตละ protocol
port นั้นจะมีหมายเลขกํากับและระบบปฏิบัติการจะเปนผูควบคุมจัดการรวมทั้งจัดสรางการเชื่อมตอระหวาง
process ซึ่งเปนปลายทางที่แทจริงกับ protocol port รวมถึงการวิธีการนําขอมูลเขาและออกจาก protocol port
โดยปกติการรับสงขอมูลกับ protocol port ซึ่งจัดการโดยระบบปฏิบัติการนั้นจะเปนไปในลักษณะ
Synchronous กลาวคือการคํานวณตาง ๆ จะหยุดรอในระหวางการทํางานกับ port (Port access) ยกตัวอยางเชน
เมื่อ process มีการอานขอมูลจาก port ในขณะที่ขอมูลยังมาไมถึง ระบบปฏิบัติการจะหยุดการทํางานของ process
นั้นไวชั่วคราวจนกวาขอมูลจะถูกสงมาถึง port เมื่อขอมูลมาถึง ระบบปฏิบัติการจะนําขอมูลออกจาก port แลวสง
ในกับ process จากนั้นระบบปฏิบัติการจะสั่งให process นั้นเริ่มทํางานตอไปได
โดยทั่วไประบบปฏิบัติการจะมีการสราง buffer ไวสําหรับรองรับขอมูลซึ่งสงมายัง port ดังนั้นหาก
ขอมูลสงมาถึง port ในขณะที่ process ยังไมพรอมที่จะรับขอมูลจาก port ขอมูลนั้นก็จะถูกเก็บลงใน buffer เพื่อรอ
การอานตอไป โดยที่ Buffer จะถูกจัดการโดย protocol software suit ซึ่งอยูในระบบปฏิบัติการ และ buffer นั้นจะ
มีลักษณะเปน queue ที่ตามทฤษฎีแลวมีขนาดไมจํากัด (infinite queue) โดยแตละ port จะมี buffer เปนเอกเทศ
ดังนั้นจึงสรุปไดวาในการสื่อสารขอมูลผูสงจะตองทราบหมายเลข IP (IP Address) และหมายเลข port
(Port Number) ของเครื่องปลายทาง และในแตละชุดขอมูล (Message) ที่สงออกไปจะมีหมายเลขทั้งสองที่กลาวมา
รวมถึง หมายเลข port ของเครื่องตัวเอง (Source port) ไปดวยทุกครั้ง โดย source port นั้นจะสงไปเพื่อใหฝง
รับทราบหมายเลข port ที่จะใชในการสงขอมูลกลับ
Connectionless Socket (Datagram Socket) 2

4.1.1 The User Datagram Protocol


The User Datagram Protocol หรือ UDP เปน Protocol ที่อาศัยกลไกการทํางานของ IP ลวน ๆ ดังนั้น
คุณลักษณะของการสงขอมูลผาน UDP จึงเปนแบบ unreliable connectionless แบบเดียวกันกับ IP คือไมมีการสง
acknowledge เมื่อไดรับขอมูล, ไมมีการเรียงลําดับขอมูล, อาจเกิดการซ้ํากันของขอมูล, ไมมีการสรางเสนทาง
เสมือนในการสื่อสาร (virtual path), ขอมูลอาจสูญหายในระหวางการสง รวมทั้งไมมีการสงขอมูลปอนกลับ
(feedback) เพื่อควบคุมความเร็วในการสื่อสาร UDP จะมีการเพิ่มเติมในสวนของ protocol port เพื่อใหสามารถ
แบงแยกขอมูลสําหรับแตละคูของการสื่อสารได Protocol port ที่เพิ่มเขาไปในแตละขอมูลที่สงออกไปนั้นไดแก
Source Port และ Destination Port ทั้งนี้เพื่อให process ในเครื่องคอมพิวเตอรที่สื่อสารขอมูลกันอยูนั้นสามารถรับ
และสงกลับขอมูลระหวางกันไดถูกตอง
โปรแกรมประยุกตที่ใชงาน UDP นั้นจะตองรับผิดชอบปญหาเรื่องความเชื่อถือได (reliability) ในการ
สื่อสารขอมูล เชน ความซ้ําซอนของขอมูล (Data Duplication), ขอมูลสูญหาย (Data Lost), ขอมูลไมเรียงลําดับกัน
(Out-of-order) เปนตน
4.1.2 Format of UDP Message
แตละ UDP Message จะถูกเรียกวา User Datagram แตละ User Datagram จะมีสวนประกอบ 2 สวน
ไดแก สวนหัว (Header) และสวนขอมูล (Data) รูป 4.1 จะแสดงสวนประกอบตาง ๆ ของ header

UDP Source PORT (16 bits) UDP Destination PORT (16 bits)
UDP Message Length (16 bits) UDP Checksum (16 bits)
Data
...

รูป 4.1 Header ของ UDP

i. UDP Source Port: field นี้มีขนาด 16 bits และมีลักษณะเปน Optional คือจะใสขอมูล port หรือไมใสก็ได
หากใสขอมูล port ก็จะเปนการระบุวาการตอบกลับจากเครื่องปลายทางจะใหสงมาที่หมายเลข port นี้
ii. UDP Destination Port: เปน field ที่มีขนาด 16 bits ใชระบุหมายเลข port ปลายทาง
iii. UDP Message Length: เปน field ที่มีขนาด 16 bits ใชระบุความยาวของขอมูลที่จะสง
iv. UDP Checksum: มีขนาด 16 bits ใชสําหรับใสคา checksum โดยการคํานวณ checksum นั้นจะคํานวณทั้ง
Message กลาวคือนําทั้งสวน header และ สวน data มาทําการคํานวณ สําหรับสวน UDP Checksum นี้เปน
Optional หากไมใชจะใสคาใน field นี้เปน 0 การทําใหสามารถเลือกที่จะไมใช checksum ก็เพื่อที่จะลด
การคํานวณลงในกรณีที่การสื่อสารนั้นกระทําบนเครือขายที่มีความนาเชื่อถือสูง
4.1.3 UDP Encapsulation and Protocol Layering
ในการทํางาน UDP จะอยูในชั้น (layer) เหนือชั้น IP ตามรูป 4.2 ซึ่งนั่นจะไดวา โปรแกรมประยุกตจะ
ปอนขอมูลที่ตองการสงใหกับชั้นของ UDP และ UDP จะอาศัยกลไกของ IP ในการนําขอมูลไปยังปลายทาง การ

เอกสารประกอบการสอนวิชา 01074201 Network Programming อ. วัจนพงศ เกษมศิริ ภาควิชาวิศวกรรมคอมพิวเตอร สจล.


Connectionless Socket (Datagram Socket) 3

ที่ชั้นของ UDP อยูบนชั้นของ IP นั้นจะไดวา Package ของ UDP (ทั้ง header และ data) จะบรรจุไวในสวน Data
ของ IP ซึ่งจะเปน package ที่จะสงออกไปยังเครือขายภายนอกตอไป ตามรูป 4.3

Application
UDP
IP
Network Interface

รูป 4.2 The conceptual layering of UDP

UDP header UDP Data Area

IP Header IP Data Area

Frame Header Frame Data Area

รูป 4.3 UDP datagram encapsulated in an IP datagram for transmission

การสงขอมูลผาน UDP อาจกลาวไดวา ในการสงขอมูลนั้น IP protocol จะรับผิดชอบเพียงการสงขอมูล


ระหวางเครื่องคอมพิวเตอรบนเครือขาย ในขณะที่ UDP protocol นั้นจะมีหนาที่ในการแบงแยกขอมูลวาขอมูลใด
จะตองสงใหกับโปรแกรมประยุกตโปรแกรมใด (IP header จะเปนตัวระบุเครื่องคอมพิวเตอรตนทาง/ปลายทาง
โดยอาศัย IP Address และ UDP header จะเปนตัวระบุหมายเลข port ของตนทางและปลายทาง)
4.1.4 UDP Multiplexing, De-multiplexing, and Ports
ในการทํางานโดยทั่วไประหวาง protocol แตละชั้นที่อยูติดกันจะมีการ multiplex และ de-multiplex
ขอมูลที่สงผานระหวางกัน ในกรณีของ UDP นั้น UDP จะรับขอมูลจากแตละโปรแกรมประยุกตที่ตองการสง
ขอมูลจากนั้นทําการสราง UDP datagram โดยใส UDP header ใหกับขอมูลนั้น แลวสง UDP datagram นั้นใหกับ
ชั้นของ IP ซึ่งอยูถัดไป และเมื่อเครื่องคอมพิวเตอรไดรับขอมูลจากเครือขายชั้นของ IP ก็จะทําการสงขอมูลที่
ไดรับซึ่งตัดสวนของ IP header ออกแลวใหกับชั้นของ UDP และในชั้นของ UDP ก็จะทําการแยกขอมูลที่ไดมา
แลวทําการสงขอมูลนั้นใหกับโปรแกรมประยุกตที่เหมาะสมตอไป
การ multiplex และ de-multiplex ที่เกิดขึ้นสําหรับ UDP นั้นจะกระทําผานกลไกของ Port โดยเมื่อ
โปรแกรมประยุกตใดตองการที่จะสงขอมูลดวย UDP จะตองทําการรองขอไปยังระบบปฏิบัติการเพื่อทําการเปด
port เมื่อการเปด port สําเร็จการสงขอมูลใดผาน port จะมีคาหมายเลขใน UDP source port field เปนหมายเลข
ของ port นั้น ดังในรูป 4.4 และเมื่อรับขอมูลจากเครือขาย UDP software suit ในระบบปฏิบัติการจะทําการ de-
multiplex ตามหมายเลข port ดังในรูป 4.5

เอกสารประกอบการสอนวิชา 01074201 Network Programming อ. วัจนพงศ เกษมศิริ ภาควิชาวิศวกรรมคอมพิวเตอร สจล.


Connectionless Socket (Datagram Socket) 4

Port 1 Port 2 Port 3

D2
D1 D3
UDP Multiplexing

H3 D3
H2 D2
H1 D1

IP Layer

รูป 4.4 UDP Multiplexing

Port 1 Port 2 Port 3

UDP De-multiplexing

UDP Datagram

IP Layer

รูปที่ 5 UDP De-multiplexing

สําหรับ port ของ UDP นั้นจะมีลักษณะเปน queue ตามที่ไดกลาวมาแลวขางตน โดยเมื่อโปรแกรมประยุกตรอง


ขอการใชงาน port ไปยังระบบปฏิบัติการ ระบบปฏิบัติการจะทําการสราง queue เพื่อใชสําหรับเก็บขอมูลขึ้นมา
พรอม ๆ กับการสราง port ดังนั้นเมื่อ UDP ไดรับ datagram จากชั้นของ IP ก็จะทําการ de-multiplex โดยการ
นําเอาขอมูลไปใสใน queue ของ port ที่ระบุไวเปน destination port ของขอมูลนั้น หาก port ที่ระบุไวใน
datagram ไมไดถูกเปดใชงานในคอมพิวเตอรปลายทางเมื่อ UDP ไดรับขอมูลมาแลวไมพบ port ที่ตองการก็จะสง
ICMP package ซึ่งระบุขอผิดพลาด "port unreachable" พรอมทั้งนําขอมูลนั้นทิ้งไป ในกรณีที่ queue ของ port
เต็ม ขอมูลที่ไดรับมาหลังจากที่ queue เต็มจะถูกทิ้งไป (discard)

4.2 Datagram Socket


Connectionless Socket หรือ Datagram Socket เปน socket ที่ใช Protocol UDP เปนหลักในการสื่อสาร
ขอมูลโดยกลไกการทํางานของ Datagram Socket จะไมมีการสรางเสนทางการเชื่อมตอเสมือน เมื่อตองการจะสง

เอกสารประกอบการสอนวิชา 01074201 Network Programming อ. วัจนพงศ เกษมศิริ ภาควิชาวิศวกรรมคอมพิวเตอร สจล.


Connectionless Socket (Datagram Socket) 5

ขอมูล socket ก็จะทําการสงขอมูลนั้นออกไปทันที โดยไมมีการตรวจสอบสถานะ/เสนทางไปยังฝงที่จะรับ ดังนั้น


การสูญหายของขอมูลจึงอาจเกิดขึ้นได
จากคุณลักษณะของ UDP ที่ไมรับประกันในเรื่องของขอมูลสูญหายระหวางทาง ไมรับประกันการ
เรียงลําดับของขอมูล ดังนั้นในโปรแกรมที่ใชงานกับ Connectionless Socket/UDP จะตองมีสวนของโปรแกรมที่
ใชจัดการเกี่ยวกับปญหาดังกลาว จึงอาจกลาวไดวา Connectionless Socket นั้นหากมองในดานการรับและสง
ขอมูลสามารถทําไดงายและมีกระบวนการที่ซับซอนนอยกวา Connection Oriented Socket เปนอยางมาก หากถา
มองดานการจัดการขอมูลซึ่งไดรับเขามาแลวจะพบวาตองมีกระบวนการรองรับที่ซับซอนมากกวาการใชงาน
Connection Oriented Socket เนื่องจากการจัดการเกี่ยวกับขอมูลที่รับเขามาหลาย ๆ อยางไมมีการรองรับโดยตัว
Protocol ดังที่ไดกลาวมาขางตน
4.2.1 การสราง Connectionless Socket
ในการสราง Connectionless Socket นั้นมีกระบวนการอยู 2 ขั้นตอนคือ
i. สราง Socket Object
ii. Bind the socket to a local IPEndPoint
หลังจากเสร็จสิน้ 2 ขั้นตอนนี้แลว Socket ที่สรางขึ้นนั้นสามารถใชรับ-สงขอมูลไดทันที ซึ่งจะพบวามีขั้นตอน
นอยกวาการสราง Connection Oriented Socket และกระบวนการตาง ๆ ที่ใชเพื่อการเชื่อมตอโดย TCP นั้นไมมี
ความจําเปนเลยสําหรับการใชงาน UDP นอกจากนั้นสําหรับโปรแกรมทําการสื่อสารโดยใช Connectionless
Socket และทําหนาที่เปนผูสงเพียงอยางเดียวนั้น สามารถตัดขั้นตอนการ bind socket ออกไปได เพียงสราง
Socket แลวสงขอมูลทาง Socket ที่สรางขึ้นไดเลย
จากที่กลาวมาเนื่องจากลักษณะการจัดการขอมูล/การเชื่อมตอที่แตกตางออกไปของ UDP คําสั่ง Send()
และ Receive() จึงไมสามารถนํามาใชงานกับ Connectionless Socket ได หากแตตองใชคําสั่งที่ถูกสรางขึ้นเพื่อ
รองรับการเชื่อมตอชนิดนี้คือ SendTo() และ ReceiveFrom()

4.2.1.1 SendTo()
เปน method ซึ่งใชในการสงขอมูลไปยังเครื่องปลายทาง โดยการใช SendTo() นั้นสามารถใส parameter
ไดในหลายลักษณะ ดังนี้

SendTo(byte[] data, EndPoint Remote)

รูปแบบขางตนเปนรูปแบบอยางงายของ SendTo() คือสงขอมูลในรูปแบบของ byte array ไปยังปลายทางซึ่งถูก


ระบุไวในตัวแปร Remote นั่นเอง

SendTo(byte[] data, SocketFlags Flags, EndPoint Remote)

สําหรับรูปแบบนี้จะรับ parameter SocketFlags เพิ่มเขามา ซึ่ง SocketFlags นี้จะใชเพื่อระบุ UDP Socket Option ที่
จะเลือกใชงาน

SendTo(byte[] data, int Size, SocketFlags Flags, EndPoint Remote)


เอกสารประกอบการสอนวิชา 01074201 Network Programming อ. วัจนพงศ เกษมศิริ ภาควิชาวิศวกรรมคอมพิวเตอร สจล.
Connectionless Socket (Datagram Socket) 6

ในรูปแบบนี้จะมีการระบุจํานวนไบตที่ตองการจะสงเพิ่มเขามา โดยจํานวนไบตที่ตองการจะสงนั้นจะระบุลงใน
ตัวแปรชนิด integer ชื่อ Size

SendTo(byte[] data, int Offset, int Size, SocketFlags Flags, EndPoint Remote)

ในรูปแบบสุดทายของ SendTo() นี้จะสามารถระบุตําแหนง offset ใน byte array data เพื่อกําหนดตําแหนงที่จะ


เริ่มทําการสงผานขอมูลผานตัวแปรชนิด integer ชื่อ Offset นั่นเอง

4.2.1.2 ReceiveFrom()
เปน Method ที่ใชสําหรับการรับขอมูล โดยจะรับ parameter 2 ตัวไดแก byte array ที่ทําหนาที่เปน data
buffer และ EndPoint ของเครื่องปลายทาง โดยรูปแบบการใชงาน ReceiveFrom() เปนดังนี้

ReceiveFrom(byte[] data, ref EndPoint Remote)

สําหรับการใชงาน ReceiveFrom() นั้นมีขอสังเกตุที่ parameter ตัวที่ 2 จะพบวาคาที่จะสงใหนั้นเปนแบบ


reference กลาวคือสิ่งที่สงไปนั้นจะเปนเพียง address ตําแหนงที่เก็บ EndPoint Object โดยที่ ReceiveFrom() นั้น
จะทําการใสคา EndPoint ของฝายสงที่ตําแหนง address ที่สงมาเปนคา parameter ของ ReceiveFrom()

โปรแกรมตัวอยาง 4.1 UDP Server


using System; //1
using System.Net; //2
using System.Net.Sockets; //3
using System.Text; //4

class SimpleUdpSrvr //5


{
public static void Main() //6
{
int recv; //7
byte[] data = new byte[1024]; //8
IPEndPoint ipep = new IPEndPoint(IPAddress.Any, 9050); //9

Socket newsock = new Socket(AddressFamily.InterNetwork, //10


SocketType.Dgram, ProtocolType.Udp);

newsock.Bind(ipep); //11

เอกสารประกอบการสอนวิชา 01074201 Network Programming อ. วัจนพงศ เกษมศิริ ภาควิชาวิศวกรรมคอมพิวเตอร สจล.


Connectionless Socket (Datagram Socket) 7

โปรแกรมตัวอยาง 4.1 UDP Server (ตอ)


Console.WriteLine("Waiting for a client..."); //12

IPEndPoint sender = new IPEndPoint(IPAddress.Any, 0); //13


EndPoint Remote = (EndPoint)(sender); //14

recv = newsock.ReceiveFrom(data, ref Remote); //15

Console.WriteLine("Message received from {0}:", Remote.ToString());//16


Console.WriteLine(Encoding.ASCII.GetString(data, 0, recv)); //17

string welcome = "Welcome to my test server"; //18


data = Encoding.ASCII.GetBytes(welcome); //19
newsock.SendTo(data, data.Length, SocketFlags.None, Remote); //20
while(true) //21
{
data = new byte[1024]; //22
recv = newsock.ReceiveFrom(data, ref Remote); //23

Console.WriteLine(Encoding.ASCII.GetString(data, 0, recv)); //24


newsock.SendTo(data, recv, SocketFlags.None, Remote); //25
}
}
}

จากตัวอยางจะพบวา
- ในขั้นตอนสราง Socket
o ใช SocketType เปน SocketType.Dgram
o ใช ProtocolType เปน ProtocolType.Udp
- เมื่อตองการใหโปรแกรมที่สรางขึ้นสามารถรับขอมูลไดตองมีขั้นตอนการ bind
- ไมมีขั้นตอนของการ connect
- ในการสรางตัวแปร Remote เพื่อเปน parameter ใหกับ ReceiveFrom() นั้นจะทําการสราง Object
sender ซึ่งเปนชนิด IPEndPoint ขึ้นมาโดยกําหนดคาให sender บรรจุ IPEndPoint วาง ๆ เอาไว
จากนั้น assign คา sender ใหกับ Remote โดยทําการแปลง Type จาก IPEndPoint เปน EndPoint
(ดังในตัวอยางโปรแกรมในบรรทัดที่ 13-14-15)
- ใช SendTo() และ ReceiveFrom() ในการรับ – สงขอมูล

ขอสังเกต
จากที่ไดกลาวไวตอนตน การเชื่อมตอแบบ Connectionless นี้ ใช Protocol สําหรับการสื่อสาร UDP
ดังนั้นจึงไมมีการสราง virtual connection ระหวางเครื่องสงและเครื่องรับ ดังนั้นหากเครื่อง client จบการทํางาน
และปด socket ไป โปรแกรมทางฝงของ server ก็ไมอาจทราบไดวาฝง client นั้นไดปด socket ไปแลว และจะ
ยังคงรอรับการติดตอจาก Client ตอไป

เอกสารประกอบการสอนวิชา 01074201 Network Programming อ. วัจนพงศ เกษมศิริ ภาควิชาวิศวกรรมคอมพิวเตอร สจล.


Connectionless Socket (Datagram Socket) 8

โปรแกรมตัวอยาง 4.2 UDP Client


using System;
using System.Net;
using System.Net.Sockets;
using System.Text;

class SimpleUdpClient
{
public static void Main()
{
byte[] data = new byte[1024];
string input, stringData;
IPEndPoint ipep = new IPEndPoint(
IPAddress.Parse("127.0.0.1"), 9050);

Socket server = new Socket(AddressFamily.InterNetwork,


SocketType.Dgram, ProtocolType.Udp);

string welcome = "Hello, are you there?";


data = Encoding.ASCII.GetBytes(welcome);
server.SendTo(data, data.Length, SocketFlags.None, ipep);

IPEndPoint sender = new IPEndPoint(IPAddress.Any, 0);


EndPoint Remote = (EndPoint)sender;

data = new byte[1024];


int recv = server.ReceiveFrom(data, ref Remote);

Console.WriteLine("Message received from {0}:", Remote.ToString());


Console.WriteLine(Encoding.ASCII.GetString(data, 0, recv));

while(true)
{
input = Console.ReadLine();
if (input == "exit")
break;
server.SendTo(Encoding.ASCII.GetBytes(input), Remote);
data = new byte[1024];
recv = server.ReceiveFrom(data, ref Remote);
stringData = Encoding.ASCII.GetString(data, 0, recv);
Console.WriteLine(stringData);
}
Console.WriteLine("Stopping client");
server.Close();
}
}

เอกสารประกอบการสอนวิชา 01074201 Network Programming อ. วัจนพงศ เกษมศิริ ภาควิชาวิศวกรรมคอมพิวเตอร สจล.


Connectionless Socket (Datagram Socket) 9

4.2.2 การใชงาน Connect() รวมกับ Connectionless Socket


ในบางครั้งโปรแกรมฝง client นั้นตองการติดตอกับ server เพียงเครื่องเดียว ดังนั้นการที่จะตองระบุ
EndPoint ของเครื่องปลายทางในทุก ๆ ครั้งที่สงขอมูล หรือการที่จะตองเตรียมตัวแปรไวรองรับคา EndPoint ฝง
สงเพื่อใชกับคําสั่ง ReceiveFrom() นั้นคอนขางจะเปนสิ่งที่ไมจําเปน
ใน C# เมื่อตองการใชงาน Connectionless Socket โดยใชรับ – สงขอมูลกับเครื่องปลายทางเพียงเครื่อง
เดียวนั้น สามารถใชคําสั่ง Connect() เขามาชวยได ซึ่งจะทําใหการทํางานตาง ๆ ลดความยุงยากลง นอกจากนั้น
เมื่อใช Connect() จะทําใหสามารถใชคําสั่ง Send() และ Receive() ไดอีกดวยโดยที่การสื่อสารตาง ๆ จะยังคงใช
UDP Packet เชนเดิม การใชงาน Connect() ไดแสดงไวในโปรแกรมตัวอยาง 4.3

โปรแกรมตัวอยาง 4.3 UDP client แบบใช Connect()


using System;
using System.Net;
using System.Net.Sockets;
using System.Text;

class ConnectUdpClient
{
public static void Main()
{
byte[] data = new byte[1024];
string input, stringData;
IPEndPoint ipep = new IPEndPoint(
IPAddress.Parse("127.0.0.1"), 9050);

Socket server = new Socket(AddressFamily.InterNetwork,


SocketType.Dgram, ProtocolType.Udp);

server.Connect(ipep);
string welcome = "Hello, are you there?";
data = Encoding.ASCII.GetBytes(welcome);
server.Send(data);

data = new byte[1024];


int recv = server.Receive(data);

Console.WriteLine("Message received from {0}:", ipep.ToString());


Console.WriteLine(Encoding.ASCII.GetString(data, 0, recv));

เอกสารประกอบการสอนวิชา 01074201 Network Programming อ. วัจนพงศ เกษมศิริ ภาควิชาวิศวกรรมคอมพิวเตอร สจล.


Connectionless Socket (Datagram Socket) 10

โปรแกรมตัวอยาง 4.3 UDP client แบบใช Connect() (ตอ)


while(true)
{
input = Console.ReadLine();
if (input == "exit")
break;
server.Send(Encoding.ASCII.GetBytes(input));
data = new byte[1024];
recv = server.Receive(data);
stringData = Encoding.ASCII.GetString(data, 0, recv);
Console.WriteLine(stringData);
}
Console.WriteLine("Stopping client");
server.Close();
}
}

คําสั่ง Connect() ในโปรแกรมขางตนไมไดสราง virtual connection เชนเดียวกับที่เกิดขึ้นใน Connection oriented


socket เนื่องจากวา socket ที่สรางขึ้นนั้นถูกกําหนดใหเปน Connectionless แตทั้งนี้ผลที่เกิดจากการใชคําสั่ง
Connect() ก็คือขอมูล endpoint ของ Socket ที่สรางขึ้นจะถูกกําหนดใหเปนไปตาม IPEndPoint object ที่ใสใหเปน
parameter ของคําสั่ง Connect() นั้น ดวยเหตุนี้การเรียกใชงาน method Send() และ Receive() นั้นจะอางอิง
address ปลายทางจาก object ชื่อ IPEndPoint โดยอัตโนมัติ สําหรับผลการทํางานของโปนแกรม
ConnectUDPClient นี้จะใหผลลัพธเชนเดียวกันกับโปรแกรม SimpleUDPClient
4.2.3 ขอดีของ Connectionless Socket
ดวยขอกําหนดของ UDP ให message ทุก message ที่รับเขามานั้นไมสูญเสียขอบเขตของตัว message
เนื่องจาก Buffer ของ UDP มีลักษณะเปน queue ดังที่ไดกลาวมาแลวในหัวขอ 4.1 และดวยกลไกการทํางานที่ไม
ตองสรางเสนทางการเชื่อมตอเสมือนทําให Socket แบบ Connectionless นี้เมื่อถูกสรางขึ้นมาแลวสามารถรับ
ขอมูลจากคอมพิวเตอรหลาย ๆ เครื่องไดพรอม ๆ กันตอ 1 socket ดังนั้นลักษณะเฉพาะของตัว UDP Packet เอง
จะตองบงบอกถึงผูสงและขอบเขตของ message นั้น โดยความสามารถในการระบุขอบเขตของ message นี้
นักศึกษาสามารถทดลองไดโดยเรียกใชงานโปรแกรมตัวอยาง 4.4 และ โปรแกรมตัวอยาง 4.5 ในหนาถัดไป แลว
สังเกตผลที่ได
จากโปรแกรมตัวอยาง 4.4 และ โปรแกรมตัวอยาง 4.5 เมื่อสั่งใหโปรแกรมทํางานโดยแยกใหโปรแกรม
client ทํางานที่เครื่องหนึ่ง และโปรแกรม server ทํางานอยูบนอีกเครื่องหนึ่งแลวทําการสื่อสารขอมูลกันผาน
ระบบเน็ตเวิรคจริง ๆ จะพบวาผลลัพธนั้นก็คือที่โปรแกรม server จะรับขอมูล 5 messages จากโปรแกรม client
แลวแสดงบนหนาจอ ซึ่งจะพบวา messages ทั้ง 5 ที่ไดรับมานั้นจะมีรูปแบบเหมือนกันตนฉบับ และหากระบบ
เน็ตเวิรคที่ทําการทดสอบนั้นมี traffic หรือ load มากก็อาจจะพบวาที่โปรแกรม server อาจไมไดรับ message ครบ
ทั้ง 5 messages ก็ได ซึ่งเปนการยืนยันวา UDP จะรับประกันในเรื่องของขอบเขตของแตละ message แตใน
ขณะเดียวกันจะไมรับประกันวา message จะสงถึงปลายทางหรือไม

เอกสารประกอบการสอนวิชา 01074201 Network Programming อ. วัจนพงศ เกษมศิริ ภาควิชาวิศวกรรมคอมพิวเตอร สจล.


Connectionless Socket (Datagram Socket) 11

โปรแกรมตัวอยาง 4.4 โปรแกรม server สําหรับทดสอบขอบเขต message ของ Datagram socket


using System;
using System.Net;
using System.Net.Sockets;
using System.Text;

class TestUdpSrvr
{
public static void Main()
{
int recv;
byte[] data = new byte[1024];
IPEndPoint ipep = new IPEndPoint(IPAddress.Any, 9050);

Socket newsock = new Socket(AddressFamily.InterNetwork,


SocketType.Dgram, ProtocolType.Udp);

newsock.Bind(ipep);
Console.WriteLine("Waiting for a client...");

IPEndPoint sender = new IPEndPoint(IPAddress.Any, 0);


EndPoint tmpRemote = (EndPoint)(sender);

recv = newsock.ReceiveFrom(data, ref tmpRemote);

Console.WriteLine("Message received from {0}:", tmpRemote.ToString());


Console.WriteLine(Encoding.ASCII.GetString(data, 0, recv));

string welcome = "Welcome to my test server";


data = Encoding.ASCII.GetBytes(welcome);
newsock.SendTo(data, data.Length, SocketFlags.None, tmpRemote);

for(int i = 0; i < 5; i++)


{
data = new byte[1024];
recv = newsock.ReceiveFrom(data, ref tmpRemote);
Console.WriteLine(Encoding.ASCII.GetString(data, 0, recv));
}
newsock.Close();
}
}

เอกสารประกอบการสอนวิชา 01074201 Network Programming อ. วัจนพงศ เกษมศิริ ภาควิชาวิศวกรรมคอมพิวเตอร สจล.


Connectionless Socket (Datagram Socket) 12

โปรแกรมตัวอยาง 4.5 โปรแกรม client สําหรับทดสอบขอบเขต message ของ Datagram socket


using System;
using System.Net;
using System.Net.Sockets;
using System.Text;

class TestUdpClient
{
public static void Main()
{
byte[] data = new byte[1024];
IPEndPoint ipep = new IPEndPoint(
IPAddress.Parse("127.0.0.1"), 9050);

Socket server = new Socket(AddressFamily.InterNetwork,


SocketType.Dgram, ProtocolType.Udp);

string welcome = "Hello, are you there?";


data = Encoding.ASCII.GetBytes(welcome);
server.SendTo(data, data.Length, SocketFlags.None, ipep);

IPEndPoint sender = new IPEndPoint(IPAddress.Any, 0);


EndPoint tmpRemote = (EndPoint)sender;

data = new byte[1024];


int recv = server.ReceiveFrom(data, ref tmpRemote);

Console.WriteLine("Message received from {0}:", tmpRemote.ToString());


Console.WriteLine(Encoding.ASCII.GetString(data, 0, recv));

server.SendTo(Encoding.ASCII.GetBytes("message 1"), tmpRemote);


server.SendTo(Encoding.ASCII.GetBytes("message 2"), tmpRemote);
server.SendTo(Encoding.ASCII.GetBytes("message 3"), tmpRemote);
server.SendTo(Encoding.ASCII.GetBytes("message 4"), tmpRemote);
server.SendTo(Encoding.ASCII.GetBytes("message 5"), tmpRemote);

Console.WriteLine("Stopping client");
server.Close();
}
}

เอกสารประกอบการสอนวิชา 01074201 Network Programming อ. วัจนพงศ เกษมศิริ ภาควิชาวิศวกรรมคอมพิวเตอร สจล.


Connectionless Socket (Datagram Socket) 13

4.2.4 ขอควรระวังในการใชงาน Connectionless Socket


ในการใชงาน Connectionless Socket มีขอพึงระวังอยู 2 ขอดวยกันคือ
i. การสูญหายของขอมูลเนื่องจากขอกําหนดบางประการของ method ชื่อ ReceiveFrom()
ii. Detecting and allowing for lost packets
ซึ่งขอพึงระวังดังกลาวหากในการเขียนโปรแกรมเพื่อสื่อสารขอมูลผาน Connectionless Socket แลวไมมีการระวัง
(aware) ที่ดีพอ ก็อาจทําใหเกิดการทํางานของโปรแกรมที่ผิดเพี้ยนไปจากวัตถุประสงคที่ตั้งไวได
4.2.4.1 การปองกันขอมูลสูญหาย
พิจารณาโปรแกรมตัวอยาง 4.6 BadUdpClient ตอไปนี้

โปรแกรมตัวอยาง 4.6 BadUdpClient


using System;
using System.Net;
using System.Net.Sockets;
using System.Text;

class BadUdpClient
{
public static void Main()
{ 1
byte[] data = new byte[30];
string input, stringData;
IPEndPoint ipep = new IPEndPoint(
IPAddress.Parse("127.0.0.1"), 9050);

Socket server = new Socket(AddressFamily.InterNetwork,


SocketType.Dgram, ProtocolType.Udp);

string welcome = "Hello, are you there?";


data = Encoding.ASCII.GetBytes(welcome);
server.SendTo(data, data.Length, SocketFlags.None, ipep);

IPEndPoint sender = new IPEndPoint(IPAddress.Any, 0);


EndPoint tmpRemote = (EndPoint)sender;

data = new byte[30];


int recv = server.ReceiveFrom(data, ref tmpRemote);

Console.WriteLine("Message received from {0}:", tmpRemote.ToString());


Console.WriteLine(Encoding.ASCII.GetString(data, 0, recv));

while(true)
{
input = Console.ReadLine();
if (input == "exit")
break;
server.SendTo(Encoding.ASCII.GetBytes(input), tmpRemote);
data = new byte[30];
recv = server.ReceiveFrom(data, ref tmpRemote);
stringData = Encoding.ASCII.GetString(data, 0, recv);
Console.WriteLine(stringData);
}
Console.WriteLine("Stopping client");
server.Close();
}
}

เอกสารประกอบการสอนวิชา 01074201 Network Programming อ. วัจนพงศ เกษมศิริ ภาควิชาวิศวกรรมคอมพิวเตอร สจล.


Connectionless Socket (Datagram Socket) 14

ณ ตําแหนงที่ 1 ในโปรแกรมจะเปนการกําหนด Read Buffer โดยกําหนดเปน byte array ในที่นี้กําหนดใหมีขนาด


เปน 30 ไบตหากขอมูลที่รับเขามาทาง socket มีขนาดนอยกวาหรือเทากับ 30 byte โปรแกรมก็จะทํางานไดอยาง
ราบรื่น หากขอมูลที่รับเขามาทาง socket นั้นมีขนาดใหญกวา 30 bytes สิ่งที่เกิดขึ้นคือ Exception Error ซึ่ง
Exception Error ดังกลาวจะเกิดเมื่อคําสั่ง ReceiveFrom() ทําการอานขอมูลเขามาไวใน Read Buffer แลวปรากฏ
วาไมสามารถนําขอมูลทั้งหมดที่อานไดมาใสไวใน Read Buffer กลาวคือยังมีขอมูลคางอยูใน UDP Buffer ที่ไม
สามารถนํามาไดนั่นเอง ReceiveFrom() ก็จะทําการเตือนระบบเพื่อใหรับทราบถึงสถานการณดังกลาว และ
เนื่องจาก Buffer ของ UDP มีลักษณะเปน queue ดังนั้นขอมูลใดซึ่งอานออกมาไมหมดก็จะหายไป ดังนั้นเมื่อ
ตรวจพบการสูญหายในลักษณะดังกลาวจึงตองใหอีกฝงที่สงขอมูลมาทําการสงขอมูลใหมเทานั้น

โปรแกรมตัวอยาง 4.7 Client ที่มีการตรวจสอบการ lost อันเนื่องมาจาก read buffer


using System;
using System.Net;
using System.Net.Sockets;
using System.Text;

class BetterdUdpClient
{
public static void Main()
{
byte[] data = new byte[30];
string input, stringData;
IPEndPoint ipep = new IPEndPoint(
IPAddress.Parse("127.0.0.1"), 9050);

Socket server = new Socket(AddressFamily.InterNetwork,


SocketType.Dgram, ProtocolType.Udp);

string welcome = "Hello, are you there?";


data = Encoding.ASCII.GetBytes(welcome);
server.SendTo(data, data.Length, SocketFlags.None, ipep);

IPEndPoint sender = new IPEndPoint(IPAddress.Any, 0);


EndPoint tmpRemote = (EndPoint)sender;

data = new byte[30];


int recv = server.ReceiveFrom(data, ref tmpRemote);

Console.WriteLine("Message received from {0}:", tmpRemote.ToString());


Console.WriteLine(Encoding.ASCII.GetString(data, 0, recv));
int i = 30;
while(true)
{
input = Console.ReadLine();
if (input == "exit")
break;
server.SendTo(Encoding.ASCII.GetBytes(input), tmpRemote);
data = new byte[i];
try
{
recv = server.ReceiveFrom(data, ref tmpRemote);
stringData = Encoding.ASCII.GetString(data, 0, recv);
Console.WriteLine(stringData);
} catch (SocketException)
{
Console.WriteLine("WARNING: data lost, retry message.");
i += 10;
} }
Console.WriteLine("Stopping client");
server.Close(); }}

เอกสารประกอบการสอนวิชา 01074201 Network Programming อ. วัจนพงศ เกษมศิริ ภาควิชาวิศวกรรมคอมพิวเตอร สจล.


Connectionless Socket (Datagram Socket) 15

สําหรับปญหาในเรื่องของ Exception Error นั้นสามารถแกไขไดโดยการนําเอา Try…catch มา


ประยุกตใชงานดังเชนในโปรแกรมตัวอยาง 4.7
โปรแกรมตัวอยาง 4.7 ขางตนนอกจากการนําเอา Try…Catch มาใชงานแลวยังมีจุดที่นาสังเกตคือ เมื่อ
เกิด Exception (Buffer Overflow) ขึ้นจะทําการเพิ่มขนาด buffer ใหใหญขึ้นไปอีก 10 bytes โดยอัตโนมัติ ซึ่งจะ
สามารถทําไดโดยการกําหนดขนาดของ byte array ใหอยูในรูปของตัวแปร i
4.2.4.2 การปองกัน Packets Lost
ปญหาหลักอยางหนึ่งของการทํางานกับ Connectionless Socket ก็คือการการสูญหายของขอมูลที่สงไป
บนเน็ตเวิรค ทั้งนี้เนื่องการการสงขอมูลนั้นไมมีการสราง virtual connection ทําใหไมสามารถทราบไดวา packet
นั้นสงถึงปลายทางแลวหรือไม ดังนั้นจึงไดมีการคิดคนวิธีการเพื่อชดเชยขอดอย/แกปญหาของ UDP ในสวนนี้ขึ้น
โดยการใช Option ชื่อ Time-outs ของ socket
4.2.4.2.1 การใช Sockets Time-outs
สําหรับโปรแกรมบางลักษณะซึ่งการสูญหายของ packet มีผลกระทบตอโปรแกรมนั้นเปนอยางมาก จึง
มีการออกแบบวิถีการทํางานของโปรแกรมเพื่อแกปญหาดังกลาว โดยการสรางกระบวนการรับสงใหเปนไปใน
ลักษณะที่วา เมื่อมีการสงขอมูลออกไป หากเครื่องปลายทางไดรับขอมูลนั้นแลวจะทําการสราง packet เพื่อตอบ
รับกลับมา (Acknowledge) หากไมมีการตอบกลับมาในชวงเวลาที่กําหนดก็จะทําการสงขอมูลนั้นซ้ําอีกครั้งหนึ่ง
โดยการจะทําเชนนั้นไดมีข้นั ตอนดังนี้

i. กําหนดคา time-outs ใหกับ Socket ที่สรางขึ้นดังนี้

SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, 1234)

โดย parameter ตัวแรกจะบงบอกถึงชนิดของ Socket Option ที่ตองการทํางานดวย สําหรับ parameter ตัวที่ 2


SocketOptionName จะใชสําหรับระบุ Option ที่ตองการจะกําหนดคา และสุดทายตัวเลขจะเปนคาที่กําหนดให
สําหรับ Option นั้น ๆ ซึ่งในบรรทัดขางตนก็จะเปนการกําหนดคา Time Out ใหกับ Socket นั่นเอง การทํางานที่
เกิดขึ้นเมื่อมีการกําหนด time-outs ใหกับ Socket คือ เมื่อมีการเรียกใชงาน ReceiveFrom() โปรแกรมจะรอรับ
ขอมูลเปนเวลา (หนวยเปนมิลลิวินาที) ตามที่กําหนดไวใน SetSocketOption() ขางตน หาก ReceiveFrom() ไมได
รับขอมูลภายในเวลาดังกลาว จะมีการสราง Exception ขึ้น

ii. การรอรับคา Exception


จากขอ i เมื่อมี Exception เกิดขึ้นยอมแสดงถึงเหตุการณ Time-Outs ของ Socket ดังนั้นจึงตองมีการตั้ง
Try…Catch ขึ้นมาเพื่อรอรับ Exception ที่จะเกิดขึ้น

iii. Retransmission
เมื่อสามารถตรวจจับการสูญหายของขอมูลไดโดยการตั้งคา Time-Outs และ Catch Exception ซึ่งเกิด
จากการ time-outs ไดก็อาจสราง method ขึ้นมาเพื่อ retransmission ซึ่งสูญหายนั้นอีกครั้ง ดังในโปรแกรมตัวอยาง
4.8

เอกสารประกอบการสอนวิชา 01074201 Network Programming อ. วัจนพงศ เกษมศิริ ภาควิชาวิศวกรรมคอมพิวเตอร สจล.


Connectionless Socket (Datagram Socket) 16

โปรแกรมตัวอยาง 4.8 Method สําหรับ Retransmission


private static int AdvSndRcvData(Socket s, byte[] message, EndPoint rmtdevice)
{
int recv = 0;
int retry = 0;

while (true)
{
Console.WriteLine("Attempt #{0}", retry);
try
{
s.SendTo(message, message.Length, SocketFlags.None, rmtdevice);
data = new byte[size];
recv = s.ReceiveFrom(data, ref Remote);
} catch (SocketException e)
{
if (e.ErrorCode == 10054)
recv = 0;
else if (e.ErrorCode == 10040)
{
Console.WriteLine("Error receiving packet");
size += 10;
recv = 0;
}
}

if (recv > 0)
{
return recv;
} else
{
retry++;
if (retry > 4)
{
return 0;
}
}
}
}

จาก Method ขางบน โปรแกรมจะทําการ Retransmission นอกจากนั้นหาก Retransmission แลวยังเกิด Time-Outs


หรือ Error อีกก็จะทําการ Retry อีกทั้งหมด 4 ครั้ง โดย e.ErrorCode ที่เกิดขึ้นจาก Exception นั้นที่สําคัญมีดังนี้

ตาราง 4.1 รหัส e.ErrorCode


Error Code Description Error Code Description
10013 Permission Denied 10042 Bad protocol option
10014 Bad Address 10043 Protocol not support
10022 Invalid Argument 10048 Address already in used
10024 Too many open socket 10050 Network is down
10035 Resource temp. unavailable 10051 Network is unreachable
10036 Operation now in progress 10054 Connection reset by peer
10038 Socket op. on a non Socket 10056 Soc. is already connected
10039 Destination Addr. Require 10057 Socket is not connected
10040 Message too long 10064 Host is down
10041 Protocol wrong type 11001 Host not found

เอกสารประกอบการสอนวิชา 01074201 Network Programming อ. วัจนพงศ เกษมศิริ ภาควิชาวิศวกรรมคอมพิวเตอร สจล.


Connectionless Socket (Datagram Socket) 17

จากวิธีการตาง ๆ ในหัวขอขางตนสามารถนํามาเขียนเปนโปรแกรม Client ซึ่งสามารถรองรับการสูญหายของ


ขอมูลและ retransmission ขอมูลเมื่อการการสูญหายไดดังนี้

โปรแกรมตัวอยาง 4.9 โปรแกรม Client ที่รองรับการ lost และสามารถ retransmission


using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
class BestUdpClient
{
private static byte[] data = new byte[1024];
private static IPEndPoint sender = new IPEndPoint(IPAddress.Any, 0);
private static EndPoint Remote = (EndPoint)sender;
private static int size = 30;
private static int AdvSndRcvData(Socket s, byte[] message, EndPoint rmtdevice)
{
int recv = 0;
int retry = 0;
while (true)
{
Console.WriteLine("Attempt #{0}", retry);
try
{
s.SendTo(message, message.Length, SocketFlags.None, rmtdevice);
data = new byte[size];
recv = s.ReceiveFrom(data, ref Remote);
} catch (SocketException e)
{
if (e.ErrorCode == 10054)
recv = 0;
else if (e.ErrorCode == 10040)
{
Console.WriteLine("Error receiving packet");
size += 10;
recv = 0;
}
}
if (recv > 0)
{
return recv;
} else
{
retry++;
if (retry > 4)
{
return 0;
}
}
}
}

เอกสารประกอบการสอนวิชา 01074201 Network Programming อ. วัจนพงศ เกษมศิริ ภาควิชาวิศวกรรมคอมพิวเตอร สจล.


Connectionless Socket (Datagram Socket) 18

โปรแกรมตัวอยาง 4.9 โปรแกรม Client ที่รองรับการ lost และสามารถ retransmission (ตอ)


public static void Main()
{
string input, stringData;
int recv;
IPEndPoint ipep = new IPEndPoint(
IPAddress.Parse("127.0.0.1"), 9050);

Socket server = new Socket(AddressFamily.InterNetwork,


SocketType.Dgram, ProtocolType.Udp);

int sockopt = (int)server.GetSocketOption(SocketOptionLevel.Socket,


SocketOptionName.ReceiveTimeout);
Console.WriteLine("Default timeout: {0}", sockopt);
server.SetSocketOption(SocketOptionLevel.Socket,
SocketOptionName.ReceiveTimeout, 3000);
sockopt = (int)server.GetSocketOption(SocketOptionLevel.Socket,
SocketOptionName.ReceiveTimeout);
Console.WriteLine("New timeout: {0}", sockopt);

string welcome = "Hello, are you there?";


data = Encoding.ASCII.GetBytes(welcome);

recv = AdvSndRcvData(server, data, ipep);


if (recv > 0)
{
stringData = Encoding.ASCII.GetString(data, 0, recv);
Console.WriteLine(stringData);
} else
{
Console.WriteLine("Unable to communicate with remote host");
return;
}

while(true)
{
input = Console.ReadLine();
if (input == "exit")
break;
recv = AdvSndRcvData(server, Encoding.ASCII.GetBytes(input), ipep);
if (recv > 0)
{
stringData = Encoding.ASCII.GetString(data, 0, recv);
Console.WriteLine(stringData);
} else
Console.WriteLine("Did not receive an answer");
}
Console.WriteLine("Stopping client");
server.Close();
}
}

เอกสารประกอบการสอนวิชา 01074201 Network Programming อ. วัจนพงศ เกษมศิริ ภาควิชาวิศวกรรมคอมพิวเตอร สจล.

You might also like