الإدخال والإخراج الحاجب مقابل غير الحاجب
افهم أهمية حلقات الأحداث
الإدخال والإخراج الحاجب مقابل غير الحاجب درس مجاني في C Academy على CoddyKit. هذا هو الدرس 1 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في C Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة C Academy 4 دروس في المجموع.
معنى الحجب
تعلّق استدعاءات النظام الحاجبة خيط الاستدعاء حتى تصبح العملية قابلة للتنفيذ. فعند استدعاء recv() على مقبس لا يحتوي بيانات، تضع النواة خيطكم في الانتظار حتى تصل البايتات.
يسهل فهم هذا النموذج: اتصال واحد، وخيط واحد، وشيفرة متسلسلة. لكن تكلفته تظهر عند الحاجة إلى خدمة آلاف العملاء في الوقت نفسه.
ssize_t n = recv(fd, buf, sizeof buf, 0);
/* thread sleeps here until data or error */
if (n > 0) handle(buf, n);مشكلة التوسع
مع الإدخال والإخراج الحاجب، يؤدي عميل عالق إلى حجب الخيط بأكمله. والحل التقليدي هو تخصيص خيط واحد، أو عملية واحدة، لكل اتصال.
ينجح ذلك إلى حد معين، لكن 10,000 خيط تعني 10,000 مكدس، وتبديلًا كثيفًا للسياق، وكلفة إضافية للجدولة. وهذه هي مشكلة C10k الشهيرة التي دفعت الخوادم نحو التصاميم المعتمدة على الأحداث.
الوضع غير الحاجب
لا ينام المقبس غير الحاجب أبدًا. فإذا تعذر إكمال الاستدعاء فورًا، فإنه يعيد -1 مباشرةً ويضبط errno على EAGAIN أو EWOULDBLOCK.
تصبح شيفرتكم مسؤولة عن إعادة المحاولة لاحقًا. ويتيح ذلك لخيط واحد إدارة عدة مقابس من دون أن يعلق أبدًا على أحدها.
ssize_t n = recv(fd, buf, sizeof buf, 0);
if (n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
/* no data right now, try again later */
}ضبط O_NONBLOCK باستخدام fcntl
تحولون واصفًا إلى الوضع غير الحاجب بإضافة الراية O_NONBLOCK باستخدام fcntl(). اقرأوا الرايات الحالية دائمًا أولًا، ثم أجروا OR مع البت المطلوب حتى لا تمسحوا الإعدادات الأخرى.
يُستخدم هذا المساعد نفسه مع مقابس الاستماع، ومقابس العملاء المقبولة، والأنابيب على حد سواء.
int set_nonblocking(int fd) {
int flags = fcntl(fd, F_GETFL, 0);
if (flags == -1) return -1;
return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}معالجة القراءات الجزئية
يجعل الإدخال والإخراج غير الحاجب العمليات الجزئية أمرًا اعتياديًا. فقد يعيد recv() عددًا من البايتات أقل من المطلوب، وقد يقبل send() جزءًا فقط من المخزن المؤقت.
يجب تتبع مقدار ما أرسلتموه أو استقبلتموه، ثم المتابعة من ذلك الموضع. لا تفترضوا أبدًا أن استدعاءً واحدًا ينقل كل البايتات.
size_t sent = 0;
while (sent < len) {
ssize_t w = send(fd, buf + sent, len - sent, 0);
if (w < 0) { if (errno == EAGAIN) break; else return -1; }
sent += w;
}الانتظار النشط خطأ
تتمثل الطريقة الساذجة لاستخدام المقابس غير الحاجبة في المرور على جميعها ضمن حلقة وإعادة المحاولة باستمرار. ويستهلك هذا الانتظار النشط 100% من وحدة المعالجة المركزية حتى عندما لا يحدث شيء.
ما نريده فعليًا هو أن نسأل النواة: "أخبريني أي الواصفات جاهزة، ودعيني أنام حتى ذلك الحين." وهذا بالضبط ما توفره إشعارات الجاهزية.
إشعارات الجاهزية
يتيح تعدد إرسال الإدخال والإخراج لخيط واحد الانتظار على عدة واصفات في الوقت نفسه، والاستيقاظ فقط عندما يصبح واحد منها على الأقل جاهزًا. وتتولى النواة مراقبتها نيابةً عنكم.
الواجهتان التقليديتان هما select() وpoll(). وهما تعملان، لكنهما تعيدان فحص كل واصف في كل استدعاء، ما يصبح مكلفًا عند التوسع.
fd_set rfds;
FD_ZERO(&rfds);
FD_SET(fd, &rfds);
select(fd + 1, &rfds, NULL, NULL, NULL);لماذا لا يتوسع select وpoll؟
كل من select() وpoll() يعمل بتعقيد O(n): يمرر كل استدعاء مجموعة الواصفات كاملة إلى النواة، التي تفحصها كلها، ثم تفحصونها كلها مرة أخرى للعثور على الجاهز منها.
كما يصل select() إلى حد أقصى يقارب FD_SETSIZE، وغالبًا ما يكون 1024. ومع آلاف الاتصالات، تصبح هذه الكلفة هي العامل المهيمن.
ظهور epoll
يمثل epoll الحل القابل للتوسع في Linux. تسجلون الاهتمام بواصف مرة واحدة، ثم تحتفظ النواة ببنية بيانات داخلية تتتبع الجاهزية.
يعيد كل انتظار الواصفات الجاهزة فعليًا فقط، ولذلك تتناسب الكلفة مع الاتصالات النشطة لا مع إجمالي الاتصالات. وهذا يجعلها تقريبًا O(1) لكل حدث جاهز.
int epfd = epoll_create1(0);
/* register fds once, then wait for ready events */المقابس غير الحاجبة مع epoll
يشكل epoll والمقابس غير الحاجبة فريقًا واحدًا. يخبركم epoll بأن الواصف جاهز، وتتيح الاستدعاءات غير الحاجبة استنزافه من دون نوم.
ينبغي دائمًا ضبط O_NONBLOCK على المقابس التي تمررونها إلى epoll. وإلا فقد يؤدي استيقاظ زائف أو قراءة جزئية إلى حجب خيط حلقة الأحداث الوحيد.
set_nonblocking(conn_fd);
struct epoll_event ev = { .events = EPOLLIN, .data.fd = conn_fd };
epoll_ctl(epfd, EPOLL_CTL_ADD, conn_fd, &ev);النموذج الذهني
تصوروا الخادم على هيئة حلقة: احجبوا التنفيذ في epoll_wait()، واحصلوا على قائمة صغيرة من الواصفات الجاهزة، ونفذوا عملًا غير حاجب على كل منها، ثم كرروا.
ينام الخيط عندما لا يكون هناك عمل، ويستيقظ فقط للعمل الحقيقي. ويمكن لخيط واحد الآن خدمة عشرات الآلاف من الاتصالات بكفاءة.
تحقق سريع
اختبروا فهمكم للمقابس غير الحاجبة.
مراجعة
الإدخال والإخراج الحاجب بسيط، لكنه يشغل خيطًا لكل اتصال، ويفشل عند التوسع. أما الإدخال والإخراج غير الحاجب فيعيد النتيجة فورًا مع EAGAIN بدلًا من النوم.
يؤدي استطلاع المقابس في حلقة ضيقة إلى هدر وحدة المعالجة المركزية، لذلك نستخدم إشعارات الجاهزية. إن select/poll بتعقيد O(n)، بينما يتوسع epoll ليتعامل مع آلاف الاتصالات. سنضبط epoll بعد ذلك.
الأسئلة الشائعة
هل درس «الإدخال والإخراج الحاجب مقابل غير الحاجب» مجاني؟
نعم — نص درس «الإدخال والإخراج الحاجب مقابل غير الحاجب» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة C Academy، انتقل إلى CoddyKit PRO. تتضمن دورة C Academy 4 دروس في المجموع.
ماذا ستتعلم في «الإدخال والإخراج الحاجب مقابل غير الحاجب»؟
افهم أهمية حلقات الأحداث تتمرن على C Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ C Academy؟
لا تُشترط خبرة سابقة. C Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 1 من أصل 4.
كم من الوقت يستغرق درس «الإدخال والإخراج الحاجب مقابل غير الحاجب»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس C Academy هذا؟
نعم. كل درس في C Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- الإدخال والإخراج الحاجب مقابل غير الحاجب
- إعداد epoll
- حلقة الأحداث
- خادم Echo بسيط