0Pricing
C Academy · บทเรียน

เซิร์ฟเวอร์สะท้อนกลับอย่างง่าย

ให้บริการไคลเอนต์หลายรายพร้อมกัน

เซิร์ฟเวอร์สะท้อนกลับอย่างง่าย เป็นบทเรียน C Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน C Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส C Academy มีบทเรียนทั้งหมด 4 บทเรียน

สิ่งที่เรากำลังสร้าง

เซิร์ฟเวอร์สะท้อนข้อมูลจะรับการเชื่อมต่อ TCP และส่งไบต์ทุกตัวที่ไคลเอ็นต์ส่งกลับไป นี่คือ “สวัสดีโลก” ของการเขียนโปรแกรมเครือข่าย

เราจะสร้างเซิร์ฟเวอร์นี้บน epoll เพื่อให้เธรดเดียวให้บริการไคลเอ็นต์จำนวนมากได้พร้อมกัน ส่วนประกอบต่าง ๆ ได้แก่ ซ็อกเก็ตที่รอรับการเชื่อมต่อ ลูป epoll และการจัดการอ่านแล้วเขียนสำหรับไคลเอ็นต์แต่ละราย

การสร้างซ็อกเก็ตที่รอรับการเชื่อมต่อ

เริ่มด้วย socket() สำหรับปลายทาง TCP แบบ IPv4 ค่าทั้งสามคือ AF_INET SOCK_STREAM และโพรโทคอล 0

ฟังก์ชันนี้คืนค่าตัวบอกไฟล์ ซึ่งเราจะนำไปผูก ตั้งค่าให้ทำงานแบบไม่บล็อก และลงทะเบียนกับ epoll ในภายหลัง ให้ตรวจสอบค่า -1 ในการเรียกระบบทุกครั้ง

int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if (listen_fd == -1) { perror("socket"); exit(1); }

การนำที่อยู่กลับมาใช้ใหม่

ก่อนผูกซ็อกเก็ต ให้ตั้งค่า SO_REUSEADDR ด้วย setsockopt() วิธีนี้ทำให้เซิร์ฟเวอร์เริ่มทำงานใหม่และผูกกับพอร์ตเดิมได้โดยไม่ต้องรอให้สถานะ TIME_WAIT หมดลง

หากไม่ตั้งค่านี้ การเริ่มใหม่อย่างรวดเร็วมักล้มเหลวด้วย EADDRINUSE ซึ่งเป็นปัญหาที่น่าหงุดหงิดระหว่างการพัฒนา

int yes = 1;
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes);

การผูกกับพอร์ต

เติมค่า family พอร์ต และที่อยู่ลงใน struct sockaddr_in ใช้ htons() สำหรับพอร์ต และใช้ INADDR_ANY เพื่อรอรับการเชื่อมต่อจากทุกอินเทอร์เฟซ

จากนั้นเรียก bind() เพื่อผูกซ็อกเก็ตเข้ากับที่อยู่นั้น htons แปลงลำดับไบต์ของโฮสต์เป็นลำดับไบต์ของเครือข่าย ซึ่งเป็นสิ่งจำเป็น

struct sockaddr_in addr = {0};
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(8080);
bind(listen_fd, (struct sockaddr*)&addr, sizeof addr);

การรอรับการเชื่อมต่อและเปลี่ยนเป็นแบบไม่บล็อก

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

จากนั้นเปลี่ยนซ็อกเก็ตเป็นแบบไม่บล็อก เพื่อให้ accept() ไม่หยุดลูปเหตุการณ์ ตอนนี้ซ็อกเก็ตก็พร้อมลงทะเบียนกับ epoll แล้ว

listen(listen_fd, SOMAXCONN);
set_nonblocking(listen_fd);

การลงทะเบียนตัวรอรับการเชื่อมต่อ

สร้างอินสแตนซ์ epoll และเพิ่มซ็อกเก็ตที่รอรับการเชื่อมต่อด้วย EPOLLIN เหตุการณ์อ่านได้ที่เกิดขึ้นตรงนี้หมายความว่ามีไคลเอ็นต์ใหม่กำลังรอให้รับการเชื่อมต่อ

เราเก็บ fd ไว้ใน data.fd เพื่อให้ระบุตัวรอรับการเชื่อมต่อภายในลูปได้

int epfd = epoll_create1(0);
struct epoll_event ev = { .events = EPOLLIN, .data.fd = listen_fd };
epoll_ctl(epfd, EPOLL_CTL_ADD, listen_fd, &ev);

การรับไคลเอ็นต์

เมื่อตัวรอรับการเชื่อมต่ออ่านได้ ให้วนเรียก accept() จนกว่าจะได้ EAGAIN ซ็อกเก็ตของไคลเอ็นต์ใหม่แต่ละรายการจะถูกตั้งค่าให้ทำงานแบบไม่บล็อกและลงทะเบียนเพื่อรอ EPOLLIN

จากจุดนี้ epoll จะแจ้งให้เราทราบทุกครั้งที่ไคลเอ็นต์นั้นส่งข้อมูลที่เราสามารถสะท้อนกลับไปได้

int c = accept(listen_fd, NULL, NULL);
if (c != -1) {
    set_nonblocking(c);
    struct epoll_event cev = { .events = EPOLLIN, .data.fd = c };
    epoll_ctl(epfd, EPOLL_CTL_ADD, c, &cev);
}

ตรรกะการสะท้อนข้อมูล

สำหรับไคลเอ็นต์ที่พร้อมใช้งาน ให้ใช้ recv() อ่านข้อมูลลงในบัฟเฟอร์ หากได้รับไบต์ ให้ส่งไบต์เดียวกันกลับไปทันทีด้วย send()

การอ่านแล้วเขียนอย่างง่ายนี้คือการสะท้อนข้อมูล ในการใช้งานจริง คุณจะต้องเก็บไบต์ที่ส่งไม่หมดไว้ในบัฟเฟอร์ แต่สำหรับการสาธิตนี้ เราสมมติว่าข้อมูลตอบกลับขนาดเล็กพอดีกับบัฟเฟอร์ส่ง

char buf[4096];
ssize_t r = recv(fd, buf, sizeof buf, 0);
if (r > 0) {
    send(fd, buf, r, 0);
}

การตรวจจับการตัดการเชื่อมต่อ

การคืนค่า 0 จาก recv() หมายความว่าไคลเอ็นต์ปิดการเชื่อมต่ออย่างเรียบร้อย การคืนค่าติดลบพร้อมสิ่งอื่นที่ไม่ใช่ EAGAIN คือข้อผิดพลาดจริง

ไม่ว่ากรณีใด ให้ลบตัวบอกไฟล์ออกจาก epoll และเรียก close() เพื่อคืนทรัพยากรและหยุดไม่ให้มีเหตุการณ์เพิ่มเติม

if (r == 0 || (r < 0 && errno != EAGAIN)) {
    epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
    close(fd);
}

ลูปฉบับสมบูรณ์

นำส่วนประกอบต่าง ๆ มารวมกัน: รอเหตุการณ์ ตรวจว่าตัวบอกไฟล์เป็นตัวรอรับการเชื่อมต่อหรือไคลเอ็นต์ แล้วดำเนินการตามนั้น

ลูปขนาดกะทัดรัดนี้คือเซิร์ฟเวอร์สะท้อนข้อมูลแบบพร้อมกันที่ทำงานได้อย่างสมบูรณ์ เธรดเดียวรองรับไคลเอ็นต์จำนวนมาก โดยไม่ต้องสร้างเธรดสำหรับการเชื่อมต่อแต่ละรายการ

for (;;) {
    int n = epoll_wait(epfd, events, MAX_EVENTS, -1);
    for (int i = 0; i < n; i++) {
        if (events[i].data.fd == listen_fd) accept_clients();
        else echo_or_close(events[i].data.fd);
    }
}

การทดสอบและขั้นตอนถัดไป

คอมไพล์บน Linux แล้วเชื่อมต่อด้วย nc localhost 8080 สิ่งที่คุณพิมพ์จะถูกส่งกลับมา เปิดเทอร์มินัลหลายหน้าต่างเพื่อยืนยันการทำงานพร้อมกัน

หากต้องการทำให้ระบบทนทานยิ่งขึ้น ให้จัดเก็บข้อมูลที่ส่งได้เพียงบางส่วนไว้ในบัฟเฟอร์ จัดการ EPOLLOUT เปลี่ยนเป็น EPOLLET พร้อมอ่านข้อมูลจนหมด และจัดการ EPOLLRDHUP สำหรับการปิดการเชื่อมต่อครึ่งหนึ่ง

ตรวจสอบความเข้าใจอย่างรวดเร็ว

ทดสอบความเข้าใจของคุณเกี่ยวกับลำดับการทำงานของเซิร์ฟเวอร์สะท้อนข้อมูล

สรุปทบทวน

คุณได้สร้างเซิร์ฟเวอร์เอคโคด้วย epoll แล้ว ได้แก่ สร้าง ตั้งค่า SO_REUSEADDR ผูก พอร์ต รับฟัง และทำให้ไม่บล็อก ลงทะเบียนตัวรับการเชื่อมต่อ รับการเชื่อมต่อจากไคลเอ็นต์ในลูป และส่งข้อมูลกลับด้วย recv()/send()

จัดการกรณีที่คืนค่าเป็น 0 ให้ถือว่าเป็นการตัดการเชื่อมต่อ แล้วปิดการเชื่อมต่ออย่างเรียบร้อย จากโครงสร้างพื้นฐานนี้ คุณสามารถต่อยอดเป็นเซิร์ฟเวอร์เครือข่ายแบบเธรดเดียวประสิทธิภาพสูงได้

คำถามที่พบบ่อย

บทเรียน “เซิร์ฟเวอร์สะท้อนกลับอย่างง่าย” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “เซิร์ฟเวอร์สะท้อนกลับอย่างง่าย” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส C Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส C Academy มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “เซิร์ฟเวอร์สะท้อนกลับอย่างง่าย”

ให้บริการไคลเอนต์หลายรายพร้อมกัน คุณปฏิบัติ C Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน C Academy หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน C Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน

บทเรียน “เซิร์ฟเวอร์สะท้อนกลับอย่างง่าย” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน C Academy นี้ได้ไหม

ได้ บทเรียน C Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. I/O แบบบล็อกเทียบกับไม่บล็อก
  2. ตั้งค่า epoll
  3. ลูปเหตุการณ์
  4. เซิร์ฟเวอร์สะท้อนกลับอย่างง่าย
← กลับไปที่ C Academy