0Pricing
C Academy · درس

خادم Echo بسيط

خدّم عدة عملاء في آن واحد

خادم Echo بسيط درس مجاني في C Academy على CoddyKit. هذا هو الدرس 4 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في C Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة C Academy 4 دروس في المجموع.

ما الذي سنبنيه

يقبل خادم الصدى اتصالات TCP ويرسل مجددًا كل البايتات التي يرسلها العميل. وهو بمثابة «hello world» لبرمجة الشبكات.

سنبنيه باستخدام 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);

الربط بمنفذ

املأ 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. ويعني حدث قابل للقراءة هنا أن عميلًا جديدًا ينتظر القبول.

نخزن الواصف في 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 على أنها انقطاع اتصال، وأغلقوا الاتصال بطريقة سليمة. وانطلاقًا من هذا الهيكل الأساسي، يمكنكم تطوير خادم شبكة عالي الأداء يعمل بخيط واحد.

الأسئلة الشائعة

هل درس «خادم Echo بسيط» مجاني؟

نعم — نص درس «خادم Echo بسيط» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة C Academy، انتقل إلى CoddyKit PRO. تتضمن دورة C Academy 4 دروس في المجموع.

ماذا ستتعلم في «خادم Echo بسيط»؟

خدّم عدة عملاء في آن واحد تتمرن على C Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ C Academy؟

لا تُشترط خبرة سابقة. C Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 4 من أصل 4.

كم من الوقت يستغرق درس «خادم Echo بسيط»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس C Academy هذا؟

نعم. كل درس في C Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. الإدخال والإخراج الحاجب مقابل غير الحاجب
  2. إعداد epoll
  3. حلقة الأحداث
  4. خادم Echo بسيط
← العودة إلى C Academy