ลูปเหตุการณ์
ตอบสนองต่อซ็อกเก็ตที่อ่านได้
ลูปเหตุการณ์ เป็นบทเรียน C Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน C Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส C Academy มีบทเรียนทั้งหมด 4 บทเรียน
หัวใจของเซิร์ฟเวอร์
ลูปเหตุการณ์คือกลไกหลักที่ขับเคลื่อนเซิร์ฟเวอร์ epoll โดยจะรอซ้ำ ๆ จนกว่าตัวบอกไฟล์จะพร้อมใช้งาน แล้วส่งงานไปยังตัวจัดการ
โครงสร้างของลูปจะเหมือนเดิมเสมอ: เรียก epoll_wait() วนดูเหตุการณ์ที่ส่งคืนมา ดำเนินการกับแต่ละเหตุการณ์ แล้ววนซ้ำอีกครั้ง ทุกอย่างที่เหลือจะเชื่อมต่ออยู่กับโครงร่างนี้
for (;;) {
int n = epoll_wait(epfd, events, MAX_EVENTS, -1);
for (int i = 0; i < n; i++) handle(&events[i]);
}การเรียก epoll_wait
epoll_wait() รับ fd ของ epoll อาร์เรย์ผลลัพธ์ของ epoll_event ความจุของอาร์เรย์ และระยะหมดเวลาเป็นมิลลิวินาที
ฟังก์ชันจะบล็อกจนกว่าจะมีตัวบอกไฟล์อย่างน้อยหนึ่งตัวพร้อมใช้งาน (หรือระยะหมดเวลาสิ้นสุด) แล้วคืนจำนวนเหตุการณ์ที่ถูกเติมลงในอาร์เรย์ของคุณ เฉพาะรายการเหล่านั้นเท่านั้นที่ใช้ได้
struct epoll_event events[MAX_EVENTS];
int n = epoll_wait(epfd, events, MAX_EVENTS, -1);
if (n == -1) { perror("epoll_wait"); }อาร์กิวเมนต์ระยะหมดเวลา
ระยะหมดค่า -1 จะบล็อกอย่างไม่มีกำหนดจนกว่าจะมีเหตุการณ์เกิดขึ้น ค่า 0 จะคืนค่าทันที โดยตรวจสอบสถานะโดยไม่หยุดรอ
ค่าบวกจะจำกัดระยะเวลาที่รอ ซึ่งมีประโยชน์เมื่อคุณต้องทำงานเป็นระยะด้วย เช่น ล้างข้อมูลบันทึกหรือทำให้การเชื่อมต่อที่ไม่มีการใช้งานหมดอายุ
int n = epoll_wait(epfd, events, MAX_EVENTS, 1000); /* up to 1s */
if (n == 0) run_periodic_tasks();การกำหนดขนาดอาร์เรย์เหตุการณ์
ความจุของอาร์เรย์จะจำกัดจำนวนเหตุการณ์ที่พร้อมใช้งานซึ่งการเรียก epoll_wait() หนึ่งครั้งสามารถรายงานได้ หากมีมากกว่านั้น ส่วนที่เหลือจะถูกส่งคืนในการเรียกครั้งถัดไป
ขนาดพอประมาณ เช่น 64 หรือ 1024 ก็เพียงพอ epoll จะหมุนเวียนเหตุการณ์ที่รอดำเนินการอย่างทั่วถึงระหว่างการเรียกต่าง ๆ ดังนั้นจึงไม่มีเหตุการณ์ใดถูกละเลย
#define MAX_EVENTS 64
struct epoll_event events[MAX_EVENTS];การส่งงานบนซ็อกเก็ตที่รอรับการเชื่อมต่อ
ภายในลูป ให้ตรวจสอบก่อนว่าตัวบอกไฟล์ที่พร้อมใช้งานคือตัวที่ใช้รอรับการเชื่อมต่อหรือไม่ หากใช่ แสดงว่ามีการเชื่อมต่อใหม่รออยู่
ให้รับการเชื่อมต่อ ตั้งค่าให้ทำงานแบบไม่บล็อก และลงทะเบียนกับ epoll ตัวบอกไฟล์อื่น ๆ คือไคลเอ็นต์ที่มีข้อมูลให้อ่านหรือมีพื้นที่ให้เขียน
if (events[i].data.fd == listen_fd) {
accept_new_connections(epfd, listen_fd);
} else {
handle_client(epfd, &events[i]);
}การรับการเชื่อมต่อในลูป
เหตุการณ์ความพร้อมใช้งานหนึ่งครั้งบนซ็อกเก็ตที่รอรับการเชื่อมต่ออาจหมายถึงมีการเชื่อมต่อหลายรายการรออยู่ ให้เรียก accept() ซ้ำในลูปจนกว่าจะคืนค่า EAGAIN
สิ่งนี้จำเป็นสำหรับโหมดตรวจขอบ และเป็นแนวปฏิบัติที่ดีในทุกกรณี เพื่อไม่ให้ไคลเอ็นต์ต้องรอจนกว่าจะมีการปลุกครั้งถัดไป
for (;;) {
int c = accept(listen_fd, NULL, NULL);
if (c == -1) { if (errno == EAGAIN) break; else break; }
set_nonblocking(c);
add_to_epoll(epfd, c);
}การจัดการเหตุการณ์ที่อ่านได้
เมื่อ EPOLLIN เกิดขึ้นบนไคลเอ็นต์ ให้ทำการอ่านข้อมูลจากไคลเอ็นต์ ในโหมดตรวจระดับ การเรียก recv() เพียงครั้งเดียวต่อการปลุกหนึ่งครั้งถือว่ายอมรับได้
การคืนค่า 0 หมายความว่าไคลเอ็นต์ปิดการเชื่อมต่อแล้ว ให้ล้างทรัพยากรของการเชื่อมต่อนั้น การคืนค่าติดลบพร้อมกับ EAGAIN หมายความว่าขณะนี้คุณอ่านข้อมูลที่มีอยู่จนหมดแล้ว
ssize_t r = recv(fd, buf, sizeof buf, 0);
if (r == 0) { close_conn(epfd, fd); }
else if (r > 0) { process(buf, r); }การอ่านจนหมดในโหมดตรวจขอบ
เมื่อใช้ EPOLLET คุณต้องอ่านจนกว่า recv() จะคืนค่า EAGAIN เคอร์เนลจะแจ้งเตือนเฉพาะตอนที่สถานะเปลี่ยนเป็นอ่านได้ ดังนั้นไบต์ที่เหลือจะไม่ถูกตรวจพบจนกว่าจะมีการเปลี่ยนแปลงครั้งถัดไป
ครอบการอ่านไว้ในลูป และออกจากลูปเฉพาะเมื่อได้ EAGAIN หรือถึงจุดสิ้นสุดของกระแสข้อมูล
for (;;) {
ssize_t r = recv(fd, buf, sizeof buf, 0);
if (r > 0) process(buf, r);
else if (r == 0) { close_conn(epfd, fd); break; }
else { if (errno == EAGAIN) break; else { close_conn(epfd, fd); break; } }
}การจัดการเหตุการณ์ที่เขียนได้
EPOLLOUT จะเกิดขึ้นเมื่อซ็อกเก็ตสามารถรับข้อมูลขาออกเพิ่มเติมได้ คุณต้องการเงื่อนไขนี้เฉพาะเมื่อมีไบต์ในบัฟเฟอร์ที่ยังส่งไม่ครบก่อนหน้านี้
ล้างข้อมูลออกจากบัฟเฟอร์ เมื่อบัฟเฟอร์ว่างแล้ว ให้เปลี่ยนสิ่งที่สนใจกลับเป็นเฉพาะ EPOLLIN ด้วย EPOLL_CTL_MOD เพื่อหลีกเลี่ยงเหตุการณ์พร้อมเขียนที่เกิดขึ้นอย่างต่อเนื่อง
if (events[i].events & EPOLLOUT) {
flush_pending(fd);
if (buffer_empty(fd)) watch_read_only(epfd, fd);
}การจัดการการรอที่ถูกขัดจังหวะ
epoll_wait() อาจคืนค่า -1 พร้อมกับ errno == EINTR หากสัญญาณมาขัดจังหวะการรอ กรณีนี้ไม่ใช่ข้อผิดพลาดจริง ให้ลองใหม่
ลูปที่ทำงานได้อย่างทนทานจะทำต่อเมื่อพบ EINTR และถือว่ารหัสข้อผิดพลาดอื่นเป็นความผิดพลาดร้ายแรงเท่านั้น หากลืมจัดการกรณีนี้ เซิร์ฟเวอร์อาจหยุดทำงานทันทีที่ได้รับสัญญาณครั้งแรก
int n = epoll_wait(epfd, events, MAX_EVENTS, -1);
if (n == -1) {
if (errno == EINTR) continue;
perror("epoll_wait"); break;
}ลูปเดียวกับไคลเอ็นต์จำนวนมาก
เมื่อนำทุกอย่างมาประกอบกัน เธรดเดียวจะวนผ่านตัวบอกไฟล์ที่พร้อมใช้งานไปเรื่อย ๆ: รับการเชื่อมต่อ อ่าน เขียน ปิด แล้วทำซ้ำ
เนื่องจากเธรดจะหยุดรอใน epoll_wait() เมื่อไม่มีงาน และประมวลผลเฉพาะงานที่พร้อมเมื่อมีงาน การออกแบบนี้จึงรองรับการเชื่อมต่อพร้อมกันหลายหมื่นรายการได้อย่างสบาย
ตรวจสอบความเข้าใจอย่างรวดเร็ว
ทดสอบความเข้าใจของคุณเกี่ยวกับลูปเหตุการณ์
สรุปทบทวน
ลูปเหตุการณ์เรียก epoll_wait() วนดูเหตุการณ์ที่พร้อมใช้งาน แล้วส่งงานตามประเภท: รับการเชื่อมต่อบนซ็อกเก็ตที่รอรับการเชื่อมต่อ อ่านเมื่อเป็น EPOLLIN ล้างข้อมูลเมื่อเป็น EPOLLOUT และล้างทรัพยากรเมื่อมีการตัดการเชื่อมต่อ
วนรับการเชื่อมต่อและอ่านข้อมูล ลองใหม่เมื่อพบ EINTR และอ่านจนหมดในโหมด EPOLLET หัวข้อถัดไปเราจะประกอบเซิร์ฟเวอร์สะท้อนข้อมูลให้สมบูรณ์
เรียนรู้ C ด้วย AI tutor — ฟรี
เขียนและเรียกใช้โค้ดจริงในเบราว์เซอร์ของคุณ รับความช่วยเหลือทันทีจาก AI tutor 24/7 และเรียนรู้ต่อจากที่คุณหยุดบนเว็บหรือในแอป
- คอร์ส
- 39
- บทเรียน
- 144
คำถามที่พบบ่อย
บทเรียน “ลูปเหตุการณ์” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “ลูปเหตุการณ์” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส C Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส C Academy มีบทเรียนทั้งหมด 4 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “ลูปเหตุการณ์”
ตอบสนองต่อซ็อกเก็ตที่อ่านได้ คุณปฏิบัติ C Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน
คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน C Academy หรือไม่
ไม่จำเป็นต้องมีประสบการณ์มาก่อน C Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน
บทเรียน “ลูปเหตุการณ์” ใช้เวลานานแค่ไหน
บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย
ฉันเขียนและรันโค้ดในบทเรียน C Academy นี้ได้ไหม
ได้ บทเรียน C Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ