الإنجليزيةالفرنسيةالإسبانية

OnWorks فافيكون

perlipc - عبر الإنترنت في السحابة

قم بتشغيل perlipc في موفر الاستضافة المجاني OnWorks عبر Ubuntu Online أو Fedora Online أو محاكي Windows عبر الإنترنت أو محاكي MAC OS عبر الإنترنت

هذا هو أمر perlipc الذي يمكن تشغيله في موفر الاستضافة المجاني OnWorks باستخدام إحدى محطات العمل المجانية المتعددة عبر الإنترنت مثل Ubuntu Online أو Fedora Online أو محاكي Windows عبر الإنترنت أو محاكي MAC OS عبر الإنترنت

برنامج:

اسم


perlipc - اتصال Perl بين العمليات (الإشارات، fifos، الأنابيب، العمليات الفرعية الآمنة،
المقابس والإشارات)

الوصف


تم بناء مرافق IPC الأساسية لـ Perl من إشارات Unix القديمة الجيدة، والتي تسمى الأنابيب،
يتم فتح توجيه الإخراج، وإجراءات مأخذ توصيل Berkeley، واستدعاءات SysV IPC. يتم استخدام كل منها بشكل طفيف
حالات مختلفة.

إشارات


يستخدم Perl نموذجًا بسيطًا للتعامل مع الإشارات: يحتوي تجزئة %SIG على أسماء أو مراجع
معالجات الإشارة المثبتة بواسطة المستخدم. سيتم استدعاء هذه المعالجات باستخدام وسيطة وهي
اسم الإشارة التي أثارتها. قد يتم إنشاء إشارة عمدا من
تسلسل لوحة مفاتيح معين مثل control-C أو control-Z، يتم إرساله إليك من جهاز آخر
العملية، أو يتم تشغيلها تلقائيًا بواسطة النواة عند حدوث أحداث خاصة، مثل
خروج العملية الفرعية، أو نفاد مساحة المكدس للعملية الخاصة بك، أو الوصول إلى عملية
حد حجم الملف.

على سبيل المثال، لمحاصرة إشارة المقاطعة، قم بإعداد معالج مثل هذا:

قشورنا $؛

كاتش_زاب الفرعي {
بلدي $signame = التحول؛
$shucks++;
يموت "أرسل لي شخص ما اسم SIG$"؛
}
$SIG{INT} = __PACKAGE__ . "::catch_zap";
$SIG{INT} = \&catch_zap; #أفضل استراتيجية

قبل إصدار Perl 5.8.0، كان من الضروري القيام بأقل قدر ممكن من العمل في مشروعك
معالج؛ لاحظ كيف أن كل ما نفعله هو تعيين متغير عام ثم رفع الاستثناء.
وذلك لأنه في معظم الأنظمة، لا تتم إعادة دخول المكتبات؛ وخاصة الذاكرة
التخصيص وإجراءات الإدخال/الإخراج ليست كذلك. وهذا يعني أن تفعل ما يقرب من اى شى في الخاص
يمكن للمعالج من الناحية النظرية أن يؤدي إلى حدوث خطأ في الذاكرة وتفريغ أساسي لاحق - راجع "مؤجل
الإشارات (الإشارات الآمنة)" أدناه.

أسماء الإشارات هي تلك المدرجة بواسطة "kill -l" على نظامك، أو يمكنك ذلك
استرجعها باستخدام وحدة CPAN IPC::Signal.

يمكنك أيضًا اختيار تعيين السلاسل "IGNORE" أو "DEFAULT" كمعالج، حيث
في حالة أن بيرل سيحاول تجاهل الإشارة أو القيام بالشيء الافتراضي.

في معظم أنظمة Unix، تكون إشارة "CHLD" (المعروفة أحيانًا باسم "CLD") مميزة
السلوك فيما يتعلق بقيمة "تجاهل". تعيين $SIG{CHLD} على "IGNORE" في مثل هذا
النظام الأساسي له تأثير عدم إنشاء عمليات زومبي عندما تفشل العملية الأصلية في ذلك
"wait()" في عملياتها الفرعية (أي يتم حصاد العمليات الفرعية تلقائيًا). الاتصال
عادةً ما يُرجع "wait()" مع تعيين $SIG{CHLD} على "IGNORE" "-1" على هذه الأنظمة الأساسية.

لا يمكن محاصرة بعض الإشارات أو تجاهلها، مثل KILL وSTOP (ولكن ليس
إشارات TSTP. لاحظ أن تجاهل الإشارات يجعلها تختفي. إذا كنت تريدهم فقط
تم حظرها مؤقتًا دون أن تضيع، سيتعين عليك استخدام sigprocmask الخاص بـ POSIX.

إرسال إشارة إلى معرف العملية السلبي يعني أنك ترسل الإشارة إلى الكل
مجموعة عمليات يونكس. يرسل هذا الرمز إشارة قطع الاتصال لجميع العمليات الحالية
مجموعة العمليات، ويقوم أيضًا بتعيين $SIG{HUP} على "IGNORE" حتى لا يقتل نفسه:

# نطاق الكتلة المحلي
{
local $SIG{HUP} = "IGNORE";
اقتل HUP => -$$;
# كتابة رائعة لـ: kill("HUP"، -$$)
}

إشارة أخرى مثيرة للاهتمام لإرسالها هي الإشارة رقم صفر. هذا لا يؤثر في الواقع على
عملية فرعية، ولكن بدلاً من ذلك تتحقق مما إذا كانت حية أو تم تغيير معرفاتها الفريدة (UIDs).

ما لم (قتل 0 => $kid_pid) {
تحذير "حدث شيء شرير لـ $kid_pid"؛
}

قد تفشل الإشارة رقم صفر لأنك لا تملك الإذن بإرسال الإشارة عند توجيهها
في العملية التي لا يتطابق فيها UID الحقيقي أو المحفوظ مع UID الحقيقي أو الفعال لـ
عملية الإرسال، على الرغم من أن العملية على قيد الحياة. قد تكون قادرا على تحديد السبب
من الفشل باستخدام $! أو "٪!".

ما لم (kill(0 => $pid) || $!{EPERM}) {
تحذير "$pid يبدو ميتًا"؛
}

قد ترغب أيضًا في استخدام وظائف مجهولة المصدر لمعالجات الإشارات البسيطة:

$SIG{INT} = sub { die "\nOutta here!\n" };

يحتاج معالجو SIGCHLD إلى بعض العناية الخاصة. إذا مات طفل ثان أثناء وجوده في الإشارة
المعالج الذي تسبب في الوفاة الأولى، لن نحصل على إشارة أخرى. لذا يجب أن نكرر هنا وإلا فإننا
سوف يترك الطفل غير المحصود كزومبي. وفي المرة القادمة يموت طفلان
زومبي آخر. وما إلى ذلك وهلم جرا.

استخدم POSIX ":sys_wait_h";
$SIG{CHLD} = فرعي {
بينما ((my $child = waitpid(-1, WNOHANG)) > 0) {
$Kid_Status{$child} = $?;
}
};
#افعل شيئاً يشوك...

كن حذرا: qx (), النظام()، وبعض الوحدات النمطية لاستدعاء الأوامر الخارجية تقوم بـ فرع(),
then انتظر() للنتيجة. وبالتالي، سيتم استدعاء معالج الإشارة الخاص بك. لأن انتظر() وكان
تم استدعاؤه بالفعل من قبل النظام() or qx ()أطلقت حملة انتظر() في معالج الإشارة لن يرى المزيد
الزومبي وبالتالي سيتم حظره.

أفضل طريقة لمنع هذه المشكلة هي استخدام waitpid ()، كما في المثال التالي:

استخدم POSIX ":sys_wait_h"; # للقراءة غير المحظورة

أطفالي؛

$SIG{CHLD} = فرعي {
# لا تغير $! و$؟ معالج خارجي
محلي ($!، $؟)؛
بينما ( (my $pid = waitpid(-1, WNOHANG)) > 0 ) {
حذف $ children{$pid};
cleanup_child($pid, $?);
}
};

بينما (1) {
$pid = fork();
يموت "لا يمكن التفرع" ما لم يتم تحديد $pid؛
إذا ($pid == 0) {
#...
خروج 0 ؛
{} آخر
$أطفال{$pid}=1;
#...
نظام(أمر$);
#...
}
}

يتم استخدام معالجة الإشارة أيضًا للمهلات في Unix. بينما محمية بأمان داخل
كتلة "eval{}"، يمكنك تعيين معالج الإشارة لاحتجاز إشارات الإنذار ومن ثم جدولة موعدها
يتم تسليم واحدة لك في عدد من الثواني. ثم حاول إجراء عملية الحظر،
مسح المنبه عند الانتهاء ولكن ليس قبل الخروج من كتلة "eval{}". لو أنه
تنفجر، سوف تستخدم يموت () للقفز من الكتلة.

وإليك مثال على ذلك:

$ALARM_EXCEPTION الخاص بي = "إعادة تشغيل المنبه"؛
تقييم {
local $SIG{ALRM} = sub { die $ALARM_EXCEPTION };
إنذار 10؛
قطيع(FH, 2) # قفل الكتابة المحظور
|| يموت "لا يمكن أن يتدفق: $!";
إنذار 0؛
};
إذا ($@ && $@ !~ quotemeta($ALARM_EXCEPTION)) { يموت }

إذا انتهت مهلة العملية النظام() or qx ()، هذه التقنية قادرة على توليد
الاموات الاحياء. إذا كان هذا مهمًا بالنسبة لك، فستحتاج إلى القيام بذلك بنفسك فرع() إكسيك ()و اقتل
عملية الطفل الضال.

ولمعالجة الإشارات الأكثر تعقيدًا، قد ترى وحدة POSIX القياسية. للأسف،
هذا غير موثق بالكامل تقريبًا، لكن t/lib/posix.t ملف من مصدر بيرل
التوزيع لديه بعض الأمثلة في ذلك.

معالجة هيه تنفس الصعداء حتى سيجنل in الشياطين
عملية تبدأ عادةً عند تشغيل النظام ويتم إيقاف تشغيله عند إغلاق النظام
down يسمى البرنامج الخفي (Disk And Execution MOnitor). إذا كانت العملية السرية تحتوي على ملف
ملف التكوين الذي تم تعديله بعد بدء العملية، يجب أن يكون هناك ملف
طريقة لإخبار هذه العملية بإعادة قراءة ملف التكوين الخاص بها دون إيقاف العملية.
توفر العديد من البرامج الخفية هذه الآلية باستخدام معالج الإشارة "SIGHUP". عندما تريد أن تقول
البرنامج الخفي لإعادة قراءة الملف، ما عليك سوى إرسال إشارة "SIGHUP" إليه.

يطبق المثال التالي برنامجًا خفيًا بسيطًا، والذي يعيد تشغيل نفسه في كل مرة
تم استقبال إشارة "SIGHUP". الكود الفعلي موجود في الروتين الفرعي "code()"، والذي
ما عليك سوى طباعة بعض معلومات التصحيح لإثبات نجاحها؛ يجب استبداله بالحقيقي
رمز.

#!/ البيرة / بن / بيرل

استخدام صارم
استخدام التحذيرات

استخدم بوسيكس ()؛
استخدم FindBin ()؛
استخدم الملف :: Basename ()؛
استخدم File::Spec::Functions qw(catfile);

$ | = 1 ؛

# جعل البرنامج الخفي مشتركًا بين الأنظمة الأساسية، لذلك يستدعي exec البرنامج النصي دائمًا
# نفسه بالمسار الصحيح، بغض النظر عن كيفية استدعاء البرنامج النصي.
$script = File::Basename::basename($0);
$SELF = catfile($FindBin::Bin, $script);

# يكشف POSIX قناع sigprocmask بشكل صحيح
$SIG{HUP} = فرعي {
طباعة "حصلت على SIGHUP\n";
exec($SELF, @ARGV) || يموت "$0: تعذر إعادة التشغيل: $!";
};

شفرة()؛

الرمز الفرعي {
طباعة "معرف المنتج: $$\n";
طباعة "ARGV: @ARGV\n";
عدد دولاراتي = 0 ؛
بينما (1) {
النوم 2؛
طباعة ++$count, "\n";
}
}

المؤجلة إشارات (آمن الإشارات)
قبل إصدار Perl 5.8.0، كان تثبيت كود Perl للتعامل مع الإشارات يعرضك للخطر
شيئان. أولاً، يتم إعادة إدخال عدد قليل من وظائف مكتبة النظام. إذا انقطعت الإشارة
بينما يقوم Perl بتنفيذ وظيفة واحدة (مثل malloc(3) أو printf(٣) وإشارتك
ثم يستدعي المعالج نفس الوظيفة مرة أخرى، فقد تحصل على سلوك غير متوقع - غالبًا أ
تفريغ الأساسية. ثانيًا، لم تعد لغة Perl نفسها تدخل من جديد عند أدنى المستويات. إذا كانت الإشارة
يقاطع Perl بينما يقوم Perl بتغيير هياكل البيانات الداخلية الخاصة به، بالمثل
قد ينتج عن ذلك سلوك غير متوقع.

كان هناك شيئان يمكنك القيام بهما، وأنت تعلم ذلك: أن تكون مصابًا بجنون العظمة أو أن تكون واقعيًا. ال
كان النهج المصاب بجنون العظمة هو القيام بأقل قدر ممكن في معالج الإشارة الخاص بك. تعيين موجود
متغير عدد صحيح له قيمة بالفعل، والعودة. هذا لا يساعدك إذا كنت في
استدعاء نظام بطيء، والذي سيتم إعادة تشغيله للتو. وهذا يعني أن عليك "الموت" من أجل لونججم(3)
خارج المعالج. حتى هذا يعد متعجرفًا بعض الشيء بالنسبة للمصاب بجنون العظمة الحقيقي، الذي يتجنب
"يموت" في المعالج لأن النظام is خارج ليحصل عليك. كان النهج العملي هو
قل "أعرف المخاطر، لكني أفضّل الراحة"، وافعل أي شيء تريده في حياتك
معالج الإشارة، وكن مستعدًا لتنظيف عمليات التفريغ الأساسية بين الحين والآخر.

يتجنب الإصدار 5.8.0 من Perl والإصدارات الأحدث هذه المشكلات عن طريق إشارات "التأجيل". أي أنه عندما
يتم تسليم الإشارة إلى العملية بواسطة النظام (إلى كود C الذي ينفذ Perl) أ
يتم تعيين العلامة، ويعود المعالج على الفور. ثم في النقاط الاستراتيجية "الآمنة" في
مترجم بيرل (على سبيل المثال عندما يكون على وشك تنفيذ كود تشغيل جديد) يتم فحص العلامات و
يتم تنفيذ معالج مستوى Perl من %SIG. يسمح المخطط "المؤجل" بأكثر من ذلك بكثير
المرونة في تشفير معالجات الإشارة كما نعلم أن مترجم بيرل في مكان آمن
الحالة، وأننا لسنا في وظيفة مكتبة النظام عند استدعاء المعالج.
ومع ذلك، فإن التنفيذ يختلف عن لغة Perls السابقة بالطرق التالية:

أكواد التشغيل طويلة الأمد
نظرًا لأن مترجم Perl ينظر إلى إشارات الإشارة فقط عندما يكون على وشك تنفيذ إشارة جديدة
كود التشغيل، إشارة تصل أثناء تشغيل كود التشغيل لفترة طويلة (على سبيل المثال، تعبير عادي
لن تتم رؤية العملية على سلسلة كبيرة جدًا) حتى يكتمل كود التشغيل الحالي.

إذا تم إطلاق إشارة من أي نوع معين عدة مرات أثناء تشغيل كود التشغيل (مثلاً من ملف
مؤقت دقيق الحبيبات)، سيتم استدعاء معالج هذه الإشارة مرة واحدة فقط، بعد
اكتمال كود التشغيل؛ سيتم تجاهل كافة الحالات الأخرى. علاوة على ذلك، إذا كان لديك
يتم غمر قائمة انتظار إشارة النظام إلى درجة وجود إشارات
مرفوع ولكن لم يتم اكتشافه بعد (وبالتالي لم يتم تأجيله) في وقت اكتمال شفرة التشغيل،
قد يتم التقاط هذه الإشارات وتأجيلها خلال أكواد التشغيل اللاحقة
نتائج مفاجئة في بعض الأحيان. على سبيل المثال، قد ترى تنبيهات يتم تسليمها حتى بعد ذلك
دعوة إنذار(0) حيث أن الأخير يوقف رفع الإنذارات لكنه لا يلغيها
تم إطلاق الإنذارات ولكن لم يتم اكتشافها بعد. لا تعتمد على السلوكيات
الموصوفة في هذه الفقرة لأنها آثار جانبية للتنفيذ الحالي و
قد يتغير في الإصدارات المستقبلية من Perl.

مقاطعة الإدخال/الإخراج
عندما يتم تسليم إشارة (على سبيل المثال، SIGINT من عنصر التحكم-C)، ينقطع نظام التشغيل
في عمليات IO مثل اقرأ(2) الذي يستخدم في تنفيذ بيرل قراءة سطر ()
الدالة، عامل التشغيل "<>". في لغة Perls الأقدم، تم استدعاء المعالج على الفور (ومثل
"قراءة" ليست "غير آمنة"، وهذا يعمل بشكل جيد). مع المخطط "المؤجل" يكون المعالج
ليس يتم استدعاؤه على الفور، وإذا كان Perl يستخدم مكتبة "stdio" الخاصة بالنظام، فتلك المكتبة
قد يعيد تشغيل "القراءة" دون العودة إلى Perl لمنحه فرصة لاستدعاء %SIG
معالج. إذا حدث هذا على نظامك، فالحل هو استخدام طبقة ":perlio".
قم بإجراء IO - على الأقل على تلك المقابض التي تريد أن تكون قادرًا على اقتحامها بالإشارات.
(تتحقق الطبقة ":perlio" من إشارات الإشارة وتستدعي معالجات %SIG قبل الاستئناف
عملية الإدخال والإخراج.)

الإعداد الافتراضي في Perl 5.8.0 والإصدارات الأحدث هو استخدام طبقة ":perlio" تلقائيًا.

لاحظ أنه ليس من المستحسن الوصول إلى مقبض الملف داخل معالج الإشارة حيث
لقد قاطعت تلك الإشارة عملية الإدخال/الإخراج على نفس المقبض. بينما سوف بيرل في
على الأقل حاول جاهدًا عدم التعطل، فلا توجد ضمانات لسلامة البيانات؛ على سبيل المثال،
قد يتم إسقاط بعض البيانات أو كتابتها مرتين.

بعض وظائف مكتبة الشبكات مثل gethostbyname () ومن المعروف أن لديهم خاصة بهم
تطبيقات المهلات التي قد تتعارض مع المهلات الخاصة بك. اذا كنت تمتلك
مشاكل مع مثل هذه الوظائف، حاول استخدام POSIX سيجاكشن () وظيفة، والتي تتجاوز
إشارات بيرل الآمنة كن حذرًا من أن هذا يعرضك للذاكرة المحتملة
الفساد كما هو موضح أعلاه.

بدلاً من تعيين $SIG{ALRM}:

local $SIG{ALRM} = sub { die "alarm" };

جرب شيئًا مثل ما يلي:

استخدم POSIX qw(SIGALRM);
POSIX::sigaction(SIGALRM, POSIX::SigAction->new(sub { die "alarm" }))
|| die "خطأ في إعداد معالج SIGALRM: $!\n";

هناك طريقة أخرى لتعطيل سلوك الإشارة الآمنة محليًا وهي استخدام
وحدة "Perl::Unsafe::Signals" من CPAN، والتي تؤثر على جميع الإشارات.

مكالمات النظام القابلة لإعادة التشغيل
في الأنظمة التي تدعمها، استخدمت الإصدارات الأقدم من Perl علامة SA_RESTART عندما
تثبيت معالجات %SIG. وهذا يعني أن مكالمات النظام القابلة لإعادة التشغيل ستستمر
بدلاً من العودة عند وصول الإشارة. من أجل إيصال الإشارات المؤجلة
على الفور، يقوم Perl 5.8.0 والإصدارات الأحدث بذلك ليس استخدم SA_RESTART. وبالتالي، قابلة لإعادة التشغيل
يمكن أن تفشل مكالمات النظام (مع ضبط $! على "EINTR") في الأماكن التي كانت تفعل ذلك سابقًا
لقد نجحت.

تقوم الطبقة الافتراضية ":perlio" بإعادة محاولة "القراءة" و"الكتابة" و"الإغلاق" كما هو موضح أعلاه؛
سيتم دائمًا إعادة محاولة مكالمات "الانتظار" و"الانتظار" المتقطعة.

إشارات باعتبارها "أخطاء"
يتم إنشاء إشارات معينة مثل SEGV وILL وBUS بواسطة عنونة الذاكرة الظاهرية
الأخطاء و"الأخطاء" المشابهة. هذه عادة ما تكون قاتلة: هناك القليل من مستوى بيرل
يمكن للمعالج أن يفعل معهم. لذا يقوم بيرل بتسليمهم على الفور بدلاً من محاولة القيام بذلك
تأجيل لهم.

الإشارات الناتجة عن حالة نظام التشغيل
في بعض أنظمة التشغيل، من المفترض أن تقوم بعض معالجات الإشارة "بفعل شيء ما"
قبل العودة. أحد الأمثلة يمكن أن يكون CHLD أو CLD، مما يشير إلى وجود عملية فرعية
مكتمل. في بعض أنظمة التشغيل، من المتوقع أن "ينتظر" معالج الإشارة
عملية الطفل المكتملة. في مثل هذه الأنظمة لن يعمل نظام الإشارة المؤجلة
تلك الإشارات: لا تفعل "الانتظار". مرة أخرى سيبدو الفشل وكأنه حلقة
سيقوم نظام التشغيل بإعادة إصدار الإشارة نظرًا لوجود طفل مكتمل
العمليات التي لم يتم "انتظارها" بعد.

إذا كنت تريد استعادة سلوك الإشارة القديمة على الرغم من تلف الذاكرة المحتمل، فقم بتعيين
متغير البيئة "PERL_SIGNALS" إلى "غير آمن". ظهرت هذه الميزة لأول مرة في لغة بيرل
5.8.1

عين أنابيب


الأنبوب المسمى (يُشار إليه غالبًا باسم FIFO) هو آلية Unix IPC القديمة للعمليات
التواصل على نفس الجهاز. إنه يعمل تمامًا مثل الأنابيب المجهولة العادية، باستثناء
أن العمليات تلتقي باستخدام اسم ملف ولا يلزم أن تكون مرتبطة.

لإنشاء توجيه إخراج مسمى، استخدم الدالة "POSIX::mkfifo()".

استخدم بوسيكس qw(مكفيفو)؛
mkfifo($path, 0700) || يموت "فشل مسار mkfifo $: $!";

يمكنك أيضًا استخدام أمر Unix مكنود(١) أو في بعض الأنظمة. مكفيفو(1). هذه قد لا
كن في طريقك الطبيعي، بالرغم من ذلك.

# قيمة إرجاع النظام معكوسة، لذا فإن && ليس ||
#
$ENV{PATH} .= ":/ الخ:/usr/etc";
إذا (نظام ("mknod"، $path، "p")
&& النظام("mkfifo"، المسار $))
{
يموت "mk{nod,fifo} $path Failed";
}

يعد fifo مناسبًا عندما تريد ربط عملية بعملية غير مرتبطة بها. عندما انت
افتح fifo، فسيقوم البرنامج بالحظر حتى يكون هناك شيء ما على الطرف الآخر.

على سبيل المثال، لنفترض أنك ترغب في الحصول على .إمضاء يكون الملف عبارة عن أنبوب مسمى يحتوي على ملف
برنامج بيرل على الطرف الآخر. الآن في كل مرة أي برنامج (مثل البريد، قارئ الأخبار،
برنامج الإصبع، وما إلى ذلك) يحاول القراءة من هذا الملف، فإن برنامج القراءة سوف يقرأ الجديد
التوقيع من برنامجك سنستخدم مشغل اختبار ملف فحص الأنابيب، -p، لايجاد
معرفة ما إذا كان أي شخص (أو أي شيء) قد قام بإزالة fifo الخاص بنا عن طريق الخطأ.

chdir(); # اذهب للمنزل
$FIFO الخاص بي = ".signature";

بينما (1) {
ما لم (-p $FIFO) {
إلغاء ربط $FIFO؛ #تجاهل أي فشل، سيتم اكتشافه لاحقًا
تتطلب بوسيكس؛ # تأخير تحميل الوحدة الثقيلة
بوسيكس::mkfifo($FIFO, 0700)
|| يموت "لا يمكن mkfifo $FIFO: $!";
}

# كتل السطر التالي حتى يكون هناك قارئ
مفتوح (FIFO، "> $FIFO") || die "لا يمكن فتح $FIFO: $!";
طباعة FIFO "John Smith (smith\@host.org)\n"، `fortune -s`؛
إغلاق (FIFO) || die "لا يمكن إغلاق $FIFO: $!";
النوم 2؛ # لتجنب الإشارات المكررة
}

باستخدام افتح() For IPC


بيرل الأساسية افتح() يمكن أيضًا استخدام البيان للعملية البينية أحادية الاتجاه
الاتصال إما عن طريق إلحاق أو إضافة رمز توجيه الإخراج إلى الوسيطة الثانية
افتح(). إليك كيفية بدء شيء ما في عملية فرعية تنوي الكتابة إليها:

open(SPOOLER, "| cat -v | lpr -h 2>/dev/null")
|| يموت "لا يمكن أن تفرع: $!";
local $SIG{PIPE} = sub { die "أنبوب التخزين المؤقت مكسور" };
طباعة التخزين المؤقت "الأشياء\n";
إغلاق التخزين المؤقت || يموت "التخزين المؤقت السيئ: $! $؟";

وإليك كيفية بدء عملية فرعية تنوي القراءة منها:

مفتوح (الحالة، "netstat -an 2>&1 |")
|| يموت "لا يمكن أن تفرع: $!";
بينما ( ) {
التالي إذا /^(tcp|udp)/;
طباعة.
}
حالة الإغلاق || يموت "netstat سيئة: $! $؟";

إذا كان من الممكن التأكد من أن برنامجًا معينًا هو برنامج Perl النصي الذي يتوقع أسماء الملفات فيه
@ARGV، يمكن للمبرمج الذكي أن يكتب شيئًا كهذا:

% برنامج f1 "cmd1|" - f2 "cmd2|" f3 <ملف تمب

وبغض النظر عن نوع الصدفة التي يتم الاتصال منها، فإن برنامج Perl سيقرأ من ملف
ملف f1، العملية cmd1، الإدخال القياسي (com.tmpfile في هذه الحالة)، f2 ملف cmd2
الأمر، وأخيرا f3 ملف. أنيق جداً، أليس كذلك؟

قد تلاحظ أنه يمكنك استخدام العلامات الخلفية للحصول على نفس تأثير فتح الأنبوب
للقراءة:

طباعة grep { !/^(tcp|udp)/ } `netstat -an 2>&1`;
تموت "حالة netstatus سيئة ($؟)" إذا $؟؛

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

كن حذرًا للتحقق من قيم الإرجاع من كليهما افتح() أغلق(). إذا كنت جاري الكتابة إلى
أنبوبًا، يجب عليك أيضًا احتجاز SIGPIPE. بخلاف ذلك، فكر في ما يحدث عند بدء التشغيل
توجيه إخراج إلى أمر غير موجود: the افتح() سوف تنجح في جميع الاحتمالات (فقط
يعكس فرع()'s النجاح)، ولكن بعد ذلك سوف تفشل مخرجاتك - بشكل مذهل. بيرل لا يستطيع ذلك
معرفة ما إذا كان الأمر يعمل، لأن الأمر الخاص بك يعمل بالفعل بشكل منفصل
العملية التي إكسيك () ربما فشلت. ولذلك، في حين يعود قراء الأوامر الزائفة
مجرد EOF سريع، سيتم ضرب كاتبي الأوامر الزائفة بإشارة، وهو ما يفضلونه
كن مستعدًا للتعامل. يعتبر:

open(FH, "|زائف") || يموت "لا يمكن أن تفرع: $!";
طباعة FH "bang\n"; # ليست ضرورية ولا كافية
# للتحقق من إعادة طباعة الطباعة!
إغلاق(FH) || يموت "لا يمكن إغلاق: $!";

سبب عدم التحقق من القيمة المرتجعة من طباعة() بسبب التخزين المؤقت للأنابيب.
تأخرت عمليات الكتابة الفعلية. وهذا لن ينفجر حتى الإغلاق، وسوف ينفجر مع
سيجيبي. للقبض عليه، يمكنك استخدام هذا:

$SIG{PIPE} = "تجاهل";
open(FH, "|زائف") || يموت "لا يمكن أن تفرع: $!";
طباعة FH "bang\n";
إغلاق(FH) || يموت "لا يمكن الإغلاق: الحالة=$؟";

مقابض الملفات
تشترك كل من العملية الرئيسية وأي عمليات فرعية في نفس STDIN وSTDOUT و
مقابض ملفات STDERR. إذا حاولت كلتا العمليتين الوصول إليهما في وقت واحد، فقد يحدث أشياء غريبة
يحدث. قد ترغب أيضًا في إغلاق أو إعادة فتح معالجات الملفات الخاصة بالطفل. يمكنك الحصول
حول هذا عن طريق فتح الأنبوب الخاص بك مع افتح()، ولكن في بعض الأنظمة يعني هذا أن
لا يمكن لعملية الطفل أن تعمر بعد الوالد.

خلفيّة العمليات
يمكنك تشغيل أمر في الخلفية باستخدام:

نظام("كمد &");

سيكون الأمر STDOUT و STDERR (وربما STDIN، اعتمادًا على الصدفة الخاصة بك) هو
نفس الوالدين. لن تحتاج إلى الإمساك بـ SIGCHLD بسبب أخذ الشوكة المزدوجة
مكان؛ انظر أدناه للحصول على التفاصيل.

تنفيذ تفكك of طفل تبدأ من العنصر الرئيسي
في بعض الحالات (بدء عمليات الخادم، على سبيل المثال) سوف ترغب في ذلك تمامًا
فصل عملية الطفل عن الوالد. وهذا ما يسمى في كثير من الأحيان الشيطان. أ
الشيطان حسن التصرف سوف يفعل ذلك أيضًا شدير () إلى الدليل الجذر لذلك لا يمنع
إلغاء تحميل نظام الملفات الذي يحتوي على الدليل الذي تم تشغيله منه، و
إعادة توجيه واصفات الملفات القياسية الخاصة به من وإلى / ديف / لاغية بحيث لا يحدث الإخراج العشوائي
ينتهي الأمر على محطة المستخدم.

استخدم POSIX "setsid"؛

شيطان فرعي {
chdir("/") || يموت "لا يمكن توجيه إلى /: $!";
open(STDIN, "< /dev/null") || يموت "لا يمكن قراءة /dev/null: $!";
open(STDOUT, "> /dev/null") || يموت "لا يمكن الكتابة إلى /dev/null: $!";
محدد(my $pid = fork()) || يموت "لا يمكن أن تفرع: $!";
الخروج إذا $pid؛ # غير الصفر يعني الآن أنني الوالد
(setsid() != -1) || die "لا يمكن بدء جلسة جديدة: $!";
open(STDERR, ">&STDOUT") || يموت "لا يمكن مضاعفة stdout: $!";
}

تشير فرع() يجب أن يأتي قبل مجموعة معرف () للتأكد من أنك لست قائد مجموعة العمليات؛
هيه مجموعة معرف () سوف تفشل إذا كنت. إذا كان نظامك لا يحتوي على مجموعة معرف () وظيفة،
جاكيت / ديف / tty واستخدم "TIOCNOTTY" ioctl () عليه بدلا من ذلك. يرى الكتابة البعيدة(4) لمزيد من التفاصيل.

يجب على المستخدمين غير العاملين بنظام Unix التحقق من "نظام التشغيل الخاص بك::العملية" وحدة للحلول الممكنة الأخرى.

آمن أنبوب يفتح
هناك طريقة أخرى مثيرة للاهتمام لـ IPC وهي جعل برنامجك الفردي يعمل بعمليات متعددة و
التواصل بين - أو حتى بين - أنفسكم. ال افتح() ستقبل الوظيفة ملفًا
وسيطة إما "-|" أو "|-" لفعل شيء مثير جدًا للاهتمام: فهو يتفرع من اتصال الطفل
إلى مقبض الملف الذي قمت بفتحه. يقوم الطفل بتشغيل نفس البرنامج الذي يستخدمه الوالد.
يعد هذا مفيدًا لفتح ملف بأمان عند تشغيله تحت معرف UID أو GID مفترض
مثال. إذا قمت بفتح الأنابيب إلى ناقصًا، يمكنك الكتابة إلى مقبض الملف الذي فتحته وملف
سوف يجدها الطفل في له ستدين. إذا قمت بفتح الأنابيب تبدأ من ناقص، يمكنك أن تقرأ من
filehandle الذي قمت بفتحه لكل ما يكتبه طفلك له خروج قياسي.

استخدم الإنجليزية؛
$PRECIOUS = "/path/to/some/safe/file";
حسابي $sleep_count؛
معرف $ الخاص بي;

افعل
$pid = open(KID_TO_WRITE, "|-");
ما لم (تم تعريف $pid) {
تحذير "لا يمكن تفرع: $!";
يموت "الإنقاذ" إذا كان $sleep_count++ > 6؛
النوم 10؛
}
} حتى يتم تعريف $pid;

إذا ($pid) { # أنا الوالد
طباعة KID_TO_WRITE @some_data؛
إغلاق(KID_TO_WRITE) || تحذير "لقد خرج الطفل $؟";
} else { # أنا الطفل
# إسقاط الأذونات في برامج setuid و/أو setgid:
($EUID، $EGID) = ($UID، $GID)؛
مفتوح (الملف الخارجي، "> $PRECIOUS")
|| يموت "لا يمكن فتح $PRECIOUS: $!";
بينما ( ) {
طباعة الملف؛ # STDIN الخاص بالطفل هو KID_TO_WRITE الخاص بالوالدين
}
إغلاق (خارج) || die "لا يمكن إغلاق $PRECIOUS: $!";
خروج(0)؛ #لا تنسى هذا !!
}

الاستخدام الشائع الآخر لهذا البناء هو عندما تحتاج إلى تنفيذ شيء ما بدون
تدخل قذيفة. مع النظام()، إنه أمر بسيط، لكن لا يمكنك استخدام أنبوب مفتوح
أو backticks بأمان. ذلك لأنه لا توجد طريقة لمنع الصدفة من الحصول عليها
أيدي على الحجج الخاصة بك. بدلاً من ذلك، استخدم عنصر تحكم المستوى الأدنى للاتصال إكسيك () مباشرة.

إليك علامة خلفية آمنة أو أنبوبًا مفتوحًا للقراءة:

معرف $ الخاص بي = open(KID_TO_READ, "-|");
محدد($pid) || يموت "لا يمكن أن تفرع: $!";

إذا ($pid) {#parent
بينما ( ) {
#افعل شيئًا مثيرًا للاهتمام
}
إغلاق(KID_TO_READ) || تحذير "لقد خرج الطفل $؟";

} آخر { # طفل
($EUID، $EGID) = ($UID، $GID)؛ #سويد فقط
exec($program, @options, @args)
|| يموت "لا يمكن تنفيذ البرنامج: $!";
# لم تصل
}

وهنا أنبوب آمن مفتوح للكتابة:

$pid = open(KID_TO_WRITE, "|-");
محدد($pid) || يموت "لا يمكن أن تفرع: $!";

$SIG{PIPE} = sub { die "عفوًا، تعطل أنبوب البرنامج $" };

إذا ($pid) {#parent
طباعة KID_TO_WRITE @data؛
إغلاق(KID_TO_WRITE) || تحذير "لقد خرج الطفل $؟";

} آخر { # طفل
($EUID، $EGID) = ($UID، $GID)؛
exec($program, @options, @args)
|| يموت "لا يمكن تنفيذ البرنامج: $!";
# لم تصل
}

من السهل جدًا قفل العملية باستخدام هذا النوع من افتح()، أو في الواقع مع أي فائدة
of يضخ() مع عمليات فرعية متعددة. المثال أعلاه "آمن" لأنه بسيط
والمكالمات إكسيك (). راجع "تجنب انسداد الأنابيب" للتعرف على مبادئ السلامة العامة، ولكن هناك
هناك المزيد من المشاكل مع Safe Pipe Opens.

على وجه الخصوص، إذا قمت بفتح الأنبوب باستخدام "open FH, "|-""، فلا يمكنك استخدامه ببساطة
أغلق() في العملية الأم لإغلاق كاتب غير مرغوب فيه. خذ بعين الاعتبار هذا الرمز:

$pid = open(WRITER, "|-"); #الشوكة تفتح طفلاً
محدد($pid) || يموت "فشل الشوكة الأولى: $!";
إذا ($معرف المنتج) {
إذا (بلدي $sub_pid = fork()) {
محدد($sub_pid) || يموت "فشل الشوكة الثانية: $!";
إغلاق(الكاتب) || يموت "تعذر إغلاق الكاتب: $!";
#الآن افعل شيئاً آخر...
}
آخر {
# اكتب أولاً إلى WRITER
#...
# ثم عند الانتهاء
إغلاق(الكاتب) || يموت "تعذر إغلاق الكاتب: $!";
خروج(0)
}
}
آخر {
# أولاً قم بشيء ما باستخدام STDIN، ثم
خروج(0)
}

في المثال أعلاه، لا يريد الأصل الحقيقي الكتابة إلى معالج ملف WRITER، لذلك
يغلقه. ومع ذلك، لأنه تم فتح WRITER باستخدام "open FH، "|-""، فإنه يحتوي على خاص
السلوك: إغلاقه يدعو waitpid () (انظر "waitpid" في perlfunc)، والذي ينتظر
عملية فرعية للخروج. إذا انتهت العملية الفرعية بانتظار حدوث شيء ما في
القسم الذي يحمل علامة "افعل شيئًا آخر"، لديك طريق مسدود.

يمكن أن يكون هذا أيضًا مشكلة في العمليات الفرعية الوسيطة في التعليمات البرمجية الأكثر تعقيدًا، والتي
سوف يتصل waitpid () على جميع مقابض الملفات المفتوحة أثناء التدمير العالمي - لا يمكن التنبؤ به
النظام.

لحل هذه المشكلة، يجب عليك استخدام يدويا يضخ(), فرع()، وشكل افتح() الذي يحدد واحد
واصف ملف إلى آخر، كما هو موضح أدناه:

أنبوب (قارئ، كاتب) || يموت "فشل الأنبوب: $!";
$pid = fork();
محدد($pid) || يموت "فشل الشوكة الأولى: $!";
إذا ($معرف المنتج) {
إغلاق القارئ؛
إذا (بلدي $sub_pid = fork()) {
محدد($sub_pid) || يموت "فشل الشوكة الأولى: $!";
إغلاق(الكاتب) || يموت "لا يمكن إغلاق الكاتب: $!";
}
آخر {
#اكتب للكاتب...
#...
# ثم عند الانتهاء
إغلاق(الكاتب) || يموت "لا يمكن إغلاق الكاتب: $!";
خروج(0)
}
#اكتب للكاتب...
}
آخر {
open(STDIN, "<&READER") || die "لا يمكن إعادة فتح STDIN: $!";
إغلاق(الكاتب) || يموت "لا يمكن إغلاق الكاتب: $!";
# قم بعمل ما...
خروج(0)
}

منذ الإصدار 5.8.0 من Perl، يمكنك أيضًا استخدام نموذج القائمة "مفتوح" للأنابيب. هذا هو المفضل
عندما ترغب في تجنب قيام الصدفة بتفسير الأحرف الأولية التي قد تكون موجودة في ملفك
سلسلة الأمر.

لذلك على سبيل المثال، بدلاً من استخدام:

open(PS_PIPE, "ps aux|") || يموت "لا يمكن فتح أنبوب ps: $!";

يمكن للمرء استخدام أي من هذه:

فتح (PS_PIPE، "-|"، "ps"، "aux")
|| يموت "لا يمكن فتح أنبوب ps: $!";

@ps_args = qw[ps aux];
مفتوح (PS_PIPE، "-|"، @ps_args)
|| يموت "لا يمكن فتح @ps_args|: $!";

لأن هناك أكثر من ثلاث حجج لذلك افتح()، شوك ps(1) الأمر بدون
ينتج غلافًا، ويقرأ مخرجاته القياسية عبر مقبض الملف "PS_PIPE". ال
بناء الجملة المقابل ل اكتب لتوجيه توجيهات الإخراج هو استخدام "|-" بدلاً من "-|".

من المسلم به أن هذا كان مثالًا سخيفًا إلى حد ما، لأنك تستخدم سلسلة حرفية
المحتوى آمن تمامًا. ولذلك لا يوجد سبب للجوء إلى ما هو أصعب في القراءة،
شكل متعدد الوسائط من الأنابيب افتح(). ومع ذلك، عندما لا يمكنك التأكد من أن
وسيطات البرنامج خالية من الأحرف الأولية لـ Shell، وهو الشكل الأكثر روعةً لـ افتح() ينبغي أن تكون
مستخدم. على سبيل المثال:

@grep_args = ("egrep"، "-i"، $some_pattern، @many_files)؛
مفتوح (GREP_PIPE، "-|"، @grep_args)
|| die "لا يمكن فتح @grep_args|: $!";

هنا شكل الأنبوب متعدد الوسائط افتح() ويفضل لأن النمط والواقع
حتى أسماء الملفات نفسها قد تحتوي على أحرف أولية.

انتبه إلى أن هذه العمليات هي شوكات يونكس كاملة، مما يعني أنها قد لا تكون صحيحة
يتم تنفيذها على جميع الأنظمة الغريبة.

تجنب أنبوب جمود
عندما يكون لديك أكثر من عملية فرعية واحدة، يجب أن تكون حريصًا على إغلاق كل واحدة منها
نصف أي أنابيب تم إنشاؤها للاتصال بين العمليات لا تستخدمها. هذا بسبب
أي عملية فرعية تقرأ من الأنبوب وتتوقع EOF لن تتلقى ذلك أبدًا، و
لذلك لا تخرج أبدًا. عملية واحدة لإغلاق الأنبوب لا تكفي لإغلاقه؛ الاخير
العملية مع فتح الأنبوب يجب أن تغلقه حتى يقرأ EOF.

تساعد بعض ميزات Unix المضمنة في منع ذلك في معظم الأوقات. على سبيل المثال،
تحتوي معالجات الملفات على علامة "إغلاق عند exec"، والتي تم تعيينها en في الكتلة تحت سيطرة $^F
عامل. هذا يعني أن أي معالجات ملفات لم تقم بتوجيهها بشكل صريح إلى STDIN أو STDOUT أو
STDERR للطفل برنامج سيتم إغلاقه تلقائيا.

اتصل دائمًا بشكل صريح وفوري أغلق() على الطرف القابل للكتابة لأي أنبوب، إلا إذا
هذه العملية هي في الواقع الكتابة إليها. حتى لو لم تتصل صراحة أغلق()بيرل
سوف يبقي أغلق() كافة مقابض الملفات أثناء التدمير العالمي. كما نوقش سابقا، إذا
تم فتح معالجات الملفات هذه باستخدام Safe Pipe Open، وسيؤدي ذلك إلى الاتصال
waitpid ()، الأمر الذي قد يصل إلى طريق مسدود مرة أخرى.

ثنائي الاتجاه التواصل مع آخر طريقة عملنا
في حين أن هذا يعمل بشكل جيد إلى حد معقول للاتصال أحادي الاتجاه، فماذا عن؟
الاتصالات ثنائية الاتجاه؟ النهج الأكثر وضوحًا لا يعمل:

# هذا لا يعمل!!
مفتوح (PROG_FOR_READING_AND_WRITING، "| بعض البرامج |")

إذا نسيت "استخدام التحذيرات"، فسوف تفوتك فرصة التشخيص المفيد تمامًا
رسالة:

لا يمكن عمل أنبوب ثنائي الاتجاه عند السطر -e 1.

إذا كنت تريد حقا، يمكنك استخدام المعيار open2 () من الوحدة النمطية "IPC::Open2" إلى
قبض على كلا الطرفين. هناك أيضًا open3 () في "IPC::Open3" للإدخال/الإخراج ثلاثي الاتجاهات، لذلك أنت
يمكن أيضًا التقاط STDERR الخاص بطفلك، ولكن القيام بذلك قد يتطلب الأمر أمرًا محرجًا تحديد()
Loop ولن يسمح لك باستخدام عمليات إدخال Perl العادية.

إذا نظرت إلى مصدره، سترى ذلك open2 () يستخدم البدائيات ذات المستوى المنخفض مثل
يضخ() إكسيك () syscalls لإنشاء كافة الاتصالات. على الرغم من أنه ربما كان كذلك
أكثر كفاءة باستخدام socketpair ()، لكان هذا أقل قابلية للنقل منه
هو بالفعل. ال open2 () open3 () من غير المرجح أن تعمل الوظائف في أي مكان باستثناء أ
نظام Unix، أو على الأقل امتثال واحد مزعوم لـ POSIX.

وهنا مثال على استخدام open2 ():

استخدام FileHandle ؛
استخدم IPC::Open2;
$pid = open2(*Reader, *Writer, "cat -un");
طباعة الكاتب "الأشياء\n";
$ حصلت = ;

المشكلة في هذا هي أن التخزين المؤقت سوف يدمر يومك حقًا. بالرغم من
يتم مسح مقبض الملف "الكاتب" الخاص بك تلقائيًا حتى تتمكن العملية على الطرف الآخر من إدخال بياناتك
في الوقت المناسب، لا يمكنك عادةً فعل أي شيء لإجبار هذه العملية على تقديم بياناتها إليها
لك بطريقة سريعة بالمثل. في هذه الحالة الخاصة، يمكننا فعل ذلك، لأننا
أعطى قط a -u العلم لجعله غير مخزنة. لكن عددًا قليلًا جدًا من الأوامر مُصمم للعمل
عبر الأنابيب، لذلك نادرًا ما يعمل هذا إلا إذا قمت أنت بنفسك بكتابة البرنامج على الطرف الآخر من
الأنبوب ذو النهاية المزدوجة.

الحل لذلك هو استخدام مكتبة تستخدم ملفات زائفة لجعل برنامجك يتصرف
أكثر معقولية. بهذه الطريقة لا يتعين عليك التحكم في الكود المصدري للملف
البرنامج الذي تستخدمه. تعالج وحدة "التوقع" من CPAN أيضًا هذا النوع من الأشياء.
تتطلب هذه الوحدة وحدتين أخريين من CPAN، "IO::Pty" و"IO::Stty". يقوم بإعداد أ
محطة زائفة للتفاعل مع البرامج التي تصر على التحدث إلى الجهاز الطرفي
سائق. إذا كان نظامك مدعومًا، فقد يكون هذا هو أفضل رهان لك.

ثنائي الاتجاه التواصل مع نفسك
إذا كنت تريد، يمكنك جعل مستوى منخفض يضخ() فرع() syscalls لربط هذا معًا
يُسلِّم. هذا المثال يتحدث مع نفسه فقط، ولكن يمكنك إعادة فتح المقابض المناسبة له
STDIN وSTDOUT واستدعاء العمليات الأخرى. (المثال التالي يفتقر إلى الخطأ المناسب
تدقيق.)

#!/ البيرة / بن / بيرل -w
# Pipe1 - اتصال ثنائي الاتجاه باستخدام زوجين من الأنابيب
# مصممة لتحدي زوج المقبس
استخدم IO::Handle; # آلاف الخطوط فقط للتدفق التلقائي :-(
أنبوب (PARENT_RDR، CHILD_WTR)؛ #XXX: فشل التحقق؟
أنبوب (CHILD_RDR، PARENT_WTR)؛ #XXX: فشل التحقق؟
CHILD_WTR->التنظيف التلقائي(1)
PARENT_WTR->التنظيف التلقائي(1)

إذا ($pid = fork()) {
أغلق PARENT_RDR؛
أغلق PARENT_WTR؛
طباعة CHILD_WTR "معرف الأصل $$ يرسل هذا\n";
أقضم بصوت عالي($line = );
طباعة "معرف الأصل $$ اقرأ هذا للتو: '$line'\n";
أغلق CHILD_RDR؛ أغلق CHILD_WTR؛
waitpid($pid, 0);
{} آخر
يموت "لا يمكن تفرع: $!" ما لم يتم تعريف $pid؛
أغلق CHILD_RDR؛
أغلق CHILD_WTR؛
أقضم بصوت عالي($line = );
طباعة "معرف الطفل $$ اقرأ هذا للتو: '$line'\n";
طباعة PARENT_WTR "معرف الطفل $$ يرسل هذا\n";
أغلق PARENT_RDR؛
أغلق PARENT_WTR؛
خروج(0)
}

لكن ليس عليك في الواقع إجراء مكالمتين مباشرتين. إذا كان لديك socketpair () نظام
اتصل، وسوف تفعل كل هذا بالنسبة لك.

#!/ البيرة / بن / بيرل -w
# Pipe2 - اتصال ثنائي الاتجاه باستخدام المقبس
#"الأفضل دائمًا يذهب في الاتجاهين"

استخدام المقبس
استخدم IO::Handle; # آلاف الخطوط فقط للتدفق التلقائي :-(

# نقول AF_UNIX لأنه على الرغم من أن *_LOCAL هو
#POSIX 1003.1g شكل ثابت، العديد من الآلات
# لا يزال لا يملك ذلك.
زوج المقبس (CHILD، PARENT، AF_UNIX، SOCK_STREAM، PF_UNSPEC)
|| يموت "زوج المقبس: $!";

الطفل->التنظيف التلقائي(1)
الوالد->التنظيف التلقائي(1)

إذا ($pid = fork()) {
إغلاق الوالد؛
طباعة CHILD "معرف الوالد $$ يرسل هذا\n";
أقضم بصوت عالي($line = );
طباعة "معرف الأصل $$ اقرأ هذا للتو: '$line'\n";
طفل قريب؛
waitpid($pid, 0);
{} آخر
يموت "لا يمكن تفرع: $!" ما لم يتم تعريف $pid؛
طفل قريب؛
أقضم بصوت عالي($line = );
طباعة "معرف الطفل $$ اقرأ هذا للتو: '$line'\n";
print PARENT "الطفل Pid $$ يرسل هذا\n";
إغلاق الوالد؛
خروج(0)
}

مآخذ: العميل / الخادم التواصل


على الرغم من أنه لا يقتصر تمامًا على أنظمة التشغيل المشتقة من Unix (مثل WinSock على أجهزة الكمبيوتر الشخصية).
يوفر دعم مأخذ التوصيل، كما هو الحال مع بعض مكتبات VMS)، قد لا يكون لديك مآخذ توصيل على جهازك
النظام، وفي هذه الحالة ربما لن يفيدك هذا القسم كثيرًا. مع
مآخذ التوصيل، يمكنك القيام بكل من الدوائر الافتراضية مثل تدفقات TCP ومخططات البيانات مثل حزم UDP.
قد تكون قادرًا على القيام بالمزيد اعتمادًا على نظامك.

وظائف Perl للتعامل مع المقابس لها نفس الأسماء المقابلة لها
استدعاءات النظام في لغة C، لكن وسيطاتها تميل إلى الاختلاف لسببين. أولا، بيرل
تعمل معالجات الملفات بشكل مختلف عن واصفات ملفات C. ثانيا، بيرل يعرف بالفعل
طول سلاسلها، لذلك لا تحتاج إلى تمرير تلك المعلومات.

كانت إحدى المشاكل الرئيسية في كود المقبس القديم الذي يعود إلى ما قبل الألفية في لغة بيرل هي ذلك
استخدمت قيمًا مضمنة لبعض الثوابت، مما أضر بشدة بإمكانية النقل. اذا أنت
من أي وقت مضى رأيت رمزًا يفعل أي شيء مثل الإعداد الصريح لـ "$AF_INET = 2"، فأنت تعلم أنك كذلك
في مشكلة كبيرة. النهج المتفوق بما لا يقاس هو استخدام وحدة "المقبس"،
والذي يتيح الوصول بشكل أكثر موثوقية إلى الثوابت والوظائف المختلفة التي ستحتاج إليها.

إذا كنت لا تكتب خادمًا/عميلًا لبروتوكول موجود مثل NNTP أو SMTP، فأنت
يجب أن تفكر قليلاً في كيفية معرفة الخادم الخاص بك عند انتهاء العميل
الحديث، والعكس صحيح. تعتمد معظم البروتوكولات على رسائل واستجابات من سطر واحد (وبالتالي
يعرف أحد الطرفين أن الطرف الآخر قد انتهى عند تلقي "\n") أو رسائل متعددة الأسطر و
الاستجابات التي تنتهي بنقطة في سطر فارغ ("\n.\n" ينهي رسالة/استجابة).

Internet خط الإنهاء
نهاية خط الإنترنت هو "\015\012". في ظل متغيرات ASCII لنظام Unix، يمكن ذلك
عادةً ما تتم كتابته كـ "\r\n"، ولكن في الأنظمة الأخرى، قد يتم كتابة "\r\n" في بعض الأحيان
"\015\015\012"، "\012\012\015"، أو شيء مختلف تمامًا. تحدد المعايير
كتابة "\015\012" لتكون متوافقة (كن صارمًا فيما تقدمه)، لكنهم أيضًا
نوصي بقبول "\012" وحيد عند الإدخال (كن متساهلاً فيما تطلبه). لم نفعل ذلك
لقد كنت دائمًا جيدًا جدًا بشأن ذلك في التعليمات البرمجية الموجودة في هذه الصفحة، ولكن إلا إذا كنت تستخدم جهاز Mac
من طريق العودة إلى العصور المظلمة التي سبقت يونكس، من المحتمل أن تكون على ما يرام.

Internet TCP العملاء خوادم
استخدم مآخذ توصيل مجال الإنترنت عندما تريد إجراء اتصال بخادم العميل
تمتد إلى الأجهزة خارج النظام الخاص بك.

إليك نموذج عميل TCP يستخدم مآخذ توصيل مجال الإنترنت:

#!/ البيرة / بن / بيرل -w
استخدام صارم
استخدام المقبس
بلدي ($remote، $port، $iaddr، $paddr، $proto، $line)؛

$ عن بعد = التحول || "المضيف المحلي";
منفذ $ = التحول || 2345؛ #منفذ عشوائي
إذا ($port =~ /\D/) { $port = getservbyname($port, "tcp") }
يموت "لا يوجد منفذ" إلا إذا كان $port؛
$iaddr = inet_aton($remote) || يموت "لا يوجد مضيف: $remote";
$paddr = sockaddr_in($port, $iaddr);

$proto = getprotobyname("tcp");
المقبس (SOCK، PF_INET، SOCK_STREAM، $proto) || يموت "المقبس: $!";
ربط (SOCK، $paddr) || يموت "الاتصال: $!";
بينما ($ السطر = ) {
طباعة سطر $؛
}

إغلاق (جورب) || يموت "إغلاق: $!";
خروج(0)

وهنا الخادم المقابل الذي يتوافق معه. سنترك العنوان كما هو
"INADDR_ANY" حتى تتمكن النواة من اختيار الواجهة المناسبة على الأجهزة المضيفة متعددة طرق الاتصال.
إذا كنت تريد الجلوس على واجهة معينة (مثل الجانب الخارجي للبوابة أو جدار الحماية
الجهاز)، املأ هذا بعنوانك الحقيقي بدلاً من ذلك.

#!/ البيرة / بن / بيرل -اثنين
استخدام صارم
البدء {$ENV{PATH} = "/ البيرة / بن:/ بن" }
استخدام المقبس
استخدام الكارب
$EOL الخاص بي = "\015\012";

logmsg الفرعية { طباعة "$0 $$: @_ at "، العددية localtime()، "\n" }

منفذ $ الخاص بي = Shift || 2345؛
يموت "منفذ غير صالح" إلا إذا كان $port =~ /^ \d+ $/x;

بلدي $proto = getprotobyname("tcp");

المقبس (الخادم، PF_INET، SOCK_STREAM، $proto) || يموت "المقبس: $!";
مجموعات سوكوبت (الخادم، SOL_SOCKET، SO_REUSEADDR، حزمة ("l"، 1))
|| يموت "setsockopt: $!";
bind(Server, sockaddr_in($port, INADDR_ANY)) || يموت "الربط: $!";
استمع (الخادم، SOMAXCONN) || يموت "استمع: $!";

logmsg "بدأ الخادم على المنفذ $port"؛

بلدي $paddr;

$SIG{CHLD} = \&REAPER;

for ( ; $paddr = Accept(Client, Server); Close Client) {
my($port, $iaddr) = sockaddr_in($paddr);
اسمي $ = gethostbyaddr($iaddr, AF_INET);

logmsg "اتصال من $name ["،
inet_ntoa($iaddr), "]
في المنفذ $port";

عميل الطباعة "مرحبًا، $name، إنه الآن"،
العددية المحلية ()، $EOL؛
}

وهنا نسخة متعددة المهام. إنها متعددة المهام مثل معظم الخوادم النموذجية
تفرخ (فرع()ق) خادم تابع للتعامل مع طلب العميل حتى يتمكن الخادم الرئيسي من ذلك
العودة بسرعة لخدمة عميل جديد.

#!/ البيرة / بن / بيرل -اثنين
استخدام صارم
البدء {$ENV{PATH} = "/ البيرة / بن:/ بن" }
استخدام المقبس
استخدام الكارب
$EOL الخاص بي = "\015\012";

تفرخ الفرعية. #إعلان للأمام
logmsg الفرعية { طباعة "$0 $$: @_ at "، العددية localtime()، "\n" }

منفذ $ الخاص بي = Shift || 2345؛
يموت "منفذ غير صالح" إلا إذا $port =~ /^ \d+ $/x;

بلدي $proto = getprotobyname("tcp");

المقبس (الخادم، PF_INET، SOCK_STREAM، $proto) || يموت "المقبس: $!";
مجموعات سوكوبت (الخادم، SOL_SOCKET، SO_REUSEADDR، حزمة ("l"، 1))
|| يموت "setsockopt: $!";
bind(Server, sockaddr_in($port, INADDR_ANY)) || يموت "الربط: $!";
استمع (الخادم، SOMAXCONN) || يموت "استمع: $!";

logmsg "بدأ الخادم على المنفذ $port"؛

$waitedpid الخاص بي = 0;
بلدي $paddr;

استخدم POSIX ":sys_wait_h";
استخدم إرنو؛

ريبر الفرعية {
محلي $!; # لا تدع waitpid() يحل محل الخطأ الحالي
بينما ((my $pid = waitpid(-1, WNOHANG)) > 0 && WIFEXITED($?)) {
logmsg "حصد $waitedpid" . ($? ? " مع الخروج $؟" : "");
}
$SIG{CHLD} = \&REAPER; # أكره SysV
}

$SIG{CHLD} = \&REAPER;

بينما (1) {
$paddr = قبول(العميل، الخادم) || يفعل {
# حاول مرة أخرى إذا عاد قبول () لأنه حصل على إشارة
التالي إذا $!{EINTR};
يموت "قبول: $!";
};
بلدي ($port, $iaddr) = sockaddr_in($paddr);
اسمي $ = gethostbyaddr($iaddr, AF_INET);

logmsg "اتصال من $name ["،
inet_ntoa($iaddr),
"] عند المنفذ $port";

تفرخ الفرعية {
$ | = 1 ؛
طباعة "مرحبًا، $name، إنه الآن "، scalar localtime(), $EOL;
exec "/usr/games/fortune" # XXX: أدوات إنهاء السطر "الخاطئة".
أو اعترف "لا أستطيع تنفيذ الثروة: $!";
};
إغلاق العميل؛
}

تفرخ فرعي {
بلدي $coderef = التحول؛

ما لم (@_ == 0 && $coderef && ref($coderef) مكافئ "CODE") {
الاعتراف بـ "الاستخدام: Spawn CODEREF"؛
}

معرف $ الخاص بي;
ما لم (محدد($pid = fork())) {
logmsg "لا يمكن التفرع: $!";
العودة؛
}
إلسيف ($معرف المنتج) {
logmsg "begat $pid";
يعود؛ #أنا الوالد
}
#وإلا فأنا الطفل - اذهب إلى تفرخ

open(STDIN, "<&Client") || يموت "لا يمكن خداع العميل إلى stdin"؛
open(STDOUT, ">&Client") || يموت "لا يمكن خداع العميل إلى stdout" ؛
## open(STDERR, ">&STDOUT") || يموت "لا يمكن أن يخدع stdout إلى stderr"؛
خروج($coderef->());
}

يتحمل هذا الخادم مشكلة استنساخ نسخة فرعية عبر فرع() لكل وارد
طلب. وبهذه الطريقة يمكنه التعامل مع العديد من الطلبات في وقت واحد، وهو ما قد لا تريده دائمًا.
حتى لو لم تفعل ذلك فرع()أطلقت حملة استمع () سيسمح بالعديد من الاتصالات المعلقة. شوكة
يجب أن تكون الخوادم حذرة بشكل خاص بشأن تنظيف أطفالهم الموتى (يُطلق عليهم
"zombies" بلغة Unix)، وإلا فسوف تملأ جدول العمليات بسرعة.
يُستخدم الروتين الفرعي REAPER هنا للاتصال waitpid () لأية عمليات تابعة لها
انتهى، وبالتالي ضمان إنهاء أعمالهم بشكل نظيف وعدم انضمامهم إلى صفوف
الحي الميت.

داخل حلقة while نسميها قبول () وتحقق لمعرفة ما إذا كانت تُرجع قيمة خاطئة. هذا
يشير عادةً إلى ضرورة الإبلاغ عن خطأ في النظام. ومع ذلك، فإن مقدمة
الإشارات الآمنة (انظر "الإشارات المؤجلة (الإشارات الآمنة)" أعلاه) في Perl 5.8.0 تعني ذلك
قبول () قد تتم مقاطعته أيضًا عندما تتلقى العملية إشارة. هذا عادة
يحدث عندما تخرج إحدى العمليات الفرعية المتشعبة وتخطر العملية الأصلية برسالة
إشارة CHLD.

If قبول () تمت مقاطعته بإشارة $! سيتم ضبطه على EINTR. إذا حدث هذا، يمكننا ذلك
تابع بأمان إلى التكرار التالي للحلقة واستدعاء آخر لـ قبول (). فمن
من المهم ألا يقوم رمز معالجة الإشارة الخاص بك بتعديل قيمة $!، وإلا سيتم إجراء هذا الاختبار
من المرجح أن تفشل. في روتين REAPER الفرعي نقوم بإنشاء نسخة محلية من $! قبل الاتصال
waitpid (). عندما waitpid () مجموعات $! إلى ECHILD كما تفعل حتماً عندما لا يكون لديها المزيد
الأطفال المنتظرون، يقوم بتحديث النسخة المحلية ويترك الأصل دون تغيير.

يجب عليك استخدام -T علامة لتمكين التحقق من الشوائب (انظر perlsec) حتى لو لم نكن كذلك
تشغيل setuid أو setgid. تعد هذه دائمًا فكرة جيدة للخوادم أو أي برنامج يعمل عليه
نيابة عن شخص آخر (مثل نصوص CGI)، لأنه يقلل من فرص الأشخاص من
سيكون الخارج قادرًا على اختراق نظامك.

دعونا نلقي نظرة على عميل TCP آخر. يتصل هذا بخدمة "الوقت" TCP على رقم
من الأجهزة المختلفة ويظهر مدى اختلاف ساعاتها عن النظام الذي تعمل عليه
يجري تشغيلها:

#!/ البيرة / بن / بيرل -w
استخدام صارم
استخدام المقبس

$SECS_OF_70_YEARS = 2208988800؛
ctime الفرعي { التوقيت المحلي العددي (shift() || الوقت()) }

my $iaddr = gethostbyname("localhost");
بلدي $proto = getprotobyname("tcp");
my $port = getservbyname("time", "tcp");
بلدي $paddr = sockaddr_in(0, $iaddr);
بلدي($المضيف);

$ | = 1 ؛
printf "%-24s %8s %s\n"، "localhost"، 0، ctime();

foreach $host (@ARGV) {
printf "%-24s"، $host;
$hisiaddr = inet_aton($host) || يموت "مضيف غير معروف"؛
بلدي $hispaddr = sockaddr_in($port, $hisiaddr);
المقبس (SOCKET، PF_INET، SOCK_STREAM، $proto)
|| يموت "المقبس: $!";
Connect(SOCKET, $hispaddr) || يموت "الاتصال: $!";
$rtime = pack("C4", ());
قراءة (SOCKET، $rtime، 4)؛
إغلاق(SOCKET);
$histime = unpack("N", $rtime) - $SECS_OF_70_YEARS;
printf "%8d %s\n"، $histime - time(), ctime($histime);
}

مجال يونكس TCP العملاء خوادم
وهذا أمر جيد بالنسبة لعملاء وخوادم مجال الإنترنت، ولكن ماذا عن الاتصالات المحلية؟
على الرغم من أنه يمكنك استخدام نفس الإعداد، إلا أنك في بعض الأحيان لا ترغب في ذلك. مآخذ مجال يونكس هي
محلي للمضيف الحالي، وغالبًا ما يتم استخدامه داخليًا لتنفيذ الأنابيب. على عكس
مآخذ مجال الإنترنت، يمكن أن تظهر مآخذ مجال Unix في نظام الملفات بملحق ls(1)
قائمة.

% ls -l /dev/log
srw-rw-rw- 1 جذر 0 31 أكتوبر 07:23 /dev/log

يمكنك اختبار هذه باستخدام Perl -S اختبار الملف:

ما لم (-S "/dev/log") {
يموت "هناك شيء شرير في نظام السجل" ؛
}

فيما يلي نموذج عميل مجال Unix:

#!/ البيرة / بن / بيرل -w
استخدام المقبس
استخدام صارم
بلدي ($ موعد، خط $)؛

موعد $ = التحول || "كاتسوك"؛
المقبس (SOCK، PF_UNIX، SOCK_STREAM، 0) || يموت "المقبس: $!";
Connect(SOCK, sockaddr_un($rendezvous)) || يموت "الاتصال: $!";
بينما (محدد($line = )) {
طباعة سطر $؛
}
خروج(0)

وهنا الخادم المقابل. لا داعي للقلق بشأن الشبكة السخيفة
الإنهاءات هنا لأن مآخذ توصيل مجال Unix مضمونة على المضيف المحلي، و
وبالتالي كل شيء يعمل بشكل صحيح.

#!/ البيرة / بن / بيرل -اثنين
استخدام صارم
استخدام المقبس
استخدام الكارب

البدء {$ENV{PATH} = "/ البيرة / بن:/ بن" }
تفرخ الفرعية. #إعلان للأمام
logmsg الفرعية { طباعة "$0 $$: @_ at "، العددية localtime()، "\n" }

$NAME = "catsock";
بلدي $uaddr = sockaddr_un($NAME);
بلدي $proto = getprotobyname("tcp");

المقبس (الخادم، PF_UNIX، SOCK_STREAM، 0) || يموت "المقبس: $!";
إلغاء الارتباط($NAME);
ربط (الخادم، $uaddr) || يموت "الربط: $!";
استمع (الخادم، SOMAXCONN) || يموت "استمع: $!";

logmsg "بدأ الخادم على $NAME";

$waitedpid الخاص بي؛

استخدم POSIX ":sys_wait_h";
ريبر الفرعية {
طفلي $؛
بينما (($waitedpid = waitpid(-1, WNOHANG)) > 0) {
logmsg "حصد $waitedpid" . ($? ? " مع الخروج $؟" : "");
}
$SIG{CHLD} = \&REAPER; # أكره SysV
}

$SIG{CHLD} = \&REAPER;

ل($waitedpid = 0;
قبول (العميل، الخادم) || $waitedpid;
$waitedpid = 0، إغلاق العميل)
{
التالي إذا $waitedpid؛
logmsg "الاتصال على $NAME";
تفرخ الفرعية {
طباعة "مرحبًا، لقد حان الوقت الآن"، scalar localtime(), "\n";
exec("/usr/games/fortune") || يموت "لا يمكن تنفيذ الثروة: $!";
};
}

تفرخ فرعي {
بلدي $coderef = Shift();

ما لم (@_ == 0 && $coderef && ref($coderef) مكافئ "CODE") {
الاعتراف بـ "الاستخدام: Spawn CODEREF"؛
}

معرف $ الخاص بي;
ما لم (محدد($pid = fork())) {
logmsg "لا يمكن التفرع: $!";
العودة؛
}
إلسيف ($معرف المنتج) {
logmsg "begat $pid";
يعود؛ #أنا الوالد
}
آخر {
# أنا الطفل - اذهب لتفرخ
}

open(STDIN, "<&Client") || يموت "لا يمكن خداع العميل إلى stdin"؛
open(STDOUT, ">&Client") || يموت "لا يمكن خداع العميل إلى stdout" ؛
## open(STDERR, ">&STDOUT") || يموت "لا يمكن أن يخدع stdout إلى stderr"؛
خروج($coderef->());
}

كما ترون، فهو يشبه إلى حد كبير خادم TCP الخاص بمجال الإنترنت، إلى حد كبير
الحقيقة، أننا حذفنا عدة وظائف مكررة--تفرخ (), سجل الرسالة (), ctime ()و
حصادة()--وهي نفسها الموجودة في الخادم الآخر.

فلماذا قد ترغب في استخدام مقبس مجال Unix بدلاً من أنبوب مسمى أبسط؟
لأن الأنبوب المسمى لا يمنحك جلسات عمل. لا يمكنك معرفة بيانات عملية واحدة منها
آخر. مع برمجة المقبس، تحصل على جلسة منفصلة لكل عميل؛ هذا
لماذا قبول () يأخذ حجتين.

على سبيل المثال، لنفترض أن لديك برنامجًا خفيًا لخادم قاعدة بيانات طويل الأمد تريده
ليتمكن الأشخاص من الوصول إليها من الويب، ولكن فقط إذا مروا عبر واجهة CGI.
سيكون لديك برنامج CGI صغير وبسيط يقوم بإجراء أي عمليات فحص وتسجيل تشعر بها
مثل، ثم يعمل كعميل مجال Unix ويتصل بخادمك الخاص.

TCP العملاء مع الإدخال::المقبس


بالنسبة لأولئك الذين يفضلون واجهة ذات مستوى أعلى لبرمجة المقبس، فإن وحدة IO::Socket
يوفر نهجا موجها للكائنات. إذا كنت تفتقر إلى هذه الوحدة لسبب ما، فيمكنك ذلك
فقط قم بإحضار IO::Socket من CPAN، حيث ستجد أيضًا وحدات توفر واجهات سهلة
إلى الأنظمة التالية: DNS، FTP، Ident (RFC 931)، NIS وNISPlus، NNTP، Ping، POP3،
SMTP، وSNMP، وSSLeay، وTelnet، وTime - على سبيل المثال لا الحصر.

A الاشارات العميل
إليك عميل يقوم بإنشاء اتصال TCP بخدمة "النهار" في المنفذ 13 من
اسم المضيف "المضيف المحلي" ويطبع كل ما يهتم الخادم هناك بتقديمه.

#!/ البيرة / بن / بيرل -w
استخدم IO::Socket;
$remote = IO::Socket::INET->new(
بروتو => "برنامج التعاون الفني"،
PeerAddr => "المضيف المحلي"،
منفذ النظير => "النهار(13)"،
)
|| die "لا يمكن الاتصال بالخدمة النهارية على المضيف المحلي";
بينما (<$remote>) {طباعة}

عند تشغيل هذا البرنامج، يجب أن تحصل على شيء يبدو كالتالي:

الأربعاء 14 مايو 08:40:46 MDT 1997

وهنا ما تلك المعلمات ل جديد() يعني المنشئ:

"بروتو"
هذا هو البروتوكول الذي يجب استخدامه. في هذه الحالة، سيتم إرجاع مقبض المقبس
متصل بمقبس TCP، لأننا نريد اتصالًا موجهًا للتيار، أي واحد
الذي يعمل إلى حد كبير مثل ملف قديم عادي. ليست كل المقابس من هذا النوع.
على سبيل المثال، يمكن استخدام بروتوكول UDP لإنشاء مقبس مخطط بيانات يستخدم للرسائل-
عابرة.

"عنوان النظير"
هذا هو الاسم أو عنوان الإنترنت للمضيف البعيد الذي يعمل عليه الخادم. نحن
كان من الممكن تحديد اسم أطول مثل "www.perl.com"، أو عنوان مثل
"207.171.7.72". لأغراض العرض التوضيحي، استخدمنا اسم المضيف الخاص
"المضيف المحلي"، والذي يجب أن يعني دائمًا الجهاز الحالي الذي تعمل عليه. ال
عنوان الإنترنت المقابل للمضيف المحلي هو "127.0.0.1"، إذا كنت تفضل استخدام ذلك.

"منفذ النظير"
هذا هو اسم الخدمة أو رقم المنفذ الذي نرغب في الاتصال به. كان من الممكن أن نحصل
يمكنك التخلص من استخدام "النهار" فقط على الأنظمة التي تحتوي على خدمات نظام جيدة التكوين
الملف،[ملاحظة: تم العثور على ملف خدمات النظام في / الخ / خدمات تحت يونيكسي
Systems.] لكننا هنا حددنا رقم المنفذ (13) بين قوسين. باستخدام فقط
كان من الممكن أن يعمل الرقم أيضًا، لكن القيم الحرفية الرقمية تجعل المبرمجين حذرين
متوتر.

لاحظ كيف يتم استخدام القيمة المرجعة من المُنشئ "الجديد" كمقبض ملف في ملف
"حائط اللوب؟ وهذا ما يسمى غير مباشر مقبض الملف، متغير عددي يحتوي على أ
مقبض الملف. يمكنك استخدامه بنفس الطريقة التي تستخدم بها مقبض الملف العادي. علي سبيل المثال انت
يمكن قراءة سطر واحد منه بهذه الطريقة:

$ line = <$ handle> ؛

جميع الأسطر المتبقية من هي بهذه الطريقة:

@lines = <$handle>;

وأرسل سطرًا من البيانات إليه بهذه الطريقة:

طباعة مقبض $ "بعض البيانات\n";

A ويبجيت العميل
إليك عميل بسيط يأخذ مضيفًا بعيدًا لجلب مستند منه، ثم قائمة
من الملفات التي يمكن الحصول عليها من هذا المضيف. هذا عميل أكثر إثارة للاهتمام من العميل السابق
لأنه يرسل شيئًا ما إلى الخادم أولاً قبل جلب استجابة الخادم.

#!/ البيرة / بن / بيرل -w
استخدم IO::Socket;
ما لم (@ARGV > 1) { يموت "الاستخدام: $0 عنوان URL للمضيف ..." }
$host = Shift(@ARGV);
$EOL = "\015\012";
$فارغ = $EOL × 2؛
للمستند $ الخاص بي (@ARGV) {
$remote = IO::Socket::INET->new( Proto => "tcp"،
بيرأدر => مضيف $،
منفذ النظير => "HTTP(80)"،
) || die "لا يمكن الاتصال بـ httpd على $host";
$ عن بعد->التنظيف التلقائي(1)
اطبع $remote "احصل على مستند $ HTTP/1.0". $ فارغ؛
بينما (<$remote>) {طباعة}
أغلق $remote؛
}

من المفترض أن يكون خادم الويب الذي يتعامل مع خدمة HTTP في منفذه القياسي رقم 80.
إذا كان الخادم الذي تحاول الاتصال به موجودًا في منفذ مختلف، مثل 1080 أو 8080،
يجب تحديده كزوج المعلمة المسماة، "PeerPort => 8080". طريقة "التدفق التلقائي".
يتم استخدامه على المقبس لأنه بخلاف ذلك سيقوم النظام بتخزين الإخراج الذي أرسلناه إليه.
(إذا كنت تستخدم جهاز Mac من عصور ما قبل التاريخ، فستحتاج أيضًا إلى تغيير كل "\n" في التعليمات البرمجية الخاصة بك
يرسل البيانات عبر الشبكة لتكون "\015\012" بدلاً من ذلك.)

يعد الاتصال بالخادم الجزء الأول فقط من العملية: بمجرد حصولك على
الاتصال، عليك استخدام لغة الخادم. كل خادم على الشبكة له خاصته
لغة الأوامر الصغيرة التي تتوقعها كمدخلات. السلسلة التي نرسلها إلى الخادم
البدء بـ "GET" موجود في بناء جملة HTTP. في هذه الحالة، نحن ببساطة نطلب كل المحدد
وثيقة. نعم، نحن نقوم بالفعل بإجراء اتصال جديد لكل مستند، على الرغم من أنه كذلك
نفس المضيف. هذه هي الطريقة التي اعتدت دائمًا أن تتحدث بها باستخدام HTTP. الإصدارات الأخيرة من
قد تطلب متصفحات الويب من الخادم البعيد ترك الاتصال مفتوحًا لبعض الوقت،
ولكن ليس من الضروري أن يحترم الخادم مثل هذا الطلب.

فيما يلي مثال لتشغيل هذا البرنامج، والذي سنسميه webget:

% webget www.perl.com /guanaco.html
لم يتم العثور على ملف HTTP/1.1 404
التاريخ: الخميس 08 مايو 1997 الساعة 18:02:32 بتوقيت جرينتش
الخادم: أباتشي/1.2b6
اتصال: وثيق
نوع المحتوى: نص / html

404 الملف غير موجود
لم يتم العثور على الملف
لم يتم العثور على عنوان URL المطلوب /guanaco.html على هذا الخادم.


حسنًا، هذا ليس مثيرًا للاهتمام، لأنه لم يعثر على تلك الوثيقة تحديدًا. لكن
الرد الطويل لن يكون مناسبًا لهذه الصفحة.

للحصول على نسخة أكثر تميزا من هذا البرنامج، يجب أن تنظر إلى طلب lwp برنامج
مضمن مع وحدات LWP من CPAN.

تفاعلي العميل مع الإدخال::المقبس
حسنًا، لا بأس إذا كنت تريد إرسال أمر واحد والحصول على إجابة واحدة، ولكن ماذا عن ذلك؟
إعداد شيء تفاعلي بالكامل، يشبه إلى حد ما الطريقة التلنت يعمل؟ بهذه الطريقة أنت
يمكنه كتابة سطر، والحصول على الإجابة، وكتابة سطر، والحصول على الإجابة، وما إلى ذلك.

هذا العميل أكثر تعقيدًا من العميلين اللذين قمنا بهما حتى الآن، ولكن إذا كنت تستخدم نظامًا
الذي يدعم استدعاء "الشوكة" القوي، فإن الحل ليس بهذه الصعوبة. بمجرد الانتهاء من ذلك
الاتصال بأي خدمة ترغب في الدردشة معها، اتصل بـ "fork" لاستنساخ حسابك
عملية. كل من هاتين العمليتين المتطابقتين لديها مهمة بسيطة جدًا للقيام بها: الوالد
نسخ كل شيء من المقبس إلى الإخراج القياسي، في حين أن الطفل في وقت واحد
نسخ كل شيء من الإدخال القياسي إلى المقبس. لإنجاز نفس الشيء باستخدام
ستكون عملية واحدة فقط كثيرا أصعب، لأنه من الأسهل ترميز عمليتين للقيام بعملية واحدة
الأمر أكثر من مجرد ترميز عملية واحدة للقيام بشيئين. (هذا المبدأ البسيط أ
حجر الزاوية في فلسفة يونكس، وهندسة البرمجيات الجيدة أيضًا، وهي
ربما سبب انتشاره إلى أنظمة أخرى.)

ها هو الكود:

#!/ البيرة / بن / بيرل -w
استخدام صارم
استخدم IO::Socket;
بلدي ($المضيف، $port، $kidpid، $handle، $line)؛

ما لم (@ARGV == 2) { يموت "الاستخدام: منفذ مضيف $0" }
($المضيف، $المنفذ) = @ARGV؛

# إنشاء اتصال TCP بالمضيف والمنفذ المحددين
$handle = IO::Socket::INET->new(Proto => "tcp"،
بيرأدر => مضيف $،
بيربورت => منفذ $)
|| die "لا يمكن الاتصال بالمنفذ $port على $host: $!";

مقبض $->التنظيف التلقائي(1)؛ # حتى يصل الإخراج إلى هناك على الفور
طباعة STDERR "[متصل بمضيف $:$port]\n";

#تقسيم البرنامج إلى عمليتين توأم متطابق
يموت "لا يمكن أن تفرع: $!" ما لم يتم تعريفه($kidpid = fork());

# يتم تشغيل كتلة if{} فقط في العملية الأصلية
إذا ($كيدبيد) {
# انسخ المقبس إلى الإخراج القياسي
بينما (محدد ($line = <$handle>)) {
طباعة سطر STDOUT $؛
}
kill("TERM", $kidpid); # أرسل SIGTERM إلى الطفل
}
# تعمل الكتلة else{} فقط في العملية الفرعية
آخر {
# انسخ الإدخال القياسي إلى المقبس
بينما (محدد ($line = )) {
طباعة $مقبض $line؛
}
خروج(0)؛ # فقط في حالة
}

وظيفة "القتل" في كتلة "إذا" الخاصة بالوالدين موجودة لإرسال إشارة إلى طفلنا
العملية التي تعمل حاليًا في الكتلة "آخر"، بمجرد إغلاق الخادم البعيد
نهايتها الاتصال.

إذا أرسل الخادم البعيد بايتًا من البيانات في كل مرة، وكنت بحاجة إلى تلك البيانات على الفور بدونها
في انتظار السطر الجديد (وهذا قد لا يحدث)، قد ترغب في استبدال حلقة "أثناء".
في الوالد بما يلي:

البايت $ الخاص بي؛
بينما (sysread($handle, $byte, 1) == 1) {
طباعة STDOUT $ بايت؛
}

إن إجراء مكالمة نظام لكل بايت تريد قراءته ليس فعالاً للغاية (على حد تعبيره
بشكل معتدل) ولكنه الأبسط في الشرح ويعمل بشكل جيد إلى حد معقول.

TCP خوادم مع الإدخال::المقبس


كما هو الحال دائمًا، يعد إعداد الخادم أكثر تعقيدًا من تشغيل العميل. ال
النموذج هو أن الخادم ينشئ نوعًا خاصًا من المقبس الذي لا يفعل شيئًا سوى الاستماع
منفذ معين للاتصالات الواردة. يتم ذلك عن طريق الاتصال بـ
أسلوب "IO::Socket::INET->new()" مع وسيطات مختلفة قليلاً عن تلك التي يستخدمها العميل.

بروتو
هذا هو البروتوكول الذي يجب استخدامه. مثل عملائنا، سنستمر في تحديد "tcp" هنا.

ميناء محلي
نحدد منفذًا محليًا في وسيطة "LocalPort"، وهو ما لم نفعله مع
عميل. هذا هو اسم الخدمة أو رقم المنفذ الذي تريد أن يكون الخادم له.
(في نظام Unix، تكون المنافذ تحت 1024 مقتصرة على المستخدم المتميز.) في العينة الخاصة بنا، سنقوم بما يلي:
استخدم المنفذ 9000، ولكن يمكنك استخدام أي منفذ غير مستخدم حاليًا على نظامك.
إذا حاولت استخدام عنوان مستخدم بالفعل، فستتلقى رسالة "العنوان قيد الاستخدام بالفعل".
ضمن Unix، سيُظهر الأمر "netstat -a" الخدمات الحالية التي تحتوي على خوادم.

استمع
تم ضبط معلمة "الاستماع" على أقصى عدد ممكن من الاتصالات المعلقة
قبول حتى نرفض العملاء القادمين. فكر في الأمر كقائمة انتظار لانتظار المكالمات
الهاتف. تحتوي وحدة المقبس ذات المستوى المنخفض على رمز خاص للنظام
الحد الأقصى، وهو SOMAXCONN.

إعادة استخدام
هناك حاجة إلى معلمة "إعادة الاستخدام" حتى نتمكن من إعادة تشغيل خادمنا يدويًا دون انتظار
بضع دقائق للسماح بمسح المخازن المؤقتة للنظام.

بمجرد إنشاء مأخذ توصيل الخادم العام باستخدام المعلمات المذكورة أعلاه، سيتم إنشاء مأخذ توصيل الخادم العام
ثم ينتظر الخادم حتى يتصل به عميل جديد. كتل الخادم في "قبول"
الطريقة، والتي تقبل في النهاية اتصالاً ثنائي الاتجاه من العميل البعيد. (يصنع
تأكد من مسح هذا المقبض تلقائيًا للتحايل على التخزين المؤقت.)

ولزيادة سهولة الاستخدام، يطلب خادمنا من المستخدم إصدار الأوامر. معظم الخوادم لا تفعل ذلك
افعل هذا. نظرًا لعدم وجود سطر جديد في المطالبة، سيتعين عليك استخدام "قراءة النظام"
البديل من العميل التفاعلي أعلاه.

يقبل هذا الخادم واحدًا من خمسة أوامر مختلفة، ويرسل المخرجات مرة أخرى إلى العميل.
على عكس معظم خوادم الشبكة، يتعامل هذا الخادم مع عميل وارد واحد فقط في كل مرة.
تمت تغطية خوادم المهام المتعددة في الفصل 16 من الجمل.

هذا هو الرمز. حسنًا

#!/ البيرة / بن / بيرل -w
استخدم IO::Socket;
استخدم Net::hostent; # لإصدار OOish من gethostbyaddr

منفذ $ = 9000؛ # اختر شيئًا غير مستخدم

$server = IO::Socket::INET->new( Proto => "tcp"،
المنفذ المحلي => المنفذ $،
استمع => سوماكسون،
إعادة الاستخدام => 1);

يموت "لا يمكن إعداد الخادم" إلا إذا كان $server؛
طباعة "[الخادم $0 يقبل العملاء]\n";

بينما ($client = $server->accept()) {
$العميل->التنظيف التلقائي(1)
print $client "مرحبًا بك في $0؛ اكتب تعليمات لقائمة الأوامر.\n";
$hostinfo = gethostbyaddr($client->peeraddr);
printf "[الاتصال من %s]\n"، $hostinfo ؟ $hostinfo->name : $client->peerhost;
طباعة عميل $ "الأمر؟"؛
بينما (<$العميل>) {
التالي ما لم /\S/; # سطر فارغ
إذا (/quit|exit/i) { last }
elsif (/date|time/i) { printf $client "%s\n"، التوقيت المحلي العددي () }
elsif (/who/i ) { print $client `who 2>&1` }
elsif (/cookie/i ) { print $client `/usr/games/fortune 2>&1` }
elsif (/motd/i ) { print $client `cat /etc/motd 2>&1` }
آخر {
طباعة $client "الأوامر: تاريخ الخروج من ملف تعريف الارتباط motd\n";
}
} يكمل {
طباعة عميل $ "الأمر؟"؛
}
إغلاق $client;
}

UDP: الموضوع مرور


نوع آخر من إعدادات خادم العميل هو الذي لا يستخدم الاتصالات، بل الرسائل. UDP
تنطوي الاتصالات على حمل أقل بكثير ولكنها توفر أيضًا موثوقية أقل، كما هو الحال بالفعل
لا توجد وعود بأن الرسائل ستصل على الإطلاق، ناهيك عن أنها منظمة وغير مشوهة. ما زال،
يوفر UDP بعض المزايا مقارنة بـ TCP، بما في ذلك القدرة على "البث" أو "البث المتعدد" إلى
مجموعة كاملة من مضيفي الوجهة مرة واحدة (عادةً على شبكتك الفرعية المحلية). إذا وجدت
تشعر بالقلق الشديد بشأن الموثوقية وتبدأ في إجراء عمليات فحص لرسالتك
النظام، فربما ينبغي عليك استخدام TCP فقط للبدء به.

مخططات بيانات UDP هي ليس تيار بايت ولا ينبغي معاملته على هذا النحو. وهذا يجعل استخدام
آليات الإدخال/الإخراج مع التخزين المؤقت الداخلي مثل stdio (أي طباعة() والأصدقاء) على وجه الخصوص
مرهقة. يستخدم syswrite ()أو أفضل أرسل ()، كما في المثال أدناه.

فيما يلي برنامج UDP مشابه لنموذج عميل Internet TCP المذكور سابقًا. لكن،
بدلاً من التحقق من مضيف واحد في كل مرة، سيقوم إصدار UDP بالتحقق من العديد منهم
بشكل غير متزامن عن طريق محاكاة البث المتعدد ثم استخدامه تحديد() للقيام بالانتظار انتهاء المهلة
للإدخال/الإخراج. للقيام بشيء مماثل مع TCP، يجب عليك استخدام مقبض مقبس مختلف
لكل مضيف.

#!/ البيرة / بن / بيرل -w
استخدام صارم
استخدام المقبس
استخدم Sys :: Hostname ؛

بلدي ( $count، $hisiaddr، $hispaddr، $histime،
مضيف $، $iaddr، $paddr، $port، $proto،
$rin، $rout، $rtime، $SECS_OF_70_YEARS)؛

$SECS_OF_70_YEARS = 2_208_988_800;

$iaddr = gethostbyname(hostname());
$proto = getprotobyname("udp");
$port = getservbyname("time", "udp");
$paddr = sockaddr_in(0, $iaddr); # 0 يعني السماح للنواة بالاختيار

المقبس (SOCKET، PF_INET، SOCK_DGRAM، $proto) || يموت "المقبس: $!";
ربط (SOCKET، $paddr) || يموت "الربط: $!";

$ | = 1 ؛
printf "%-12s %8s %s\n"، "localhost"، 0، العددية localtime();
$ عدد = 0.
للمضيف $ (@ARGV) {
العد ++ دولار ؛
$hisiaddr = inet_aton($host) || يموت "مضيف غير معروف"؛
$hispaddr = sockaddr_in($port, $hisiaddr);
محدد(send(SOCKET, 0, 0, $hispaddr)) || يموت "أرسل $host: $!";
}

$رين = "";
vec($rin, fileno(SOCKET), 1) = 1;

# مهلة بعد 10.0 ثانية
بينما ($count && حدد($rout = $rin, undef, undef, 10.0)) {
$rtime = "";
$hispaddr = recv(SOCKET, $rtime, 4, 0) || يموت "recv: $!";
($port, $hisiaddr) = sockaddr_in($hispaddr);
$host = gethostbyaddr($hisiaddr, AF_INET);
$histime = unpack("N", $rtime) - $SECS_OF_70_YEARS;
printf "%-12s"، $host;
printf "%8d %s\n"، $histime - time()، التوقيت المحلي العددي($histime);
$count--;
}

لا يتضمن هذا المثال أي عمليات إعادة محاولة، وبالتالي قد يفشل في الاتصال بشخص يمكن الوصول إليه
يستضيف. السبب الأبرز لذلك هو ازدحام قوائم الانتظار على المضيف المرسل
إذا كان عدد المضيفين المطلوب الاتصال بهم كبيرًا بدرجة كافية.

sysv IPC


على الرغم من أن نظام System V IPC لا يُستخدم على نطاق واسع كمقابس، إلا أنه لا يزال لديه بعض الاستخدامات المثيرة للاهتمام.
ومع ذلك، لا يمكنك استخدام SysV IPC أو Berkeley mmap () أن يكون هناك متغير مشترك بين
عدة عمليات. ذلك لأن Perl سيعيد تخصيص السلسلة الخاصة بك عندما لم تفعل ذلك
يريد ذلك. يمكنك الاطلاع على الوحدات النمطية "IPC::Shareable" أو "threads::shared".
أن.

فيما يلي مثال صغير يوضح استخدام الذاكرة المشتركة.

استخدم IPC::SysV qw(IPC_PRIVATE IPC_RMID S_IRUSR S_IWUSR);

حجم $ = 2000؛
$id = shmget(IPC_PRIVATE, $size, S_IRUSR | S_IWUSR);
محدد(معرف $) || يموت "shmget: $!";
طباعة "مفتاح shm $id\n";

$message = "الرسالة رقم 1";
shmwrite($id, $message, 0, 60) || يموت "shmwrite: $!";
طباعة "كتب: '$message'\n";
shmread($id, $buff, 0, 60) || يموت "شمريد: $!";
طباعة "قراءة : '$buff'\n";

# المخزن المؤقت لـ shmread ذو نهاية مبطنة بأحرف صفرية.
substr($buff, Index($buff, "\0")) = "";
طباعة "un" ما لم $buff eq $message;
طباعة "انتفاخ\n";

طباعة "حذف معرف shm $\n";
shmctl($id, IPC_RMID, 0) || يموت "shmctl: $!";

فيما يلي مثال للإشارة:

استخدم IPC::SysV qw(IPC_CREAT);

$IPC_KEY = 1234؛
$id = semget($IPC_KEY, 10, 0666 | IPC_CREAT);
محدد(معرف $) || يموت "semget: $!";
طباعة "معرف sem $id\n";

ضع هذا الكود في ملف منفصل ليتم تشغيله في أكثر من عملية. اتصل بالملف أخذ:

# إنشاء إشارة

$IPC_KEY = 1234؛
$id = semget($IPC_KEY, 0, 0);
محدد(معرف $) || يموت "semget: $!";

$semnum = 0;
$semflag = 0;

# "خذ" الإشارة
# انتظر حتى تصبح الإشارة صفرًا
$semop = 0;
$opstring1 = pack("s!s!s!", $semnum, $semop, $semflag);

# زيادة عدد الإشارة
$semop = 1;
$opstring2 = pack("s!s!s!", $semnum, $semop, $semflag);
$opstring = $opstring1 . $opstring2;

semop($id, $opstring) || يموت "semop: $!";

ضع هذا الكود في ملف منفصل ليتم تشغيله في أكثر من عملية. استدعاء هذا الملف تمنح:

# "أعط" الإشارة
# قم بتشغيل هذا في العملية الأصلية وسترى
# أن تستمر العملية الثانية

$IPC_KEY = 1234؛
$id = semget($IPC_KEY, 0, 0);
يموت ما لم يتم تعريفه($id);

$semnum = 0;
$semflag = 0;

# تقليل عدد الإشارة
$semop = -1;
$opstring = pack("s!s!s!", $semnum, $semop, $semflag);

semop($id, $opstring) || يموت "semop: $!";

تمت كتابة رمز SysV IPC أعلاه منذ فترة طويلة، ومن المؤكد أنه يبدو قديمًا. ل
نظرة أكثر حداثة، راجع وحدة IPC::SysV.

مثال صغير يوضح قوائم انتظار رسائل SysV:

استخدم IPC::SysV qw(IPC_PRIVATE IPC_RMID IPC_CREAT S_IRUSR S_IWUSR);

معرف $ الخاص بي = msgget(IPC_PRIVATE, IPC_CREAT | S_IRUSR | S_IWUSR);
محدد(معرف $) || يموت "فشلت الرسالة: $!";

$sent الخاص بي = "message";
$type_sent الخاص بي = 1234؛

msgsnd($id, pack("l! a*", $type_sent, $sent), 0)
|| يموت "فشلت الرسالة: $!";

msgrcv($id, $rcvd_buf, 60, 0, 0)
|| يموت "فشل msgrcv: $!";

my($type_rcvd, $rcvd) = unpack("l! a*", $rcvd_buf);

إذا ($rcvd مكافئ $sent) {
اطبع "حسنًا\n";
{} آخر
طباعة "ليس بخير\n";
}

msgctl($id, IPC_RMID, 0) || يموت "فشل msgctl: $!\n";

الملاحظات


معظم هذه الإجراءات الروتينية تعود بهدوء ولكن بأدب "غير محدد" عندما تفشل بدلاً من ذلك
مما يتسبب في موت برنامجك في ذلك الوقت وهناك بسبب استثناء لم يتم اكتشافه. (في الحقيقة،
بعض الجديد البريزة وظائف التحويل تفعل تشاءم() على الحجج السيئة.) ولذلك
ضروري للتحقق من قيم الإرجاع من هذه الوظائف. ابدأ دائمًا برامج المقبس الخاصة بك
بهذه الطريقة لتحقيق النجاح الأمثل، ولا تنس إضافة -T علامة فحص الشوائب إلى
"#!" خط للخوادم:

#!/ البيرة / بن / بيرل -اثنين
استخدام صارم
استخدم سيجتراب؛
استخدام المقبس

استخدم perlipc عبر الإنترنت باستخدام خدمات onworks.net


خوادم ومحطات عمل مجانية

قم بتنزيل تطبيقات Windows و Linux

أوامر لينكس

Ad