
บทนำ
เราทราบดีว่าการดำเนินการ I/O มีบทบาทสำคัญในการอ่านและเขียนไฟล์ แต่การดำเนินการ I/O แบบเดิม (blocking) อาจทำให้โปรแกรมหยุดรอและก่อให้เกิดความล่าช้า วิธีการ Non-blocking I/O สามารถแก้ไขปัญหานี้ได้ เนื่องจาก I/O เป็น non-blocking โปรแกรมจึงสามารถทำงานต่อไปได้ขณะที่ I/O operations กำลังดำเนินการ ฟังก์ชัน select เป็นเครื่องมือที่ใช้บ่อยในภาษา C สำหรับ non-blocking I/O ด้วย select() เราสามารถติดตาม file descriptors หลายตัว เช่น sockets หรือ file handles เพื่อตรวจสอบความพร้อมในการอ่าน/เขียน หรือข้อผิดพลาด ฟังก์ชัน select() ช่วยจัดการงาน I/O หลายอย่างได้อย่างมีประสิทธิภาพโดยไม่บล็อกการทำงานของโปรแกรม
เราจะอภิปรายวิธี implement non-blocking I/O ด้วยฟังก์ชัน select() ในภาษา C รวมถึงการใช้งานพื้นฐานและตัวอย่างโปรแกรม
ฟังก์ชัน Select คืออะไร?
ฟังก์ชัน select เป็นเครื่องมืออันทรงพลังในภาษา C ช่วย implement non-blocking I/O ฟังก์ชันนี้ช่วยให้เราติดตาม file descriptors หลายตัว เช่น sockets หรือ file handles เพื่อตรวจสอบว่าพร้อมอ่านหรือเขียนหรือไม่ ฟังก์ชันรับ 3 ชุดของ file descriptors ได้แก่ read set, write set, และ exception set ผ่านชุดเหล่านี้ เราสามารถระบุได้ว่า descriptors ไหนที่ต้องการติดตามสำหรับการดำเนินการเฉพาะ ฟังก์ชันยังรับค่า timeout เพื่อระบุเวลารอสูงสุดสำหรับเหตุการณ์ เมื่อมีเหตุการณ์เกิดขึ้นบน descriptors ที่ติดตาม หรือ timeout หมดอายุ select() จะคืนค่าและให้ข้อมูลเกี่ยวกับ descriptors ที่พร้อม เราจึงสามารถดำเนินการ I/O ได้อย่างมีประสิทธิภาพโดยไม่บล็อกการทำงานของโปรแกรม
ข้อดีของ select()
- จัดการหลาย I/O operations ได้อย่างมีประสิทธิภาพโดยไม่ต้องการ thread ต่อ connection ลดการใช้ทรัพยากร
- รองรับในระบบ Unix/Linux เกือบทุกตัว
- ใช้งานได้กับ file descriptors ทุกประเภท (files, pipes, sockets, terminals)
ข้อจำกัดของ select()
- จำนวน file descriptors สูงสุดที่ติดตามได้ จำกัดโดยระบบปฏิบัติการ (มักเป็น FD_SETSIZE = 1024)
- ประสิทธิภาพลดลงเมื่อจำนวน file descriptors เพิ่มมาก (O(n) scanning)
- ต้อง rebuild fd_set ทุกครั้งที่เรียก select()
ตัวอย่างโปรแกรม: Non-blocking I/O ด้วย select()
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main() {
// File descriptors ที่จะติดตาม
int fd1, fd2;
// เปิด/สร้างไฟล์และตั้งค่าเป็น non-blocking mode
fd1 = open("file1.txt", O_RDONLY | O_NONBLOCK);
fd2 = open("file2.txt", O_WRONLY | O_NONBLOCK | O_CREAT, 0644);
if (fd1 == -1 || fd2 == -1) {
perror("Error opening files");
exit(EXIT_FAILURE);
}
fd_set read_fds, write_fds; // File descriptor sets
struct timeval timeout; // Timeout สำหรับ select
while (1) {
FD_ZERO(&read_fds); // ล้าง read set
FD_ZERO(&write_fds); // ล้าง write set
FD_SET(fd1, &read_fds); // เพิ่ม fd1 เข้า read set
FD_SET(fd2, &write_fds); // เพิ่ม fd2 เข้า write set
timeout.tv_sec = 4; // ตั้ง timeout 4 วินาที
timeout.tv_usec = 0;
int ready_fds = select(fd2 + 1, &read_fds, &write_fds, NULL, &timeout);
if (ready_fds == -1) {
perror("select");
exit(EXIT_FAILURE);
} else if (ready_fds == 0) {
printf("Timeout occurred\n");
} else {
// ตรวจสอบ fd1 พร้อมอ่านหรือไม่
if (FD_ISSET(fd1, &read_fds)) {
char buffer[100];
ssize_t bytesRead = read(fd1, buffer, sizeof(buffer) - 1);
if (bytesRead > 0) {
buffer[bytesRead] = '\0';
printf("Read from file1.txt: %s \n", buffer);
}
}
// ตรวจสอบ fd2 พร้อมเขียนหรือไม่
if (FD_ISSET(fd2, &write_fds)) {
const char* message = "Good morning";
ssize_t bytesWritten = write(fd2, message, strlen(message));
if (bytesWritten > 0) {
printf("Wrote to file2.txt: %s \n", message);
}
}
}
}
// ปิด file descriptors
close(fd1);
close(fd2);
return 0;
}
ผลลัพธ์การรัน
Wrote to file2.txt: Good morning
Wrote to file2.txt: Good morning
Wrote to file2.txt: Good morning
Wrote to file2.txt: Good morning
Timeout occurred
คำอธิบาย
ในโปรแกรม เรา implement non-blocking I/O ด้วย select() ใน C เพื่อติดตาม 2 ไฟล์คือ "file1.txt" และ "file2.txt" ตั้งค่าไฟล์เป็น non-blocking mode ทำให้โปรแกรมทำงานต่อไปได้โดยไม่ต้องรอจนกว่าไฟล์จะอ่านหรือเขียนเสร็จ ใช้ select() ตรวจสอบกิจกรรมบนไฟล์ภายใน timeout ที่กำหนด ถ้าไม่มีกิจกรรมพิมพ์ "Timeout occurred" ถ้ามีกิจกรรม จะตรวจสอบว่าไฟล์ไหนมีกิจกรรม ถ้า file1.txt มีกิจกรรม โปรแกรมอ่านเนื้อหาและพิมพ์ ถ้า file2.txt มีกิจกรรม จะเขียนข้อความ "Good morning" ลงไฟล์ โปรแกรมติดตามไฟล์ไปเรื่อยๆ จนกว่าจะถูกยกเลิก สุดท้ายปิด file descriptors เพื่อปล่อยทรัพยากรระบบ
ทางเลือกอื่น: poll() และ epoll()
สำหรับแอปพลิเคชันที่ต้องการติดตาม file descriptors จำนวนมาก (high concurrency) ควรพิจารณา:
- poll() - คล้าย select() แต่ไม่มีข้อจำกัด FD_SETSIZE ใช้โครงสร้าง pollfd array แทน
- epoll() (Linux-specific) - ประสิทธิภาพสูงกว่ามากสำหรับจำนวน descriptors มาก ใช้ event-driven model O(1) หรือ O(log n)
สรุป
ฟังก์ชัน select() ใน C ให้โซลูชันที่ดีสำหรับ non-blocking I/O operations ด้วยการติดตาม multiple file descriptors ช่วยจัดการหลาย I/O tasks ได้อย่างมีประสิทธิภาพโดยไม่บล็อกการทำงานของโปรแกรม แม้จะมีข้อจำกัด เช่น จำนวน file descriptors สูงสุด และปัญหาประสิทธิภาพกับ descriptors จำนวนมาก แต่ select() ยังคงเป็นตัวเลือกที่ดีสำหรับจัดการ non-blocking I/O ในโปรแกรม C
เกี่ยวกับผู้เขียน

Bamdeb Ghosh
Bamdeb Ghosh มีประสบการณ์ hands-on ในด้าน Wireless networking เป็นผู้เชี่ยวชาญด้านการวิเคราะห์ Wireshark capture บนเครือข่าย Wireless และ Wired พร้อมความรู้เรื่อง Android, Bluetooth, คำสั่ง Linux และ Python ติดตามเว็บไซต์: wifisharks.com