บทนำร่วม 8/8 — หน้าสุดท้ายก่อนแยกเส้นทาง

Socket API — จุดเชื่อมระหว่างแอปกับเครือข่าย

Socket API คือจุดที่แนวคิดเรื่อง address, port, transport, buffer และ connection กลายเป็นคำสั่งที่โปรแกรมเรียกใช้จริง แต่คำว่า send, receive และ connected ในมุมของระบบปฏิบัติการ ไม่ได้หมายถึงความสำเร็จระดับ application เสมอไป

1Socket คืออะไร

Application ไม่ได้สร้าง TCP segment หรือ IP packet ด้วยตนเองทุกครั้ง โดยทั่วไปมันขอให้ระบบปฏิบัติการจัดการผ่าน Socket API ซึ่งทำหน้าที่เป็น interface ระหว่าง process กับบริการสื่อสารที่ระบบมีให้

คำจำกัดความ
Socket คือ endpoint เชิงซอฟต์แวร์ที่ process ใช้ส่งหรือรับข้อมูลผ่านกลไกสื่อสารของระบบปฏิบัติการ สำหรับ Internet socket มันมักเป็นจุดเชื่อมระหว่าง application กับ transport service ส่วน socket family อื่นอาจใช้สื่อสารภายในเครื่องหรือเข้าถึงข้อมูลในระดับต่ำกว่า

Socket เป็นทั้งแนวคิดและทรัพยากรของระบบปฏิบัติการ

ในโปรแกรมแบบ Unix-like การเรียก socket() คืนค่า file descriptor ซึ่งเป็นหมายเลขที่ process ใช้อ้างถึง socket object ที่ kernel ดูแลอยู่ ข้างใน object นี้มี state, buffer, protocol information และข้อมูล address ที่เกี่ยวข้อง ตัวเลข descriptor ไม่ใช่ port และไม่ใช่หมายเลข connection บนเครือข่าย เป็นเพียง handle ภายใน process

Process สองตัวอาจมี descriptor หมายเลข 3 เหมือนกันได้ เพราะแต่ละ process มีตาราง descriptor ของตนเอง เช่นเดียวกับห้อง 3 ในคนละอาคารไม่ใช่ห้องเดียวกัน การดูตัวเลขเพียงค่าเดียวโดยไม่ดูขอบเขตจึงตีความไม่ได้

ลองนึกภาพ — เคาน์เตอร์รับส่งของ

Socket คล้ายเคาน์เตอร์ที่ application นำของมาฝากส่งและมารับของที่มาถึง Application ไม่จำเป็นต้องขับรถผ่านทุกศูนย์กระจายสินค้าเอง เพียงระบุบริการและปลายทาง แล้วระบบด้านหลังจัดการการขนส่งตาม protocol ที่เลือก

แต่การที่เคาน์เตอร์รับของจากเราแล้ว ไม่ได้แปลว่าผู้รับปลายทางเปิดกล่องและทำงานเสร็จแล้ว เช่นเดียวกับ send() ที่สำเร็จอาจหมายถึง kernel รับข้อมูลเข้า buffer แล้วเท่านั้น ขอบเขตของคำว่า “สำเร็จ” ต้องดูว่า operation นั้นรับผิดชอบถึงระดับใด

Address Family, Socket Type และ Protocol

พารามิเตอร์ตัวอย่างกำหนดอะไร
Address FamilyAF_INET, AF_INET6, AF_UNIXรูปแบบ address และขอบเขตการสื่อสาร เช่น IPv4, IPv6 หรือ Unix-domain communication ภายในเครื่อง
Socket TypeSOCK_STREAM, SOCK_DGRAMลักษณะบริการที่ API นำเสนอ เช่น byte stream หรือ message-oriented datagram
Protocol0, IPPROTO_TCP, IPPROTO_UDPเลือก protocol เฉพาะ เมื่อ family กับ type ยังรองรับได้มากกว่าหนึ่งตัว หรือระบุให้ชัดเจน

ใน API ดั้งเดิมจะพบทั้งชื่อ AF_* และ PF_* โดยทั่วไปโปรแกรมใช้ AF_INET หรือ AF_INET6 เป็น address family ส่วนการใช้ AF_INET + SOCK_STREAM + protocol 0 มักเลือก TCP ตามค่าเริ่มต้นของระบบ

ตัวอย่างการสร้าง Socket

int fd = socket(AF_INET, SOCK_STREAM, 0);

คำสั่งนี้ขอ Internet-domain stream socket ซึ่งโดยทั่วไปหมายถึง TCP/IPv4 หากสำเร็จ เราได้ descriptor สำหรับอ้างถึง socket แต่ยังไม่ได้แปลว่าเชื่อมกับ server ใดแล้ว ขั้นตอนสร้าง object กับขั้นตอนสร้าง network connection เป็นคนละเรื่อง

AF_UNIX หรือ AF_LOCAL ใช้ Unix-domain socket สำหรับ inter-process communication ภายในเครื่อง ไม่ใช่ Unix pipe แม้ทั้งคู่ช่วยให้ process สื่อสารกันได้ ส่วน packet socket เช่น AF_PACKET เป็นความสามารถเฉพาะบางระบบอย่าง Linux ใช้เข้าถึง frame ระดับ link และต้องมีสิทธิ์เหมาะสม จึงไม่ใช่เส้นทางปกติของ application ทั่วไป

IP Address บอกเครื่อง ส่วน Port ช่วยบอก Process หรือ Service

IP address ช่วยพาข้อมูลไปยัง host หรือ interface ตามขอบเขตของ network routing ส่วน port number ช่วยให้ Transport Layer ส่งข้อมูลต่อไปยัง socket ของ process ที่เหมาะสมบน host นั้น จึงอาจมอง address กับ port เป็น “ที่อยู่สองระดับ”

ลองนึกภาพ — ที่อยู่อาคารกับหมายเลขห้อง

IP address คล้ายที่อยู่อาคาร ช่วยให้พัสดุเดินทางมาถึงอาคารที่ถูกต้อง ส่วน port คล้ายหมายเลขห้องหรือแผนก ช่วยให้เจ้าหน้าที่ภายในส่งต่อถึงผู้รับบริการที่เหมาะสม

การรู้เพียงอาคารแต่ไม่รู้ห้องอาจส่งของถึงพื้นที่ได้แต่ยังไม่ถึงคนที่ต้องการ ในทางกลับกัน หมายเลขห้อง 443 มีได้ในอาคารจำนวนมาก จึงต้องใช้ address กับ port ร่วมกันตามบริบท

Connection หนึ่งชุดถูกแยกจาก connection อื่นอย่างไร

TCP connection มักระบุได้ด้วยข้อมูล 4 ค่า ได้แก่ source IP, source port, destination IP และ destination port หากรวม transport protocol เข้าไปด้วย มักพูดถึง 5-tuple ข้อมูลชุดนี้ทำให้ server ใช้ local port เดียวรับ client จำนวนมากได้ โดยแต่ละ connection ยังแยกจากกัน

ค่าตัวอย่างหน้าที่
ProtocolTCPบอกกติกาการขนส่งที่ใช้
Source IP:Port192.0.2.10:53124ระบุ endpoint ฝั่ง client ใน connection นี้
Destination IP:Port198.51.100.20:443ระบุ endpoint ของ service ปลายทาง

Client มักปล่อยให้ระบบปฏิบัติการเลือก ephemeral port ชั่วคราวให้ ส่วน server เลือก port ที่ผู้ใช้หรือระบบค้นพบได้ Port 0–1023 เป็น well-known range ตามธรรมเนียมและข้อกำหนดของระบบ แต่ server ของเราใช้ registered หรือ dynamic/private port อื่นก็ได้ หาก client รู้ว่าจะติดต่อที่ใด

หยุดคิดสักครู่
Web server หนึ่งตัว listen ที่ port 443 แล้วรองรับ client พร้อมกันหนึ่งหมื่นรายได้อย่างไร ทั้งที่ทุกคนติดต่อ port เดียวกัน? เพราะ connection ไม่ได้ระบุด้วย destination port ค่าเดียว แต่แยกด้วย tuple ของ protocol และ endpoint ทั้งสองฝั่ง

วงจรชีวิตของ socket ไม่ได้มีเพียงสี่คำสั่ง

ลำดับ operation ขึ้นกับชนิด socket และบทบาท TCP server, TCP client และ UDP endpoint ใช้ขั้นตอนไม่เหมือนกัน การสรุปเป็น create → attach → send/receive → close ช่วยเห็นภาพกว้าง แต่ยังไม่พอสำหรับเขียนโปรแกรมจริง

2TCP Client-Server Flow (ภาพรวม)

TCP เป็น connection-oriented byte stream ฝั่ง server เตรียมจุดรับ connection ส่วน client เริ่มเชื่อมต่อ เมื่อ handshake สำเร็จทั้งสองฝั่งจึงส่ง byte ผ่าน connection ได้ แต่ socket ที่ใช้รอรับกับ socket ที่ใช้คุยกับ client แต่ละรายเป็นคนละ object

socket()สร้าง endpoint
bind()กำหนด local address
listen()เปิดรับ connection
accept()รับ client และได้ socket ใหม่
send/recvแลก byte แล้ว close
ฝั่งเรียกฟังก์ชันความหมาย
Server
(Passive Open)
bind()กำหนด local address และ port ที่ socket จะใช้รับการเชื่อมต่อ อาจ bind ทุก local interface หรือเจาะจง interface
listen()เปลี่ยน socket เป็น listening socket และให้ค่า backlog เป็นคำขอหรือขอบเขตของคิว connection ที่รอ ระบบปฏิบัติการอาจปรับหรือจำกัดค่าจริง
accept()รับ connection ที่พร้อมแล้วและคืน connected socket ใหม่ สำหรับ client รายหนึ่ง ส่วน listening socket เดิมยังเปิดรอ client รายต่อไป
Client
(Active Open)
connect()เริ่มสร้าง connection ไปยัง endpoint ปลายทาง ใน blocking mode มักรอจนสำเร็จ ล้มเหลว หรือ timeout ส่วน non-blocking mode อาจคืนค่าก่อน handshake เสร็จ
ทั้งสองฝั่งsend() / recv()เขียน byte ลงและอ่าน byte จาก socket buffer ตาม state ของ connection ไม่ได้รับประกันว่าจะจัดการข้อมูลครบตามขนาดที่ application ขอใน call เดียว
ทั้งสองฝั่งshutdown() / close()ปิดทิศทางการสื่อสารบางด้านหรือปล่อย descriptor และเริ่มขั้นตอนปิด connection ตาม semantics ของระบบ

Listening Socket กับ Connected Socket

Server เริ่มจาก listening socket หนึ่งตัว เมื่อ accept() สำเร็จ ระบบคืน connected socket อีกตัวสำหรับคุยกับ client รายนั้น Server จึงมี socket สองบทบาทพร้อมกัน: ตัวหนึ่งรับแขกใหม่ อีกหลายตัวดูแลแขกที่เข้ามาแล้ว

ลองนึกภาพ — เคาน์เตอร์ต้อนรับกับโต๊ะสนทนา

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

accept() จึงไม่ได้เปลี่ยน listening socket ให้กลายเป็น connection ของ client คนแรก แต่สร้างหรือคืน socket อีกตัวสำหรับ connection นั้น หากโปรแกรมนำ listening socket ไปใช้ส่งข้อมูลให้ client จะสับสนบทบาทตั้งแต่หน้าประตูครับ

Blocking ไม่ได้แปลว่าโปรแกรมทั้งระบบต้องหยุด

ใน blocking mode คำสั่งอย่าง accept() หรือ recv() อาจรอจนมีเหตุการณ์ที่ต้องการ แต่สิ่งที่หยุดคือ thread ที่เรียก operation นั้น ไม่จำเป็นต้องเป็น process ทั้งหมด Server สามารถใช้หลาย thread, หลาย process, event loop หรือ asynchronous I/O เพื่อดูแลหลาย connection พร้อมกัน

แต่ละแนวทางมีต้นทุนต่างกัน Thread-per-connection เข้าใจง่ายแต่ใช้ทรัพยากรเพิ่มเมื่อ connection จำนวนมาก Event-driven model รองรับ connection ได้มากโดยใช้ thread น้อย แต่ state ของแต่ละ connection และ error path ซับซ้อนขึ้น ไม่มี concurrency model ที่ดีที่สุดโดยไม่ดู workload

connect() สำเร็จรับรองถึงระดับใด

TCP connect สำเร็จหมายถึง transport connection ถูกสร้างกับ endpoint ปลายทางตาม protocol ไม่ได้หมายความว่า application ปลายทาง authenticate ผู้ใช้แล้ว ทำ transaction เสร็จแล้ว หรือแม้แต่พร้อมตอบ request ที่เรากำลังจะส่ง Server process อาจ accept connection แล้วแต่ติดคิวงานภายในอีกนาน

ในทางกลับกัน connect ล้มเหลวอาจให้หลักฐานต่างกัน เช่น connection refused ชี้ว่าปลายทางตอบกลับแต่ไม่มี service รับตามเงื่อนไขนั้น ขณะที่ timeout อาจเกิดจาก packet ถูกกรอง เส้นทางขาด หรือปลายทางเงียบ การแปล error code ต้องดูขอบเขต ไม่ควรสรุปว่า “server ล่ม” จากอาการเดียว

Socket API เป็นจุดร่วมสำคัญ แต่ไม่ใช่ประตูเดียวของทุกกรณี
สำหรับ network application ทั่วไป Internet socket เป็น interface หลักที่ใช้บริการ TCP หรือ UDP และช่วยเชื่อมมุมมอง OSI/TCP-IP เข้ากับโปรแกรมจริง แต่ระบบยังมี Unix-domain socket, raw socket, packet capture framework และ API ระดับสูงที่ซ่อน socket ไว้อีกชั้นหนึ่ง จึงควรพูดว่า socket เป็นจุดร่วมสำคัญ ไม่ใช่ช่องทางเดียวที่ application ทุกตัวต้องใช้โดยตรงเสมอ

3send(), recv() และสิ่งที่ Programmer มักเข้าใจเกินจริง

TCP เป็น Byte Stream ไม่ใช่ Message Stream

TCP รักษาลำดับ byte แต่ไม่รักษาขอบเขตของแต่ละ send() สมมุติ sender ส่ง “ABC” แล้วส่ง “DEF” receiver อาจอ่านได้ “ABCDEF” ครั้งเดียว หรืออ่าน “AB”, “CD”, “EF” หลายครั้งก็ได้ ตราบใดที่ลำดับ byte ยังถูกต้อง

ดังนั้น application protocol ต้องกำหนด message framing เอง เช่น:

วิธี framingแนวคิดสิ่งที่ต้องระวัง
Fixed lengthทุก message ยาวเท่ากันง่ายแต่ไม่เหมาะกับข้อมูลขนาดหลากหลาย และอาจเสียพื้นที่
Delimiterใช้ตัวคั่น เช่น newlineต้อง escape หรือห้ามตัวคั่นปรากฏใน payload โดยไม่ตั้งใจ
Length prefixส่งความยาวก่อน payloadต้องรับ header ให้ครบ ตรวจขนาด และป้องกันค่าความยาวที่ผิดหรือเป็นอันตราย
Self-describing formatรูปแบบข้อมูลบอกจุดสิ้นสุดได้Parser ต้องรองรับข้อมูลไม่ครบและข้อมูลที่ไม่ถูกต้อง
ลองนึกภาพ — เทน้ำลงท่อ

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

TCP ดูแลให้น้ำเรียงลำดับและพยายามส่งให้ครบ แต่ไม่รู้ว่า application เรียกสิ่งใดว่า “หนึ่งข้อความ” เพราะนั่นเป็นความหมายของ protocol ชั้นบน

send() อาจส่งได้ไม่ครบตามที่ขอ

ค่า return ของ send() หรือ write() บอกจำนวน byte ที่ API รับไปในครั้งนั้น โดยเฉพาะ non-blocking socket หรือเมื่อ buffer มีพื้นที่จำกัด อาจรับได้น้อยกว่าขนาดที่ application ขอ Programmer จึงต้องวนส่งส่วนที่เหลือและจัดการ error อย่างถูกต้อง

แม้ส่งเข้า kernel buffer ได้ครบ ก็ยังไม่ได้รับรองว่า byte ถึง application ปลายทาง TCP จะพยายามส่งตาม state ของ connection แต่หากเครื่องหยุดทำงานหลังจากนั้นหรือ connection เกิดปัญหา ผลลัพธ์ระดับ application ยังอาจไม่สำเร็จ

recv() อาจได้ข้อมูลน้อยกว่าหนึ่ง Message

recv() คืน byte ที่พร้อมอ่านในขณะนั้น สูงสุดตามขนาด buffer ที่ application ให้ มันไม่จำเป็นต้องรอให้ “message ของเรา” ครบ เพราะ TCP ไม่รู้ขอบเขตดังกล่าว Application ต้องสะสมข้อมูลจน parser บอกว่าได้ frame ครบ

หาก recv() คืน 0 บน TCP โดยทั่วไปหมายถึง peer ปิดทิศทางการส่งอย่างเป็นระเบียบและเราอ่านข้อมูลที่ค้างหมดแล้ว ไม่ใช่ “ยังไม่มีข้อมูล” ส่วนกรณียังไม่มีข้อมูลใน non-blocking mode จะรายงานสถานะหรือ error ที่ต่างออกไปตาม API

ลองวิเคราะห์
Client เรียก send(fd, buf, 1000, ...) แล้วได้ค่า return 400 โปรแกรมควรทำอย่างไร? ต้องเก็บว่า 400 byte แรกถูก API รับไปแล้ว แล้วส่งต่อจาก byte ที่ 401 ไม่ใช่เริ่มส่ง 1,000 byte เดิมซ้ำทั้งหมด และไม่ควรถือว่า connection เสียทันทีเพียงเพราะเกิด partial write

Buffer ช่วยแยกจังหวะ แต่ไม่ได้มีขนาดไม่จำกัด

Send buffer ช่วยให้ application วางข้อมูลไว้แล้วกลับไปทำงานต่อ ขณะที่ kernel ค่อยส่งตามเครือข่าย Receive buffer เก็บข้อมูลที่มาถึงไว้จน application อ่าน ความเร็วของ application กับ network จึงไม่ต้องตรงกันทุกขณะ

แต่หาก sender สร้างข้อมูลเร็วกว่าที่ network ระบายออก buffer จะเต็มและ send() อาจ block, รับข้อมูลบางส่วน หรือรายงานให้ลองใหม่ หาก receiver อ่านช้า receive window อาจลดลงและ flow control ทำให้ sender ช้าตาม Buffer ดูดซับความต่างระยะสั้นได้ แต่ไม่สามารถแก้ความไม่สมดุลถาวร

Timeout เป็นนโยบายของผู้รอ ไม่ใช่หลักฐานว่าอีกฝั่งไม่ได้ทำงาน

เมื่อ operation timeout เรารู้เพียงว่าไม่เห็นผลที่ต้องการภายในเวลาที่กำหนด Request อาจไม่ถึง server, server อาจทำอยู่, response อาจหาย หรือระบบอาจเพียงช้ากว่าค่าที่เราตั้ง หาก retry operation ที่มีผลข้างเคียงโดยไม่มี idempotency อาจทำงานซ้ำ

การออกแบบจึงต้องแยก connect timeout, read timeout, write timeout และ deadline ของงานทั้งหมด เพราะแต่ละค่าป้องกันการรอคนละช่วง Deadline แบบ end-to-end ช่วยไม่ให้การ retry หลายรอบรวมกันยาวเกินเวลาที่ผู้ใช้ยอมรับ

ลองนึกภาพ — โทรไปแล้วไม่มีคนรับ

เสียงรอสายหมดเวลาไม่ได้พิสูจน์ว่าเจ้าของโทรศัพท์ไม่อยู่ เขาอาจกำลังเดินมารับ สัญญาณอาจมีปัญหา หรือรับแล้วแต่เสียงขากลับไม่ถึงเรา สิ่งที่ผู้โทรรู้แน่มีเพียง “ไม่ได้ยินคำตอบภายในเวลาที่รอ”

Timeout ในระบบเครือข่ายก็เป็นขอบเขตความอดทนของผู้รอ ไม่ใช่กล้องวงจรปิดที่บอกสถานะภายในอีกฝั่งได้ทั้งหมด

close() กับ shutdown() และการปิดเพียงครึ่งทาง

close() ปล่อย descriptor ของ process เมื่อไม่มี reference ที่เกี่ยวข้องเหลือ ระบบจึงดำเนินการปิด socket ตาม semantics ของ protocol ส่วน shutdown() ใช้ปิดทิศทางการอ่านหรือเขียนบางด้านโดยไม่จำเป็นต้องปล่อย descriptor ทันที

TCP เป็น full-duplex จึงสามารถเกิด half-close ได้ เช่น client ส่ง request เสร็จแล้วปิดทิศทางเขียนเพื่อบอกว่าไม่มีข้อมูลเพิ่ม แต่ยังอ่าน response จาก server ต่อ การเข้าใจว่าปิดหนึ่งทิศไม่เท่ากับทำลายทุกอย่างทันทีช่วยออกแบบ protocol ที่ใช้ end-of-stream เป็นสัญญาณได้

4UDP Socket — ไม่มี Connection แต่ไม่ได้แปลว่าไม่มีกติกา

UDP ให้บริการแบบ datagram และรักษาขอบเขต message แต่ไม่สร้าง connection และไม่รับรอง delivery, ordering หรือ duplicate suppression ให้ Application จึงได้ overhead และ state ที่น้อยลง พร้อมรับผิดชอบคุณสมบัติที่ต้องการเพิ่มเติมเอง

ประเด็นTCP Stream SocketUDP Datagram Socket
การเริ่มสื่อสารสร้าง connection ก่อนส่ง byteส่ง datagram ไปยัง address ปลายทางได้โดยไม่ handshake แบบ TCP
ขอบเขตข้อมูลไม่รักษาขอบเขตของ send()รักษาขอบเขต datagram ใน API
Reliability/Orderingให้ byte stream ที่น่าเชื่อถือและเรียงลำดับตามขอบเขต TCPไม่รับรองว่าจะถึง ไม่ซ้ำ หรือเรียงลำดับ
API ที่พบบ่อยconnect, accept, send, recvsendto, recvfrom และอาจใช้ connect เพื่อกำหนด default peer
ตัวอย่างงานWeb, SSH, database connectionDNS query บางรูปแบบ, real-time media และ protocol ที่สร้างกลไกเพิ่มเอง

UDP socket สามารถเรียก connect() ได้ แต่ไม่ได้สร้าง handshake หรือ reliability แบบ TCP โดยทั่วไปเป็นการบันทึก default peer ใน kernel และช่วยกรองหรือส่ง datagram โดยไม่ต้องระบุ address ทุกครั้ง คำว่า connected UDP จึงมีความหมายต่างจาก connected TCP

หนึ่ง recvfrom อ่านหนึ่ง Datagram แต่ Buffer อาจเล็กเกินไป

UDP รักษาขอบเขต datagram ต่างจาก TCP stream หาก sender ส่ง datagram สองก้อน receiver จะไม่เห็นเป็น byte stream ก้อนเดียว อย่างไรก็ตาม หาก receive buffer ที่ application ให้เล็กกว่า datagram ข้อมูลส่วนเกินอาจถูกตัดทิ้งตาม API และไม่สามารถเรียกอ่าน “ส่วนที่เหลือ” เหมือน stream ได้โดยอัตโนมัติ

Datagram ขนาดใหญ่ยังอาจนำไปสู่ IP fragmentation หรือไม่เหมาะกับ path MTU ซึ่งเพิ่มความเสี่ยงต่อ loss Application protocol จึงควรเลือกขนาด message และกลไกแบ่งข้อมูลอย่างระมัดระวัง ไม่ใช่เพียงเห็นว่า UDP รับได้หนึ่ง message แล้วส่งก้อนใหญ่เท่าไรก็ได้

UDP ไม่ได้แปลว่าเร็วเสมอ และ TCP ไม่ได้แปลว่าช้าเสมอ
UDP ไม่มี handshake และกลไกบางอย่างของ TCP จึงเหมาะกับงานบางชนิด แต่หาก application ต้องสร้าง reliability, congestion control, ordering และ security เอง ต้นทุนไม่ได้หายไป เพียงย้ายไปอยู่ที่อื่น ขณะเดียวกัน TCP connection ที่ reuse แล้วอาจส่งข้อมูลได้มีประสิทธิภาพมากกว่าการสร้างกลไกใหม่แบบไม่ครบถ้วนบน UDP

5จาก Socket เดียว ไปสู่ Server ที่รับหลาย Client

Sequential Server เข้าใจง่าย แต่ Client คนเดียวอาจขวางทุกคน

Server แบบง่ายอาจ accept client หนึ่งราย รับ request ประมวลผล ส่ง response แล้วจึงกลับไปรับรายต่อไป หาก client แรกส่งข้อมูลช้า หรือเชื่อมแล้วไม่ส่งอะไร client อื่นทั้งหมดต้องรอ แม้ CPU และ network ยังว่างอยู่

Thread, Process, Event Loop และ Async I/O

รูปแบบแนวคิดข้อดีสิ่งที่ซับซ้อนขึ้น
Process per connectionแยก address space สำหรับแต่ละงานIsolation ดีและ model ตรงไปตรงมาต้นทุน process และการสื่อสารระหว่าง process
Thread per connectionแต่ละ connection มี thread รอและทำงานเขียนแบบลำดับได้ง่ายจำนวน thread, synchronization และ shared state
Event loopthread จำนวนน้อยเฝ้าหลาย socket แล้วทำงานเมื่อพร้อมรองรับ connection จำนวนมากที่รอ I/Oต้องจัด state machine และห้ามงานหนึ่ง block loop นาน
Async/Awaitเขียน flow คล้ายลำดับแต่พักงานระหว่างรอ I/Oอ่านง่ายกว่า callback ในหลายกรณีCancellation, timeout, backpressure และ error propagation

Concurrency ไม่ได้ทำให้ CPU เร็วขึ้นเอง แต่มันเปิดให้ server ใช้เวลาที่งานหนึ่งกำลังรอ I/O ไปดูแลงานอื่น การเลือก model ต้องดูจำนวน connection ระยะเวลารอ ปริมาณ CPU ต่อ request และความง่ายในการรักษา state ให้ถูกต้อง

Backpressure: เมื่อปลายทางช้ากว่า เราจะทำอย่างไร

หาก server สร้าง response เร็วกว่าที่ client รับได้ send buffer จะค่อย ๆ เต็ม ระบบต้องชะลอ producer, เก็บคิวเพิ่ม จำกัดปริมาณงาน หรือยกเลิก connection บางส่วน หากรับทุกอย่างโดยไม่มีขอบเขต หน่วยความจำจะกลายเป็น buffer สุดท้ายและล่มทั้ง process

Backpressure คือการส่งสัญญาณย้อนกลับว่าฝั่งถัดไปรับงานไม่ทัน แนวคิดนี้ปรากฏตั้งแต่ TCP flow control ไปจนถึง message queue และ microservices Socket programming ทำให้เราเห็นปัญหานี้ในรูปพื้นฐานที่สุด

ลองวิเคราะห์
Server รับ connection ได้รวดเร็ว แต่ประมวลผลแต่ละ request ใช้ 10 วินาที การเพิ่ม backlog ให้ใหญ่มากขึ้นแก้ capacity หรือเพียงทำให้ client รอในคิวนานขึ้น? Backlog ช่วยรับแรงกระแทกระยะสั้น แต่ไม่ได้เพิ่มอัตราที่ server ทำงานเสร็จในระยะยาว

6ข้อผิดพลาดที่ควรตรวจให้พบตั้งแต่ห้องปฏิบัติการ

ความเข้าใจผิดสิ่งที่เกิดขึ้นจริงแนวทางตรวจหรือแก้
หนึ่ง send ตรงกับหนึ่ง recvTCP เป็น byte stream และแบ่ง/รวมข้อมูลได้ออกแบบ framing และวนอ่านจน message ครบ
send สำเร็จแปลว่าปลายทางทำงานแล้วอาจเพียงเขียนเข้า local bufferออกแบบ application acknowledgment หรือ response
recv ครั้งเดียวได้ข้อมูลครบอาจได้เพียงบางส่วน หรือถูก interruptวนอ่านและจัดการ partial read
timeout แปลว่า server ล้มเหลวสถานะปลายทางยังคลุมเครือใช้ request ID, idempotency และตรวจผลย้อนหลัง
listen backlog คือจำนวน client สูงสุดเกี่ยวกับคิว connection ที่รอ ไม่ใช่ capacity ทั้งหมดวัด accept rate, active connections และ processing capacity แยกกัน
UDP ไม่ต้องออกแบบ protocolApplication ยังต้องกำหนด message, timeout, retry และ security ตามงานเขียน wire format และ failure behavior ให้ชัด
close แล้วข้อมูลถึงครบแน่นอนผลขึ้นกับข้อมูลค้าง error และพฤติกรรมการปิดตรวจ return code และใช้ application-level completion เมื่อจำเป็น

ตรวจ Return Value และ Error ทุกครั้ง

System call อาจล้มเหลว ถูก interrupt หรือทำงานได้เพียงบางส่วน โปรแกรมที่ทดสอบบนเครื่องเดียวอาจดูปกติ แต่พังเมื่อเจอ network delay, client disconnect หรือ resource limit การตรวจ return value ไม่ใช่งานตกแต่งหลังเขียนเสร็จ แต่เป็นส่วนหนึ่งของ semantics ของ API

นอกจาก error จาก network ยังมีข้อจำกัดของระบบ เช่น descriptor หมด memory ไม่พอ address ถูกใช้อยู่ หรือ permission ไม่เหมาะสม Network program จึงล้มเหลวได้แม้สาย network ไม่ได้มีปัญหาเลย

ลองนึกภาพ — ใบรับฝากกับใบรับของ

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

Return value ของ send(), TCP acknowledgment และ application response เป็นหลักฐานคนละขอบเขต การใช้หลักฐานระดับล่างไปอ้างความสำเร็จระดับบนคือ overclaim ที่พบบ่อยมากในระบบเครือข่าย

7สรุปบทนำร่วมและขั้นตอนถัดไป

  • เครือข่ายต้องรองรับ application หลากหลาย พร้อมสมดุลมุมมองของ programmer, designer, provider และผู้ใช้จริง
  • Connectivity ไม่ได้หมายถึงเพียงมีสายถึงกัน แต่รวมถึง topology, switching, addressing, routing และเส้นทางสำรอง
  • Packet switching และ statistical multiplexing ช่วยแชร์ทรัพยากรตามการใช้งานจริง แต่ต้องรับมือ queue, congestion และความหน่วงที่เปลี่ยนแปลง
  • Reliability มีหลายด้าน ได้แก่ integrity, delivery, duplication, ordering และ timeliness ไม่ใช่เพียง “ส่งถึงหรือไม่”
  • Protocol กำหนด syntax, semantics และ timing ส่วน layering แบ่งหน้าที่และซ่อนรายละเอียดเท่าที่จำเป็น โดยระบบจริงอาจมี cross-layer interaction
  • OSI เป็น reference model สำหรับแยกหน้าที่ ส่วน TCP/IP สะท้อนสถาปัตยกรรมและ protocol suite ที่ Internet ใช้งานจริง
  • Performance ต้องแยก bandwidth, latency, throughput, goodput, RTT, jitter และ loss พร้อมระบุขอบเขตการวัด
  • Socket API ทำให้ process เรียกใช้บริการสื่อสารได้ แต่ผลสำเร็จของ system call ไม่ควรถูกตีความเกินขอบเขตไปเป็นความสำเร็จของ application
เปลี่ยนมุมมอง
เราเริ่มต้นชุดบทนำด้วยคำถามว่าเครือข่ายคืออะไร และจบที่โปรแกรมเรียก send() แต่เส้นทางระหว่างสองจุดนี้ทำให้เห็นว่า “ส่งข้อมูล” ไม่ใช่ operation เดียว มันเป็นข้อตกลงหลายระดับ ตั้งแต่ signal, frame, packet, transport ไปจนถึงความหมายของ application Socket ทำให้รายละเอียดจำนวนมากใช้งานง่ายขึ้น แต่ไม่ได้ทำให้ความไม่แน่นอนเหล่านั้นหายไป

จากบทนำ ไปสู่การลงมือสร้าง Network Application

หลังจากนี้ผู้เรียนควรได้ทดลองเขียน TCP client/server ที่รองรับ partial read, framing และ timeout จากนั้นจึงเปรียบเทียบกับ UDP ทดลองจำลอง delay, disconnect และ message ซ้ำ เพื่อให้เห็นว่าแนวคิดในบท Reliability และ Performance ปรากฏใน code อย่างไร

เมื่อผ่านจุดนี้ Socket API จะไม่ใช่เพียงรายชื่อ function แต่เป็นรอยต่อที่ทุกแนวคิดก่อนหน้ามาพบกัน: address จาก Network Layer, port และ stream จาก Transport Layer, protocol จาก Application Layer และ failure ที่อาจเกิดได้ตลอดเส้นทาง

คำถามปิดท้ายบทนำร่วม
ถ้า client ส่ง request สำเร็จ TCP ยืนยัน byte ครบ Server ประมวลผลสำเร็จ แต่ response หายก่อนถึง client งานนี้ “สำเร็จ” หรือ “ล้มเหลว”? คำตอบขึ้นกับมุมของแต่ละฝ่าย และนี่คือเหตุผลที่ network computing ต้องเรียนต่อจากการส่ง packet ไปถึงการออกแบบ protocol และ application semantics

ขั้นตอนถัดไป — เลือกเส้นทางที่เหมาะกับจุดเริ่มต้น

ทุกเส้นทางศึกษาระบบเครือข่ายชุดเดียวกัน แต่เริ่มจากคนละมุมและใช้ระดับรายละเอียดต่างกัน สามารถเริ่มจากเส้นทางที่ใกล้พื้นฐานของตน แล้วกลับมาเติมมุมอื่นภายหลังได้