.\" -*- coding: UTF-8 -*- .\" Copyright 2001, Paul Sheer .\" Copyright, the authors of the Linux man-pages project .\" .\" SPDX-License-Identifier: Linux-man-pages-copyleft .\" .\"******************************************************************* .\" .\" This file was generated with po4a. Translate the source file. .\" .\"******************************************************************* .TH SELECT_TUT 2 "8 فبراير 2026" "صفحات دليل لينكس 6.18" .SH الاسم select, pselect \- تعدد إدخال/إخراج متزامن .SH المكتبة مكتبة سي المعيارية (\fIlibc\fP،\ \fI\-lc\fP) .SH موجز انظر \fBselect\fP(2) .SH الوصف تُستخدم استدعاءات النظام \fBselect\fP() و\fBpselect\fP() لمراقبة واصفات ملفات متعددة بكفاءة، لمعرفة ما إذا كان أي منها جاهزًا أو يصبح جاهزًا؛ أي لمعرفة ما إذا كان الإدخال/الإخراج ممكنًا، أو حدثت "حالة استثنائية" على أي من واصفات الملفات. .P .\" توفر هذه الصفحة معلومات أساسية وتعليمية حول استخدام استدعاءات النظام هذه. للحصول على تفاصيل الوسائط ودلالات \fBselect\fP() و\fBpselect\fP()، انظر \fBselect\fP(2). .SS "دمج أحداث الإشارات والبيانات" يكون \fBpselect\fP() مفيدًا إذا كنت تنتظر إشارة بالإضافة إلى واصف (واصفات) ملف لتصبح جاهزة للإدخال/الإخراج. تستخدم البرامج التي تستقبل الإشارات عادةً معالج الإشارة فقط لرفع علامة عامة. ستشير العلامة العامة إلى أن الحدث يجب معالجته في الحلقة الرئيسة للبرنامج. ستتسبب الإشارة في عودة استدعاء \fBselect\fP() (أو \fBpselect\fP()) مع تعيين \fIerrno\fP إلى \fBEINTR\fP. هذا السلوك ضروري حتى يمكن معالجة الإشارات في الحلقة الرئيسة للبرنامج، وإلا فسيحظر \fBselect\fP() إلى أجل غير مسمى. .P الآن، سيكون هناك شرط في مكان ما في الحلقة الرئيسة للتحقق من العلامة العامة. لذا يجب أن نسأل: ماذا لو وصلت إشارة بعد الشرط، ولكن قبل استدعاء \fBselect\fP()؟ الإجابة هي أن \fBselect\fP() سيحظر إلى أجل غير مسمى، على الرغم من أن هناك حدثًا معلقًا بالفعل. تُحل حالة السباق هذه بواسطة استدعاء \fBpselect\fP(). يمكن استخدام هذا الاستدعاء لضبط قناع الإشارة إلى مجموعة من الإشارات التي ستُستقبل فقط داخل استدعاء \fBpselect\fP(). على سبيل المثال، لنفترض أن الحدث المعني هو خروج عملية تابعة. قبل بدء الحلقة الرئيسة، سنحظر \fBSIGCHLD\fP باستخدام \fBsigprocmask\fP(2). سيمكّن استدعاء \fBpselect\fP() الخاص بنا \fBSIGCHLD\fP باستخدام قناع إشارة فارغ. سيبدو برنامجنا كالتالي: .P .EX static volatile sig_atomic_t got_SIGCHLD = 0; \& static void child_sig_handler(int sig) { got_SIGCHLD = 1; } \& int main(int argc, char *argv[]) { sigset_t sigmask, empty_mask; struct sigaction sa; fd_set readfds, writefds, exceptfds; int r; \& sigemptyset(&sigmask); sigaddset(&sigmask, SIGCHLD); if (sigprocmask(SIG_BLOCK, &sigmask, NULL) == \-1) { perror("sigprocmask"); exit(EXIT_FAILURE); } \& sa.sa_flags = 0; sa.sa_handler = child_sig_handler; sigemptyset(&sa.sa_mask); if (sigaction(SIGCHLD, &sa, NULL) == \-1) { perror("sigaction"); exit(EXIT_FAILURE); } \& sigemptyset(&empty_mask); \& for (;;) { /* الحلقة الرئيسة */ /* هيئ readfds و writefds و exceptfds قبل استدعاء pselect()‎. (الكود محذوف). */ \& r = pselect(nfds, &readfds, &writefds, &exceptfds, NULL, &empty_mask); if (r == \-1 && errno != EINTR) { /* عالج الخطأ */ } \& if (got_SIGCHLD) { got_SIGCHLD = 0; \& /* عالج الحدث المؤشر عليه هنا؛ مثلاً wait()‎ لكل الأبناء المنتهين. (الكود محذوف). */ } \& /* المتن الرئيس للبرنامج */ } } .EE .SS عملي إذن ما الغاية من \fBselect\fP()؟ ألا يمكنني ببساطة القراءة والكتابة إلى واصفات الملفات الخاصة بي متى شئت؟ الغاية من \fBselect\fP() هي أنه يراقب واصفات متعددة في الوقت نفسه ويضع العملية في حالة سكون بشكل صحيح إذا لم يكن هناك نشاط. غالبًا ما يجد مبرمجو يونكس أنفسهم في موقف يضطرون فيه إلى التعامل مع إدخال/إخراج من أكثر من واصف ملف واحد حيث قد يكون تدفق البيانات متقطعًا. إذا قمت ببساطة بإنشاء تسلسل من استدعاءات \fBread\fP(2) و\fBwrite\fP(2)، فستجد أن أحد استدعاءاتك قد يحظر انتظار البيانات من/إلى واصف ملف، بينما واصف ملف آخر غير مستخدم على الرغم من أنه جاهز للإدخال/الإخراج. يتعامل \fBselect\fP() بكفاءة مع هذا الموقف. .SS "قانون select" يواجه الكثيرون ممن يحاولون استخدام \fBselect\fP() سلوكًا يصعب فهمه وينتج نتائج غير قابلة للنقل أو نتائج حدية. على سبيل المثال، البرنامج أعلاه مكتوب بعناية لعدم الحظر في أي نقطة، على الرغم من أنه لا يضبط واصفات الملفات الخاصة به على وضع عدم الحظر. من السهل إدخال أخطاء دقيقة تزيل ميزة استخدام \fBselect\fP()، لذا إليك قائمة بالأساسيات التي يجب مراقبتها عند استخدام \fBselect\fP(). .TP 4 1. يجب أن تحاول دائمًا استخدام \fBselect\fP() بدون مهلة زمنية. يجب ألا يكون لبرنامجك ما يفعله إذا لم تكن هناك بيانات متاحة. الكود الذي يعتمد على المهل الزمنية ليس عادةً قابلاً للنقل ويصعب تنقيحه. .TP 2. يجب حساب قيمة \fInfds\fP بشكل صحيح لتحقيق الكفاءة كما هو موضح أعلاه. .TP 3. يجب عدم إضافة أي واصف ملف إلى أي مجموعة إذا كنت لا تنوي التحقق من نتيجته بعد استدعاء \fBselect\fP()، والاستجابة بشكل مناسب. انظر القاعدة التالية. .TP 4. بعد عودة \fBselect\fP()، يجب فحص جميع واصفات الملفات في جميع المجموعات لمعرفة ما إذا كانت جاهزة. .TP 5. الدوال \fBread\fP(2) و\fBrecv\fP(2) و\fBwrite\fP(2) و\fBsend\fP(2) \fIلا\fP تقرأ/تكتب بالضرورة كامل كمية البيانات التي طلبتها. إذا قرأت/كتبت كامل الكمية، فذلك لأن لديك حمولة مرور منخفضة وتدفقًا سريعًا. لن يكون هذا هو الحال دائمًا. يجب أن تتعامل مع حالة تمكن دوالك من إرسال أو استقبال بايت واحد فقط. .TP 6. لا تقرأ/تكتب أبدًا بايتات مفردة فقط في المرة الواحدة إلا إذا كنت متأكدًا حقًا من أن لديك كمية صغيرة من البيانات لمعالجتها. من غير الفعال للغاية عدم قراءة/كتابة أكبر قدر ممكن من البيانات التي يمكنك تخزينها مؤقتًا في كل مرة. المخازن المؤقتة في المثال أدناه هي 1024 بايت على الرغم من أنه يمكن جعلها أكبر بسهولة. .TP 7. .\" Nonetheless, you should still cope with these errors for completeness. استدعاءات \fBread\fP(2) و\fBrecv\fP(2) و\fBwrite\fP(2) و\fBsend\fP(2) و\fBselect\fP() يمكن أن تفشل مع الخطأ \fBEINTR\fP، واستدعاءات \fBread\fP(2) و\fBrecv\fP(2) و\fBwrite\fP(2) و\fBsend\fP(2) يمكن أن تفشل مع ضبط \fIerrno\fP على \fBEAGAIN\fP (\fBEWOULDBLOCK\fP). يجب إدارة هذه النتائج بشكل صحيح (لم يُفعل ذلك بشكل صحيح أعلاه). إذا كان برنامجك لن يستقبل أي إشارات، فمن غير المرجح أن تحصل على \fBEINTR\fP. إذا كان برنامجك لا يضبط الإدخال/الإخراج غير المحظور، فلن تحصل على \fBEAGAIN\fP. .TP 8. لا تستدعِ أبدًا \fBread\fP(2) أو \fBrecv\fP(2) أو \fBwrite\fP(2) أو \fBsend\fP(2) بطول مخزن مؤقت صفري. .TP 9. إذا فشلت الدوال \fBread\fP(2) أو \fBrecv\fP(2) أو \fBwrite\fP(2) أو \fBsend\fP(2) مع أخطاء غير تلك المذكورة في \fB7.‎\fP، أو أعادت إحدى دوال الإدخال 0، مما يشير إلى نهاية الملف، فعندئذٍ يجب \fIألا\fP تمرر واصف الملف هذا إلى \fBselect\fP() مجددًا. في المثال أدناه، أغلقتُ واصف الملف فورًا، ثم ضبطتُه على \-1 لمنع إدراجه في مجموعة. .TP 10. يجب تهيئة قيمة المهلة الزمنية مع كل استدعاء جديد لـ \fBselect\fP()، لأن بعض أنظمة التشغيل تعدل البنية. ومع ذلك، فإن \fBpselect\fP() لا يعدل بنية المهلة الخاصة به. .TP 11. .\" "I have heard" does not fill me with confidence, and doesn't .\" belong in a man page, so I've commented this point out. .\" .TP .\" 11. .\" I have heard that the Windows socket layer does not cope with OOB data .\" properly. .\" It also does not cope with .\" .BR select () .\" calls when no file descriptors are set at all. .\" Having no file descriptors set is a useful .\" way to sleep the process with subsecond precision by using the timeout. .\" (See further on.) بما أن \fBselect\fP() يعدل مجموعات واصفات الملفات الخاصة به، فإذا كان الاستدعاء يُستخدم في حلقة، فيجب إعادة تهيئة المجموعات قبل كل استدعاء. .SH "قيمة الإرجاع" انظر \fBselect\fP(2). .SH ملاحظات بشكل عام، تدعم جميع أنظمة التشغيل التي تدعم المقابس أيضًا \fBselect\fP(). يمكن استخدام \fBselect\fP() لحل العديد من المشكلات بطريقة قابلة للنقل وفعالة يحاول المبرمجون المبتدئون حلها بطريقة أكثر تعقيدًا باستخدام الخيوط، والتفريع (forking)، والتواصل بين العمليات (IPCs)، والإشارات، ومشاركة الذاكرة، وما إلى ذلك. .P استدعاء النظام \fBpoll\fP(2) له نفس وظيفة \fBselect\fP()، وهو أكثر كفاءة إلى حد ما عند مراقبة مجموعات واصفات الملفات المتناثرة. وهو متاح الآن على نطاق واسع، ولكن تاريخيًا كان أقل قابلية للنقل من \fBselect\fP(). .P توفر واجهة برمجة تطبيقات \fBepoll\fP(7) الخاصة بلينكس واجهة أكثر كفاءة من \fBselect\fP(2) و\fBpoll\fP(2) عند مراقبة أعداد كبيرة من واصفات الملفات. .SH أمثلة إليك مثال يوضح بشكل أفضل الفائدة الحقيقية لـ \fBselect\fP(). القائمة أدناه هي برنامج توجيه TCP يوجه من منفذ TCP إلى آخر. .P .\" SRC BEGIN (select.c) .EX #include #include #include #include #include #include #include #include #include #include #include \& static int forward_port; \& #undef max #define max(x, y) ((x) > (y) ? (x) : (y)) \& static int listen_socket(int listen_port) { int lfd; int yes; struct sockaddr_in addr; \& lfd = socket(AF_INET, SOCK_STREAM, 0); if (lfd == \-1) { perror("socket"); return \-1; } \& yes = 1; if (setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) == \-1) { perror("setsockopt"); close(lfd); return \-1; } \& memset(&addr, 0, sizeof(addr)); addr.sin_port = htons(listen_port); addr.sin_family = AF_INET; if (bind(lfd, (struct sockaddr *) &addr, sizeof(addr)) == \-1) { perror("bind"); close(lfd); return \-1; } \& printf("accepting connections on port %d\[rs]n", listen_port); listen(lfd, 10); return lfd; } \& static int connect_socket(int connect_port, char *address) { int cfd; struct sockaddr_in addr; \& cfd = socket(AF_INET, SOCK_STREAM, 0); if (cfd == \-1) { perror("socket"); return \-1; } \& memset(&addr, 0, sizeof(addr)); addr.sin_port = htons(connect_port); addr.sin_family = AF_INET; \& if (!inet_aton(address, (struct in_addr *) &addr.sin_addr.s_addr)) { fprintf(stderr, "inet_aton(): bad IP address format\[rs]n"); close(cfd); return \-1; } \& if (connect(cfd, (struct sockaddr *) &addr, sizeof(addr)) == \-1) { perror("connect()"); shutdown(cfd, SHUT_RDWR); close(cfd); return \-1; } return cfd; } \& #define SHUT_FD1 do { \[rs] if (fd1 >= 0) { \[rs] shutdown(fd1, SHUT_RDWR); \[rs] close(fd1); \[rs] fd1 = \-1; \[rs] } \[rs] } while (0) \& #define SHUT_FD2 do { \[rs] if (fd2 >= 0) { \[rs] shutdown(fd2, SHUT_RDWR); \[rs] close(fd2); \[rs] fd2 = \-1; \[rs] } \[rs] } while (0) \& #define BUF_SIZE 1024 \& int main(int argc, char *argv[]) { int h; int ready, nfds; int fd1 = \-1, fd2 = \-1; int buf1_avail = 0, buf1_written = 0; int buf2_avail = 0, buf2_written = 0; char buf1[BUF_SIZE], buf2[BUF_SIZE]; fd_set readfds, writefds, exceptfds; ssize_t nbytes; \& if (argc != 4) { fprintf(stderr, "Usage\[rs]n\[rs]tfwd " " \[rs]n"); exit(EXIT_FAILURE); } \& signal(SIGPIPE, SIG_IGN); \& forward_port = atoi(argv[2]); \& h = listen_socket(atoi(argv[1])); if (h == \-1) exit(EXIT_FAILURE); \& for (;;) { nfds = 0; \& FD_ZERO(&readfds); FD_ZERO(&writefds); FD_ZERO(&exceptfds); FD_SET(h, &readfds); nfds = max(nfds, h); \& if (fd1 > 0 && buf1_avail < BUF_SIZE) FD_SET(fd1, &readfds); /* Note: nfds is updated below, when fd1 is added to exceptfds. */ if (fd2 > 0 && buf2_avail < BUF_SIZE) FD_SET(fd2, &readfds); \& if (fd1 > 0 && buf2_avail \- buf2_written > 0) FD_SET(fd1, &writefds); if (fd2 > 0 && buf1_avail \- buf1_written > 0) FD_SET(fd2, &writefds); \& if (fd1 > 0) { FD_SET(fd1, &exceptfds); nfds = max(nfds, fd1); } if (fd2 > 0) { FD_SET(fd2, &exceptfds); nfds = max(nfds, fd2); } \& ready = select(nfds + 1, &readfds, &writefds, &exceptfds, NULL); \& if (ready == \-1 && errno == EINTR) continue; \& if (ready == \-1) { perror("select()"); exit(EXIT_FAILURE); } \& if (FD_ISSET(h, &readfds)) { socklen_t addrlen; struct sockaddr_in client_addr; int fd; \& addrlen = sizeof(client_addr); memset(&client_addr, 0, addrlen); fd = accept(h, (struct sockaddr *) &client_addr, &addrlen); if (fd == \-1) { perror("accept()"); } else { SHUT_FD1; SHUT_FD2; buf1_avail = buf1_written = 0; buf2_avail = buf2_written = 0; fd1 = fd; fd2 = connect_socket(forward_port, argv[3]); if (fd2 == \-1) SHUT_FD1; else printf("connect from %s\[rs]n", inet_ntoa(client_addr.sin_addr)); \& /* Skip any events on the old, closed file descriptors. */ \& continue; } } \& /* NB: read OOB data before normal reads. */ \& if (fd1 > 0 && FD_ISSET(fd1, &exceptfds)) { char c; \& nbytes = recv(fd1, &c, 1, MSG_OOB); if (nbytes < 1) SHUT_FD1; else send(fd2, &c, 1, MSG_OOB); } if (fd2 > 0 && FD_ISSET(fd2, &exceptfds)) { char c; \& nbytes = recv(fd2, &c, 1, MSG_OOB); if (nbytes < 1) SHUT_FD2; else send(fd1, &c, 1, MSG_OOB); } if (fd1 > 0 && FD_ISSET(fd1, &readfds)) { nbytes = read(fd1, buf1 + buf1_avail, BUF_SIZE \- buf1_avail); if (nbytes < 1) SHUT_FD1; else buf1_avail += nbytes; } if (fd2 > 0 && FD_ISSET(fd2, &readfds)) { nbytes = read(fd2, buf2 + buf2_avail, BUF_SIZE \- buf2_avail); if (nbytes < 1) SHUT_FD2; else buf2_avail += nbytes; } if (fd1 > 0 && FD_ISSET(fd1, &writefds) && buf2_avail > 0) { nbytes = write(fd1, buf2 + buf2_written, buf2_avail \- buf2_written); if (nbytes < 1) SHUT_FD1; else buf2_written += nbytes; } if (fd2 > 0 && FD_ISSET(fd2, &writefds) && buf1_avail > 0) { nbytes = write(fd2, buf1 + buf1_written, buf1_avail \- buf1_written); if (nbytes < 1) SHUT_FD2; else buf1_written += nbytes; } \& /* Check if write data has caught read data. */ \& if (buf1_written == buf1_avail) buf1_written = buf1_avail = 0; if (buf2_written == buf2_avail) buf2_written = buf2_avail = 0; \& /* One side has closed the connection, keep writing to the other side until empty. */ \& if (fd1 < 0 && buf1_avail \- buf1_written == 0) SHUT_FD2; if (fd2 < 0 && buf2_avail \- buf2_written == 0) SHUT_FD1; } exit(EXIT_SUCCESS); } .EE .\" SRC END .P يقوم البرنامج أعلاه بتوجيه معظم أنواع اتصالات TCP بشكل صحيح بما في ذلك بيانات الإشارة خارج النطاق (OOB) التي ترسلها خوادم \fBtelnet\fP. يعالج المشكلة الصعبة المتمثلة في تدفق البيانات في كلا الاتجاهين في وقت واحد. قد تظن أنه من الأكثر كفاءة استخدام استدعاء \fBfork\fP(2) وتخصيص خيط لكل تيار. يصبح هذا أكثر تعقيدًا مما قد تظن. فكرة أخرى هي ضبط الإدخال/الإخراج غير المحظور باستخدام \fBfcntl\fP(2). هذا أيضًا له مشاكله لأنك ينتهي بك الأمر باستخدام مهلات غير فعالة. .P لا يعالج البرنامج أكثر من اتصال واحد في وقت واحد، على الرغم من أنه يمكن توسيعه بسهولة للقيام بذلك باستخدام قائمة مرتبطة من المخازن المؤقتة\[em]واحد لكل اتصال. في الوقت الحالي، تتسبب الاتصالات الجديدة في قطع الاتصال الحالي. .SH "انظر أيضًا" \fBaccept\fP(2), \fBconnect\fP(2), \fBpoll\fP(2), \fBread\fP(2), \fBrecv\fP(2), \fBselect\fP(2), \fBsend\fP(2), \fBsigprocmask\fP(2), \fBwrite\fP(2), \fBepoll\fP(7) .\" .SH AUTHORS .\" This man page was written by Paul Sheer. .PP .SH ترجمة تُرجمت هذه الصفحة من الدليل بواسطة زايد السعيدي . .PP هذه الترجمة هي وثيقة مجانية؛ راجع .UR https://www.gnu.org/licenses/gpl-3.0.html رخصة جنو العامة الإصدار 3 .UE أو ما بعده للاطلاع على شروط حقوق النشر. لا توجد أي ضمانات. .PP إذا وجدت أي أخطاء في ترجمة صفحة الدليل هذه، يرجى إرسال بريد إلكتروني إلى قائمة بريد المترجمين: .MT kde-l10n-ar@kde.org .ME .