مطبّات محرك الشبكة ومسار UNC ── الممارسة العمليّة للتعامل مع خادم الملفّات (المجلّد المشترك) في تطبيقات الأعمال

· آخر تحديث: · · محرك الشبكة, مسار UNC, SMB, مشاركة الملفّات, خدمات Windows, خادم الملفّات, C#, .NET, الشبكات, تحقيق الأخطاء, تطوير Windows, الاستشارات التقنية

«كان يعمل على جهاز التطوير، لكن عند العميل ظهرت رسالة ‘تعذّر العثور على Z:'» أو «بمجرّد وضعه في جدولة المهام (Task Scheduler)، بدأ الإخراج إلى المجلّد المشترك بالفشل» أو «بعد تحويله إلى خدمة Windows، لم يعد خادم الملفّات مرئيّاً» ── في استشارات تطبيقات الأعمال، تُعدّ الأعطال المرتبطة بخادم الملفّات (المجلّد المشترك) من أكثر الأعطال شيوعاً على الإطلاق. استيراد بيانات الطلبات، إخراج التقارير وملفّات CSV، مراقبة الملفّات التي يُصدرها جهاز ما ── ففي أنظمة الأعمال المحليّة (on-premises)، لا يزال المجلّد المشترك ركيزة تكامل فعّالة حتّى اليوم.

والمزعج أنّ الكثير من هذا النوع من الأعطال لا يعود إلى «خلل في الشيفرة»، بل يضرب بجذوره في آليّات Windows نفسها: العلاقة بين حرف القرص وجلسة تسجيل الدخول، وحساب تشغيل الخدمة والمصادقة، وإدارة اتّصالات SMB. فتتبّع الأمر عبر أداة تصحيح الأخطاء (debugger) لا يوصلك إلى السبب، ويذوب الوقت وأنت عالق عند «لا يتكرّر على جهازي».

يستهدف هذا المقال مطوّري تطبيقات الأعمال (WinForms/WPF/خدمات Windows) الذين ينفّذون إخراج الملفّات إلى المجلّد المشترك واستيرادها ومراقبتها، ويرتّب مطبّات محرك الشبكة ومسار UNC انطلاقاً من الآليّة الكامنة وراءها، ثمّ يجمع القواعد العمليّة الثابتة في جدول قرار.

1. الخلاصة أوّلاً

  • حرف القرص (مثل Z:) ليس ملكاً للنظام بأكمله، بل هو وحدة تخصّ جلسة تسجيل الدخول. يُخصَّص لكلّ جلسة تسجيل دخول مجموعة كاملة من أحرف الأقراص من A إلى Z، لذا فإنّ القرص الذي يربطه المستخدم لا يظهر لعمليّة مستخدم آخر، ولا لخدمة تعمل في جلسة تسجيل دخول مختلفة.1
  • بالنسبة لمسؤول (administrator) يعمل معه UAC، تُنشأ جلستا تسجيل دخول: واحدة بصلاحيّات عاديّة وأخرى للرفع (elevation)، ويكون ربط القرص (الرابط الرمزيّ لـ DosDevices) مستقلّاً لكلّ جلسة على حدة. لهذا يحدث «Z: لا تظهر من التطبيق المرفوع الصلاحيّات فقط». يوجد حلّ التفافيّ عبر مفتاح التسجيل EnableLinkedConnections لمشاركة الجلستين، لكنّه إعداد غير مدعوم صرّحت Microsoft بأنّه «قد يجعل النظام غير آمن، ولا تدعمه».23
  • عند الوصول إلى موارد بعيدة من خدمة (أو من عمليّة ذات سياق أمنيّ مختلف)، فإنّ التوجيه الرسميّ هو استخدام مسار UNC (\\server\share\...). أمّا تنفيذ ربط حرف قرص من داخل الخدمة عبر net use أو واجهات WNet البرمجيّة، فغير مُوصى به من زاوية تسرّب بيانات الاعتماد والتداخل بين الخدمات.1
  • الجهة التي يُمنح لها الإذن على جانب المشاركة يحدّدها حساب تشغيل الخدمة. يُصادَق LocalSystem وNetworkService على الشبكة باعتبارهما «بيانات اعتماد الحاسوب»، بينما يتحوّل LocalService إلى بيانات اعتماد مجهولة، ما يجعله غير مناسب للوصول إلى المشاركة. أمّا الخدمة التي تعمل بحساب مستخدم محلّي فلا يمكنها الوصول إلى موارد الشبكة إطلاقاً. الخيار الأفضل عمليّاً هو حساب نطاق أو gMSA الذي يترك إدارة كلمة المرور لنظام التشغيل.45678
  • لا يمكن الاتّصال في وقت واحد بنفس الخادم باستخدام أكثر من مجموعة بيانات اعتماد. يفشل الاتّصال الثاني بالخطأ 1219 (ERROR_SESSION_CREDENTIAL_CONFLICT)، وهذا سلوك مقصود بحسب التصميم (by design).910
  • المجلّد المشترك «بطيء، ينقطع، وقد لا يكون موجوداً» بوصف ذلك الحالة الطبيعيّة. يُقطَع الاتّصال الخامل افتراضيّاً من جانب الخادم بعد 15 دقيقة (وتُعاد المحاولة تلقائيّاً عند الوصول التالي). ولأنّ File.Exists تُرجع false دون رمي استثناء حتّى عند نقص الصلاحيّات أو حدوث خطأ، فلا يمكن التمييز بين «الملفّ غير موجود» و«تعذّر الوصول إلى الخادم».1112
  • يدعم FileSystemWatcher مراقبة محرّك الشبكة والحاسوب البعيد، لكن صمِّم على افتراض احتمال فقدان أحداث. قد تُفقَد الأحداث عند فيضان المخزن المؤقّت، ويُقيَّد الحدّ الأقصى للمخزن المؤقّت الداخليّ بـ 64 كيلوبايت في المراقبة عبر الشبكة. ضمّ الاستطلاع الدوريّ (polling / المسح الكامل) هو القاعدة الثابتة.13

2. العلاقة بين حرف القرص ومسار UNC ── Z: ملك «جلسة تسجيل دخولك» أنت

عند ربط \\fileserver\share بـ Z: عبر مستكشف الملفّات، يبدو الأمر وكأنّ «قرص Z:» قد نبت على مستوى الآلة بأكملها. وهذا أوّل سوء فهم.

الوثائق الرسميّة واضحة في هذا الشأن. حرف القرص ليس عامّاً على مستوى النظام، بل يُخصَّص من A إلى Z لكلّ جلسة تسجيل دخول على حدة. لا يمكن مشاركة المحرك المُعاد توجيهه (network drive) بين عمليّات تعمل بحسابات مستخدمين مختلفة، ولا يمكن لخدمة تعمل في جلسة تسجيل دخول مختلفة الوصول إلى حرف قرص أُنشئ في جلسة أخرى.1 يدير النظام ربط الأقراص استناداً إلى معرِّف SID الخاصّ بجلسة تسجيل الدخول الذي يحدّدها بشكل فريد.1

بعبارة أخرى، فإنّ Z: ليست إلّا «مجموعة من رموز الاختصار للمسار موجودة لكلّ جلسة تسجيل دخول»، أمّا الجوهر الفعليّ فهو دائماً مسار UNC. ومن هنا يمكن تفسير الأعراض المتكرّرة في الميدان تفسيراً متسلسلاً.

العرض السبب من ناحية الآليّة
Z: غير موجودة عند التشغيل بمستخدم آخر حرف القرص وحدة تخصّ جلسة تسجيل الدخول. لا يظهر ربط الآخرين1
Z: غير موجودة عند «التشغيل كمسؤول» يُنشئ UAC جلستَي تسجيل دخول: عاديّة ومرفوعة الصلاحيّات، ولا يُشارَك الربط بينهما2
Z: غير موجودة عند وضعه في جدولة المهام أو كخدمة لأنّه يُنفَّذ في جلسة تسجيل دخول مختلفة. حتّى لو أُعدّت الخدمة بحساب مستخدم، ينشئ النظام جلسة تسجيل دخول جديدة خاصّة بالخدمة1

يستحقّ موضوع UAC مزيداً من التفصيل. عندما يسجّل مستخدم من مجموعة المسؤولين دخوله، ينشئ النظام جلستَي تسجيل دخول مرتبطتين: واحدة برمز مميّز (token) محدود الصلاحيّات وأخرى برمز مسؤول كامل. وجوهر ربط القرص هو كائن رابط رمزيّ (DosDevices) يربط بين حرف القرص ومسار UNC، وهذا الكائن خاصّ بجلسة تسجيل الدخول ولا يُشارَك بين الجلسات.2 وبما أنّ نصوص تسجيل الدخول (logon scripts) تعمل في جانب الصلاحيّات العاديّة، فإنّ العمليّة المرفوعة الصلاحيّات لا ترى ذلك الربط، وهذا هو المنطق وراء الأمر.

عند ضبط EnableLinkedConnections (قيمة DWORD ضمن HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System) على 1، يُكتَب الرابط الرمزيّ في كلتا الجلستين المرتبطتين معاً، فيختفي هذا العرض. غير أنّ الوثائق الرسميّة تنصّ صراحةً على أنّ «هذا الحلّ الالتفافيّ قد يجعل النظام غير آمن. لا تدعم Microsoft هذا الحلّ الالتفافيّ. استخدمه على مسؤوليّتك الخاصّة».3 واقتراح زرع إعداد غير مدعوم في سجلّ بيئة العميل ليس حلّاً موفَّقاً لتطبيق أعمال. الطريق الصحيح هو كتابة جانب التطبيق بمسار UNC. وحتّى في دليل استكشاف أخطاء إعادة توجيه المجلّدات (Folder Redirection) الرسميّ، يُوصى بـ«استخدام مسار UNC دائماً بدلاً من حرف القرص».3

من الناحية التنفيذيّة الأمر بسيط: يكفي الاحتفاظ بالمسار المحفوظ في ملفّ الإعدادات بصيغة UNC.

// appsettings.json ── يُحفَظ بصيغة UNC لا بحرف القرص
{
  "FileTransfer": {
    "IncomingDir": "\\\\fileserver01\\edi\\incoming",
    "ProcessedDir": "\\\\fileserver01\\edi\\processed"
  }
}

إن كان التطبيق يسمح للمستخدم باختيار مسار تحت Z: عبر مربّع حوار اختيار المجلّد، فإنّ تسوية المسار إلى UNC عند الحفظ يمنع انكساره لاحقاً حتّى لو تغيّر سياق التنفيذ. وتتوفّر واجهة برمجيّة باسم WNetGetUniversalName لتحويل حرف القرص إلى UNC.14

3. الوصول إلى مجلّد مشترك من خدمة Windows ── UNC إلزاميّ، ومسألة «باسم مَن» يتمّ الوصول

كما ورد في الفصل السابق، لا يمكن للخدمة استخدام محرك مُعيَّن (mapped drive). تنصّ الوثائق الرسميّة على أنّ الخدمات (والعمليّات التي تعمل بسياق أمنيّ مختلف) ينبغي أن تصل إلى الموارد البعيدة باسم UNC، وتُصرِّح بوضوح بعدم استحسان تنفيذ ربط حرف قرص وقت التشغيل من داخل الخدمة عبر net use أو واجهات WNet البرمجيّة. والأسباب المذكورة هي: أنّ الربط قد يظهر لخدمات أخرى تعمل في السياق نفسه، وأنّ بيانات الاعتماد الممرَّرة إلى net use قد تتسرّب خارج حدود الخدمة، وأنّ عدّة خدمات تحاول إنشاء الربط نفسه فتتداخل مع خطأ «متّصل مسبقاً».1

بعد التحوّل إلى مسار UNC، تصبح المشكلة التالية هي المصادقة. باسم مَن تصل الخدمة إلى خادم الملفّات؟ يحدِّد ذلك حساب التشغيل، وهو ما يحدِّد أيضاً الجهة التي ينبغي منحها الإذن على جانب المشاركة.

حساب التشغيل الهويّة على الشبكة منح الصلاحيّة على جانب المشاركة الحكم
LocalSystem بيانات اعتماد الحاسوب4 في بيئة النطاق: صلاحيّات المشاركة وNTFS لحساب حاسوب الآلة (DOMAIN\MACHINE$) يعمل لكن الصلاحيّات مفرطة. يتطلّب استبدال الآلة إعادة ضبط الصلاحيّات
NetworkService بيانات اعتماد الحاسوب5 كما سبق صلاحيّات محلّيّة دنيا، لكن الهويّة على الشبكة مطابقة لِـ LocalSystem
LocalService بيانات اعتماد مجهولة6 لا يمكن منحها غير مناسب للوصول إلى المشاركة
مستخدم محلّي لا يمكنه الوصول إلى موارد الشبكة7
مستخدم نطاق (domain) ذلك الحساب يُمنح لذلك الحساب المعيار في الممارسة العمليّة. تبقى إدارة تغيير كلمة المرور تحدّياً
gMSA ذلك الحساب يُمنح لذلك الحساب يدير نظام التشغيل كلمة المرور تلقائيّاً (تغيير تلقائيّ كلّ 30 يوماً). الخيار الأفضل في البيئات الداعمة815

كون LocalSystem وNetworkService يخرجان إلى الشبكة «باعتبارهما الحاسوب» هو سلوك موثَّق رسميّاً بوضوح.45 وتحمل وثائق BITS، كنتيجة طبيعيّة لذلك، تنبيهاً عمليّاً مباشراً: «إذا كانت قائمة ACL للملفّ المصدر تقصر الوصول على حساب مستخدم، فستُرفَض الخدمة (المُصادَق عليها ببيانات اعتماد الحاسوب)» و«لا ينبغي لحسابات النظام استخدام محرك مُعيَّن (mapped drive)».16

من هذا يمكن استخلاص ثلاثة توجيهات عمليّة:

  • الخطوة الأولى في تشخيص «رُفض الوصول بعد التحويل إلى خدمة» هي التحقّق من حساب التشغيل. فعندما كان يعمل على سطح المكتب كانت صلاحيّاتك «أنت»، أمّا في الخدمة فيُفحَص الوصول بالهويّة الواردة في الجدول أعلاه. تحقّق من أذونات المشاركة وقوائم NTFS ACL كلتيهما بالنسبة لتلك الهويّة.
  • إذا كان التشغيل طويل الأمد في بيئة نطاق، اجعل حساب التشغيل حساب نطاق أو gMSA. يحدِّث نظام التشغيل كلمة مرور gMSA العشوائيّة المكوَّنة من 240 بايت تلقائيّاً كلّ 30 يوماً، ما يُلغي بنيويّاً حوادث من نوع «توقّف كلّ شيء صباح الاثنين بسبب انتهاء صلاحيّة كلمة مرور حساب الخدمة».
  • في بيئة مجموعة عمل (workgroup بلا نطاق)، لا تصلح المصادقة ببيانات اعتماد الحاسوب، فيأتي دور بيانات الاعتماد الصريحة الموضَّحة في الفصل التالي.

طريقة بناء الخدمة نفسها (ضبط حساب التشغيل، خيارات الاسترداد، الإيقاف الآمن) موضّحة في «كيفيّة بناء خدمات Windows وتشغيلها»، وآليّة الجلسات وتسجيل الدخول مرتَّبة في «كيف نفهم عزل الجلسات في Windows».

4. التعامل مع بيانات الاعتماد ── net use، ومدير بيانات الاعتماد، والخطأ 1219

عندما يتعذّر منح حساب التشغيل نفسه صلاحيّات على جانب المشاركة (كما في بيئة مجموعة عمل، أو عندما يملك جهاز NAS نظام حسابات خاصّاً به)، يصبح الاتّصال عبر تمرير بيانات اعتماد صريحة هو الحلّ. توجد ثلاث وسائل رئيسيّة لذلك:

  • net use \\server\share /user:... ── يُنشئ اتّصالاً ضمن جلسة تسجيل الدخول تلك. مفيد للتحقّق التفاعليّ، لكن تنفيذه داخل خدمة غير موصى به كما ورد في الفصل السابق.1
  • مدير بيانات الاعتماد (cmdkey) ── الحفظ عبر cmdkey /add:server /user:svc-file /pass:... يجعل بيانات الاعتماد المحفوظة تُستخدَم تلقائيّاً عند المصادقة اللاحقة مع ذلك الخادم.1718 وبما أنّ الحفظ يتمّ على مستوى ملفّ تعريف المستخدم، فإنّ المطبّ هو أنّه إذا كان الاستخدام من خدمة، فيجب التسجيل «في سياق حساب تشغيل الخدمة» نفسه.
  • الاتّصال برمجيّاً (WNetAddConnection2) ── يمكن إنشاء اتّصال بمورد شبكة مع تحديد بيانات اعتماد العميل. تذكر الوثائق الرسميّة هذا أيضاً كإحدى استراتيجيّات وصول عمليّة الخادم إلى موارد الشبكة.19 وإذا استُخدم بصيغة «اتّصال بلا جهاز» (deviceless) دون تخصيص حرف قرص، يبقى الوصول عبر مسار UNC كما هو.

وأشهر مطبّ في هذا المجال هو الخطأ 1219.

Multiple connections to a server or shared resource by the same user, using more than one user name, are not allowed. (ERROR_SESSION_CREDENTIAL_CONFLICT, 1219)10

لا يمكن إنشاء عدّة اتّصالات بأسماء مستخدمين مختلفة من نفس جلسة تسجيل الدخول تجاه نفس الخادم. فإن حاولتَ الاتّصال بنفس خادم الملفّات بنوعين من بيانات الاعتماد ── حسابك الشخصيّ لمشاركة المبيعات، وحساب مخصَّص لمشاركة تكامل الأنظمة ── يفشل الاتّصال الثاني بالخطأ 1219. تصرِّح الوثائق الرسميّة بأنّ هذا سلوك «مقصود بحسب التصميم (by design)»، والحلول الالتفافيّة المذكورة هي «الاتّصال بعنوان IP» أو «إنشاء اسم مستعار (alias) في DNS والاتّصال به» ── أي بعبارة أخرى، طريقة إيهام النظام بأنّه خادم آخر.9

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

غير أنّ الاسم المستعار ليس مجرّد «سطر CNAME واحد يُضاف في DNS وينتهي الأمر». ففي بيئة مصادقة Kerberos، يحاول عميل SMB المصادقة عبر SPN (اسم أساسيّ للخدمة) المطابق لاسم الوجهة المتَّصل بها، لذا قد يفشل الوصول عبر CNAME نفسه إذا لم يكن SPN مسجَّلاً لذلك الاسم المستعار. ويذكر دليل استكشاف الأخطاء الرسميّ غياب SPN كأحد أسباب هذه المشكلة، ويوصي بتكوين الاسم المستعار كاسم بديل لاسم الحاسوب عبر netdom computername <اسم الخادم> /add:<الاسم المستعار> بدلاً من CNAME في DNS.20 تحقّق دائماً من عمل الاتّصال عبر الاسم المستعار في البيئة المستهدَفة فعليّاً قبل اعتماده كحلّ لمشكلة 1219 في التصميم.

وتجدر الإشارة إلى أنّ المتطلّب المتقدِّم القائل «أريد أن تصل الخدمة إلى خادم الملفّات بصلاحيّات مستخدم العميل المتَّصل» يقع في نطاق impersonation، لا في نطاق إعادة استخدام بيانات الاعتماد. وتوصي الوثائق الرسميّة نفسها بـ impersonation بدلاً من احتفاظ الخدمة ببيانات الاعتماد.1 للاطّلاع على الكتابة الصحيحة لِـ impersonation والاعتبارات الإضافيّة اللازمة مع الموارد البعيدة، راجع «التعامل الصحيح مع رموز impersonation في Windows».

5. التصميم على افتراض «البطء، والانقطاع، وإمكانيّة عدم الوجود»

الشيفرة المكتوبة بنفس إحساس التعامل مع قرص محلّي ستتعثّر لا محالة في مكان ما عند التعامل مع مجلّد مشترك. توجد ثلاث حقائق ينبغي اعتبارها الأساس.

أوّلاً، من الطبيعيّ أن ينقطع الاتّصال. يُقطَع الاتّصال الخامل افتراضيّاً بعد مهلة 15 دقيقة لمنع إهدار موارد الخادم. وهذه هي الظاهرة المألوفة لظهور علامة × الحمراء على محرك الشبكة في مستكشف الملفّات، وتُعاد المحاولة تلقائيّاً بسرعة عند الوصول التالي.11 بعبارة أخرى، فإنّ «تطبيق أعمال نادر الوصول ينتظر لحظة عند كلّ وصول» و«أداة مراقبة تضجّ بأنّ الاتّصال ‘انقطع!’ دون أيّ ضرر فعليّ» كلاهما سلوك مطابق للمواصفات. لا تراقب وجود الاتّصال، بل احكم بناءً على نجاح عمليّة الإدخال/الإخراج (I/O) الفعليّة.

ثانياً، طريقة ظهور الأخطاء غير ودّيّة. ترجع File.Exists القيمة false دون رمي استثناء سواء كان المسار غير صالح أو الصلاحيّات غير كافية أو حدث عطل في القرص.12 ومحلّيّاً لا تسبِّب «false يعني غير موجود» أيّ إشكال يُذكَر، لكن عند التعامل مع المشاركة، تُختزَل «الملفّ غير موجود» و«تعذّر الوصول إلى الخادم/نقص الصلاحيّات» جميعها إلى false، ما يعني أنّ الشيفرة التي تتفرّع بناءً على Exists لاتّخاذ قرار عمل تنتهي بشكل طبيعيّ باعتبار ‘لا يوجد هدف’ عند حدوث عطل في الشبكة ── وهو نوع مزعج من الانكسار الصامت. التصميم الأسهل تشخيصاً عند التعامل مع المشاركة هو فتح الملفّ مباشرةً دون فحص وجود مسبق، والتمييز عبر الاستثناء.

ثالثاً، قد لا يكون الطرف الآخر موجوداً بعد. فقد يسبق تشغيل الخدمة جاهزيّة الشبكة مباشرةً بعد إقلاع الآلة، وقد يكون خادم الملفّات نفسه قيد إعادة التشغيل. وبما أنّ الاتّصال الدائم (persistent connection) آليّة تُستعاد عند تسجيل دخول المستخدم21، فلا ضمانة في عالم الخدمات الذي لا يمرّ بتسجيل دخول على أنّ «المشاركة ستظهر بمجرّد بدء التشغيل». السلوك الصحيح هو الانتظار مع إعادة المحاولة، لا التحقّق من الاتّصال مرّة واحدة عند بدء التشغيل والإنهاء عند الفشل.

بدمج هذه الاعتبارات الثلاثة، يصبح الهيكل الأساسيّ لجانب الكتابة كما يلي:

// إعادة المحاولة للأخطاء الشبكيّة المؤقّتة فقط، والفشل الفوري لأخطاء العمل
private static async Task WriteToShareAsync(string finalPath, byte[] content, CancellationToken ct)
{
    var dir = Path.GetDirectoryName(finalPath)!;
    var tempPath = Path.Combine(dir, $"~{Guid.NewGuid():N}.tmp");

    try
    {
        for (var attempt = 1; ; attempt++)
        {
            try
            {
                await File.WriteAllBytesAsync(tempPath, content, ct);
                File.Move(tempPath, finalPath); // نشر "الاكتمال" عبر rename داخل نفس المجلّد
                return;
            }
            catch (IOException ex) when (attempt < 5 && IsRetryable(ex))
            {
                // إعادة محاولة الأعطال الشبكيّة المؤقّتة فقط بتراجع أُسّيّ (بحدّ أقصى)
                _logger.LogWarning(ex, "فشلت الكتابة إلى المشاركة (المحاولة رقم {Attempt}). تجري إعادة المحاولة", attempt);
                await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, attempt)), ct);
            }
        }
    }
    catch
    {
        // عند الاستسلام وتمرير الاستثناء، نظِّف الملفّ المؤقّت بأفضل جهد ممكن
        try { File.Delete(tempPath); } catch { /* يُتجاهَل فشل التنظيف */ }
        throw;
    }
}

// يصل IOException بنفس النوع سواء كان السبب "انقطاع الشبكة" أو "وجود ملفّ بنفس الاسم في الوجهة" أو "امتلاء القرص".
// عبر الـ 16 بت السفلى من HResult (رمز خطأ Win32)، نختار فقط الأعطال المؤقّتة التي تفيد إعادة المحاولة معها
private static bool IsRetryable(IOException ex)
{
    var win32 = ex.HResult & 0xFFFF;
    return win32 is 53   // ERROR_BAD_NETPATH: تعذّر العثور على مسار الشبكة
              or 59   // ERROR_UNEXP_NET_ERR: خطأ شبكة غير متوقّع
              or 64   // ERROR_NETNAME_DELETED: لم يعد اسم الشبكة متاحاً
              or 121; // ERROR_SEM_TIMEOUT: انتهاء المهلة (ما يُعرف بمهلة السيمافور)
}

من المهمّ أيضاً عدم الاكتفاء بكتابة «أعِد المحاولة إن كان IOException» بشكل غير متأنٍّ. فبما أنّ الفشل الذي لا تتغيّر نتيجته مهما أُعيدت المحاولة ── كوجود ملفّ بنفس الاسم في الوجهة، أو طول المسار المفرط، أو امتلاء القرص ── يصل أيضاً بنفس نوع IOException، فإنّ الحكم استناداً إلى نوع الاستثناء وحده يعني إعادة محاولة خطأ دائم 5 مرّات والانتظار بلا فائدة عبر التراجع الأُسّيّ. اختر «الفشل الذي تفيد إعادة المحاولة معه» فقط عبر رموز خطأ Win32 (مثل 53/59/64/121، من فئة انقطاع الوصول إلى المشاركة والمهلات الزمنيّة22) كما في المثال أعلاه. والممارسة الواقعيّة هي إضافة الرموز المُلاحَظة في سجلّات البيئة الفعليّة تدريجيّاً.

النقطة الأهمّ ليست إضافة إعادة المحاولة بحدّ ذاتها، بل إقرانها ببروتوكول تسليم آمن حتّى مع إعادة المحاولة (temp -> rename، وidempotency). فإذا انقطع الاتّصال أثناء الكتابة، يبقى ملفّ ناقص. وإذا ضُمن أنّ الاسم النهائيّ لا يظهر إلّا عبر «rename بعد الإغلاق»، أمكن منع قراءة الطرف المستقبِل لملفّ غير مكتمل («نيء»). ولمّا كان انهيار العمليّة أو انقطاع الطاقة يمنع تنفيذ التنظيف داخل الشيفرة نفسها، فمن المفيد أيضاً تجهيز تنظيف دوريّ في مجلّد التسليم («حذف ما لم يُحدَّث من ملفّات ~*.tmp منذ مدّة معيّنة») لتفادي انسداد المجلّد بتراكم المخلَّفات. الصورة الكاملة لتصميم هذا التسليم مرتَّبة في «أساسيّات التحكّم الحصريّ في تكامل الملفّات».

توجد نقطة انتباه أخرى خاصّة بالتعامل عبر الشبكة: نتيجة «الفشل» لا تضمن أنّ الفشل قد حدث فعلاً. فإذا انقطع الاتّصال بعد اكتمال rename على جانب الخادم مباشرةً، يمكن أن يعود استثناء إلى العميل رغم أنّ ملفّ الاسم النهائيّ قد نُشر بالفعل. وإذا أُعيدت المحاولة دون تفكير في هذه الحالة، فإمّا أن تتعثّر بخطأ «يوجد بالفعل ملفّ بنفس الاسم في الوجهة»، أو أن يُنشَر نفس البيانات مرّتين إذا كان الطرف المستقبِل قد استوعب النسخة الأولى بالفعل. الحلّ هو التحقّق من حالة الوجهة ومطابقتها قبل إعادة محاولة فشل مرحلة rename. فإذا تضمّن الاسم النهائيّ معرِّف معالجة (رقم فاتورة أو GUID تنفيذ)، أمكن الحكم بأنّ «وجود ملفّ نهائيّ بنفس المعرِّف يعني أنّ rename السابقة نجحت فعلاً» والخروج باعتبارها نجحت، كما يمكن للطرف المستقبِل أيضاً رفض الاستيعاب المزدوج عبر تكرار المعرِّف.

6. التحكّم الحصريّ عبر SMB وموثوقيّة FileSystemWatcher

6.1. لا يجوز الإفراط في الثقة بالقفل (lock)

يُلمَس المجلّد المشترك من عدّة عملاء في وقت واحد. الفتح بـ FileShare.None يجعل الحصريّة تعمل حتّى عبر SMB أثناء فترة الفتح، لكنّ التصميم المعتمِد على «إن أمكن أخذ القفل فالأمر آمن» ينهار عند فقدان المقبض (handle) بسبب الانقطاع، أو عند وجود طرف لا يأخذ القفل أصلاً (نسخ يدويّ، نظام آخر). ينبغي أن يكون جوهر الحصريّة ليس قفل نظام التشغيل، بل جانب بروتوكول التسليم: temp -> rename، والمطالبة الذرّيّة (فقط الفائز بعمليّة rename من incoming إلى processing يعالج الملفّ)، وidempotency. نترك تفاصيل هذا التفكير لمقال التحكّم الحصريّ المذكور أعلاه.

6.2. واقع استخدام FileSystemWatcher مع مشاركة بعيدة

يُذكَر رسميّاً أنّ FileSystemWatcher يدعم مراقبة الملفّات ليس فقط محلّيّاً، بل على محرك الشبكة والحاسوب البعيد أيضاً.13 استخدامه بحدّ ذاته أمر مشروع. لكن توجد فرضيّتان تخصّان الموثوقيّة.

  • تصل الإشعارات عبر مخزن مؤقّت، وتُفقَد الأحداث عند فيضانه. إذا تركّزت التغييرات في وقت قصير، تُفقَد الأحداث التي تتجاوز حجم المخزن المؤقّت.13
  • يُقيَّد الحدّ الأقصى لـ InternalBufferSize بـ 64 كيلوبايت في المراقبة عبر الشبكة. وهو قيد موثَّق يعني إمكانيّة أقلّ للتوسّع مقارنةً بالمراقبة المحلّيّة.13

علاوة على ذلك، شاهدنا مراراً في ميدان تحقيق الأخطاء عرضاً مفاده أنّه عند حدوث إعادة تشغيل أو انقطاع للخادم على الطرف الآخر من المشاركة، تموت المراقبة بصمت ولا تصل أيّ إشعارات. الخلاصة العمليّة هي التعامل مع FileSystemWatcher على مشاركة بعيدة باعتباره «مجرّد تلميح للتنبّه المبكِّر» فقط، واعتماد المسح الكامل الدوريّ (polling) عند بدء التشغيل وعند حدوث خطأ وبشكل دوريّ باستمرار كمصدر الحقيقة. أنماط التصميم بما فيها التعامل مع تكرار الأحداث واختلال ترتيبها مفصَّلة في «دليل FileSystemWatcher العمليّ».

7. القواعد الثابتة في الممارسة العمليّة (جدول القرار)

نلخِّص فيما يلي محتوى ما سبق في جدول قرار حسب كلّ نقطة يتردَّد فيها المصمِّم.

نقطة النقاش الخيارات معيار الحكم
طريقة حمل المسار حرف القرص / مسار UNC المسار الذي يتعامل معه التطبيق يجب أن يكون UNC دائماً. اعتبر حرف القرص مجرّد راحة على شاشة المستخدم1
الكتابة إلى المشاركة كتابة مباشرة / temp -> rename الكتابة المباشرة لا تضمن «عدم قراءة ملفّ في منتصف الكتابة». الأساس هو التعهّد بأنّ الاسم النهائيّ = الاكتمال
كشف الملفّات الجديدة FileSystemWatcher وحده / الاستطلاع الدوريّ (polling) / الجمع بينهما افترض فقدان أحداث في المشاركة البعيدة. إن كانت فترة دقائق مقبولة، فالاستطلاع الدوريّ وحده أبسط وأمتن. وإن لزمت الفوريّة، فاجمع بينهما13
حساب تشغيل الخدمة LocalSystem / حساب نطاق / gMSA إن وُجد وصول إلى مشاركة، فاستخدم حساب نطاق أو gMSA. وإن كانت البيئة تدعم gMSA أمكن إلغاء إدارة كلمة المرور بأكملها8
عند الحاجة إلى بيانات اعتماد منفصلة استدعاء net use برمجيّاً / WNetAddConnection2 / تسجيل مسبق عبر cmdkey net use داخل خدمة غير موصى به. وبما أنّ تعدّد بيانات الاعتماد تجاه نفس الخادم يتعثّر بالخطأ 1219، تجنَّب ذلك منذ مرحلة التصميم عبر تجميع الحسابات أو اسم مستعار في DNS19
الوصول المباشر من عدد كبير من العملاء كلّ جهاز يصل عبر UNC مباشرةً / عبر خدمة وسيطة (API) عندما يصبح حاصل ضرب عدد الأجهزة × بيانات الاعتماد × الوصول المتزامن غير قابل للإدارة، اجمع التعامل مع المشاركة في خدمة واحدة، واجعل العملاء يتحدَّثون عبر API

نضيف توضيحاً للسطر الأخير. التكامل عبر المجلّد المشترك سهل، لكن كلّما زاد عدد مصادر الوصول، ازدادت إدارة «من يملك أيّ صلاحيّة» و«من يتعارض مع مَن» صعوبة بشكل أُسّيّ. إذا تجاوز الأمر حجماً معيّناً، اجمع العمليّة التي تلمس خادم الملفّات في خدمة Windows واحدة، ودَع كلّ عميل يتحدَّث معها عبر HTTP أو gRPC. تخفيض رتبة المجلّد المشترك من «واجهة بين الأنظمة» إلى «تفصيل تنفيذ داخليّ لتلك الخدمة» هو الشكل الأسهل صيانةً على المدى الطويل.

8. الخلاصة

  • حرف القرص ليس سوى رمز يخصّ جلسة تسجيل الدخول. عدم ظهوره لمستخدم آخر أو عمليّة مرفوعة الصلاحيّات أو خدمة هو سلوك مقصود بالتصميم، والطريق الصحيح هو كتابة التطبيق بمسار UNC. EnableLinkedConnections حلّ التفافيّ غير مدعوم.
  • الوصول إلى المشاركة من خدمة يتطلّب UNC إلزاميّاً. تُحدَّد هويّة المصادقة بحساب التشغيل: LocalSystem/NetworkService ببيانات اعتماد الحاسوب، وLocalService ببيانات مجهولة. الممارسة العمليّة هي منح صلاحيّات المشاركة لحساب نطاق أو gMSA.
  • الاتّصال المتزامن بأكثر من بيانات اعتماد تجاه نفس الخادم يفشل بالخطأ 1219 (سلوك مقصود). تجنَّب ذلك منذ مرحلة التصميم عبر تجميع الحسابات أو اسم مستعار في DNS.
  • المشاركة «بطيئة، تنقطع، وقد لا تكون موجودة» بوصف ذلك حالة طبيعيّة. انقطاع الخمول (افتراضيّاً بعد 15 دقيقة) ليس عطلاً، ولا يمكن تمييز false في File.Exists عن عطل الشبكة. أدرِج إعادة المحاولة مع temp -> rename وidempotency معاً.
  • يدعم FileSystemWatcher المراقبة البعيدة، لكن بسبب فقدان الأحداث عند فيضان المخزن المؤقّت والحدّ الأقصى البالغ 64 كيلوبايت، فإنّ ضمّ المسح الكامل هو القاعدة الثابتة.
  • عند التردّد، ارجع إلى جدول القرار في الفصل السابع. وإذا كبر الحجم، ففكِّر أيضاً في تصميم يجمع العمليّة التي تلمس المشاركة في خدمة وسيطة.

مقالات ذات صلة

مجالات الاستشارة ذات الصلة

تتعامل شركة Komura Soft LLC مع تصميم وتنفيذ أنظمة تكامل الملفّات عبر المجلّد المشترك، ودراسة تكوين الوصول والمصادقة المرافقة لتحويل التطبيق إلى خدمة Windows، وتحقيق الأعطال من نوع «لا تظهر المشاركة بعد التحويل إلى خدمة» أو «يفشل فقط في بيئة معيّنة».

المراجع

  1. Microsoft Learn، Services and Redirected Drives. حول كون حرف القرص لا يُخصَّص عالميّاً على مستوى النظام بل لكلّ جلسة تسجيل دخول، وأنّ الخدمة لا يمكنها الوصول إلى حرف قرص في جلسة أخرى ويجب أن تستخدم اسم UNC، وأسباب عدم استحسان ربط الأقراص عبر net use ودوال WNet داخل الخدمة (تسرّب بيانات الاعتماد، التداخل بين الخدمات، وغيرها)، وأنّ الخدمات المُعدَّة بحساب مستخدم يُنشأ لها أيضاً جلسة تسجيل دخول جديدة، وأنّ انتحال هويّة العميل (client impersonation) مُوصى به بدلاً من احتفاظ الخدمة ببيانات الاعتماد.  2 3 4 5 6 7 8 9 10 11

  2. Microsoft Learn، Mapped drives are not available from an elevated prompt when UAC is configured to Prompt for credentials. حول إنشاء جلستَي تسجيل دخول مرتبطتين عند تفعيل UAC، وكون جوهر ربط القرص كائن رابط رمزيّ (DosDevices) خاصّ بجلسة تسجيل الدخول ولا يُشارَك بين الجلسات، وكون EnableLinkedConnections يفرض كتابة الرابط الرمزيّ في كلتا الجلستين.  2 3

  3. Microsoft Learn، Folder Redirection fails to apply when redirected to mapped drive letter, instead of UNC path. حول إنشاء LSA رمزَي وصول عند تسجيل دخول مسؤول وربط القرص بالرمز القياسيّ، وخطوات ضبط قيمة تسجيل EnableLinkedConnections والتحذير بأنّ «هذا قد يجعل النظام غير آمن، ولا تدعمه Microsoft»، وأنّ استخدام مسار UNC بدلاً من حرف القرص مُوصى به دائماً.  2 3

  4. Microsoft Learn، LocalSystem Account. حول امتلاك LocalSystem صلاحيّات واسعة محلّيّاً، وتقديمه بيانات اعتماد الحاسوب إلى الخادم البعيد على الشبكة.  2 3

  5. Microsoft Learn، NetworkService Account. حول امتلاك NetworkService حدّاً أدنى من الصلاحيّات محلّيّاً، وتقديمه بيانات اعتماد الحاسوب إلى الخادم البعيد على الشبكة.  2 3

  6. Microsoft Learn، LocalService Account. حول تقديم LocalService بيانات اعتماد مجهولة على الشبكة.  2

  7. Microsoft Learn، About Service Logon Accounts. حول تحديد حساب تسجيل دخول الخدمة للسياق الأمنيّ وقت التشغيل، وعدم قدرة الخدمة التي تعمل بالسياق الأمنيّ لحساب مستخدم محلّي على الوصول إلى موارد الشبكة.  2

  8. Microsoft Learn، Group Managed Service Accounts overview. حول كون gMSA حساب نطاق يوفِّر إدارة تلقائيّة لكلمة المرور وتبسيطاً لإدارة SPN، مع إمكانيّة ترك إدارة كلمة المرور لنظام تشغيل Windows.  2 3

  9. Microsoft Learn، The network folder specified is currently mapped using a different user name and password error. حول كون الخطأ الناتج عن محاولة إنشاء عدّة اتّصالات بنفس الخادم ببيانات اعتماد مختلفة سلوكاً مقصوداً بالتصميم (by design)، والحلول الالتفافيّة وهي الاتّصال بعنوان IP أو إنشاء اسم مستعار DNS آخر.  2 3

  10. Microsoft Learn، System Error Codes (1000-1299). حول تعريف ورسالة الخطأ 1219 (ERROR_SESSION_CREDENTIAL_CONFLICT).  2

  11. Microsoft Learn، Mapped drive connection to network share may be lost. حول قطع الاتّصال الخامل افتراضيّاً بعد مهلة 15 دقيقة (autodisconnect)، وظهور علامة × حمراء على أيقونة القرص في مستكشف الملفّات مع إعادة الاتّصال بسرعة عند الوصول.  2

  12. Microsoft Learn، File.Exists(String) Method. حول إرجاع false دون رمي استثناء عند عدم وجود صلاحيّة قراءة، وإرجاع false أيضاً عند حدوث أيّ خطأ أثناء فحص الوجود (مسار غير صالح، عطل قرص، نقص صلاحيّات، وغيرها).  2

  13. Microsoft Learn، FileSystemWatcher Class. حول دعم مراقبة الملفّات على الحاسوب المحلّي ومحرك الشبكة والحاسوب البعيد، وإمكانيّة فقدان الأحداث عند تجاوز حجم المخزن المؤقّت، وكون الحدّ الأقصى لِـ InternalBufferSize هو 64 كيلوبايت في المراقبة عبر الشبكة.  2 3 4 5

  14. Microsoft Learn، WNet Functions. حول كون WNetGetUniversalName دالّة تحصل على اسم بصيغة عالميّة (UNC) من مسار قائم على حرف قرص. 

  15. Microsoft Learn، Secure group managed service accounts. حول كون كلمة مرور gMSA عشوائيّة مكوَّنة من 240 بايت يُغيِّرها نظام التشغيل تلقائيّاً كلّ 30 يوماً، ما يُغني عن تخطيط المسؤول لتغيير كلمة المرور أو إيقاف الخدمة. 

  16. Microsoft Learn، Service Accounts and BITS. حول كون مصادقة LocalSystem/NetworkService على الشبكة تتمّ ببيانات اعتماد الحاسوب، وLocalService ببيانات اعتماد مجهولة، ورفض الوصول عندما تكون قائمة ACL مقصورة على حساب مستخدم، وعدم استحسان استخدام حسابات النظام لمحرك مُعيَّن (mapped drive). 

  17. Microsoft Learn، cmdkey. حول إمكانيّة إنشاء وسرد وحذف اسم المستخدم وكلمة المرور المحفوظَين (بيانات الاعتماد) عبر أمر cmdkey. 

  18. Microsoft Learn، Credentials processes in Windows authentication. حول قيام مدير بيانات الاعتماد بحفظها في حاوية بيانات اعتماد Windows، وتقديمها تلقائيّاً عند المصادقة اللاحقة. 

  19. Microsoft Learn، Client Access to Network Resources. حول ذِكر إنشاء اتّصال عبر WNetAddConnection2 مع تحديد بيانات اعتماد العميل كإحدى استراتيجيّات وصول عمليّة الخادم إلى موارد الشبكة. 

  20. Microsoft Learn، SMB file server share access is unsuccessful through DNS CNAME alias. حول كون أحد أسباب فشل الوصول إلى SMB عبر CNAME هو عدم تسجيل SPN للاسم المستعار، وتوجيه الدليل إلى تعريف الاسم المستعار عبر أمر netdom computername بدلاً من CNAME في DNS. 

  21. Microsoft Learn، Windows Networking Operations. حول كون الاتّصال الدائم (persistent connection) اتّصال شبكة يستعيده النظام تلقائيّاً عند تسجيل دخول المستخدم. 

  22. Microsoft Learn، System Error Codes (0-499). حول تعريف كلّ من ERROR_BAD_NETPATH (53) وERROR_UNEXP_NET_ERR (59) وERROR_NETNAME_DELETED (64) وERROR_SEM_TIMEOUT (121). 

أحدث المقالات التي تشترك في نفس الوسوم. عمّق فهمك بمواضيع مرتبطة.

ترتبط هذه المقالة بشكل طبيعي بصفحات الخدمات التالية.

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

أسئلة شائعة حول موضوع هذه المقالة.

لماذا لا يمكن الوصول إلى محرك الشبكة (Z:) من خدمة Windows؟
لأنّ حرف القرص ليس ملكاً للنظام بأكمله، بل هو وحدة تخصّ جلسة تسجيل الدخول. فـ Z: التي يربطها المستخدم عبر مستكشف الملفّات ليست سوى رمز ينتمي إلى جلسة تسجيل دخول ذلك المستخدم تحديداً، ولا تظهر لخدمة تعمل ضمن جلسة تسجيل دخول مختلفة. وحتّى إن كانت الخدمة تعمل بحساب المستخدم نفسه، فإنّ النظام ينشئ جلسة تسجيل دخول جديدة خاصّة بالخدمة، فلا يُورَث ربط القرص الذي تمّ من سطح المكتب حتّى مع الحساب نفسه. الوصول الصحيح من الخدمة يكون عبر مسار UNC بالصيغة \\server\share.
كيف يمكن الوصول إلى مجلّد مشترك من خدمة تعمل بحساب LocalSystem؟
يُصادَق LocalSystem على الشبكة باعتباره بيانات اعتماد الحاسوب. ففي بيئة نطاق (domain)، يمكن الوصول بمنح الصلاحيّة لحساب حاسوب تلك الآلة (DOMAIN\MACHINE$) على جانب المشاركة (أذونات المشاركة وقوائم NTFS ACL). لكن هذا النهج صعب التشغيل عمليّاً، لأنّ استبدال الآلة يعني إعادة ضبط الصلاحيّات من جديد؛ لذا يُوصى في الممارسة العمليّة بجعل حساب تشغيل الخدمة حساب نطاق أو gMSA (حساب خدمة مُدار جماعيّاً)، ومنح صلاحيّات المشاركة لذلك الحساب.
هل يجوز مراقبة الملفّات الموجودة في مجلّد مشترك باستخدام FileSystemWatcher؟
يمكن استخدامه، لكن لا تعتمد عليه بمفرده. يدعم FileSystemWatcher المراقبة على محرّك الشبكة أو الحاسوب البعيد، إلّا أنّ إشعارات التغيير تصل عبر مخزن مؤقّت (buffer)، فإذا تركّزت الإشعارات في وقت قصير فاض المخزن المؤقّت وفُقدت بعض الأحداث. والأدهى أنّ حدّ المخزن المؤقّت الأقصى يُقيَّد بـ 64 كيلوبايت في المراقبة عبر الشبكة. المعاملة الصحيحة هي اعتبار الإشعار «مجرّد محفّز لإعادة المسح»، مع ضمّ مسح كامل دوريّ (polling) عند بدء التشغيل وعند حدوث خطأ وبشكل دوريّ باستمرار كقاعدة ثابتة.
كيف يمكن جعل تكامل الملفّات مقاوماً لانقطاع الشبكة؟
صمِّم النظام على افتراض أنّ «المشاركة بطيئة، وتنقطع، وقد لا تكون موجودة أصلاً» كحالة طبيعيّة. وبالتحديد: نفِّذ الكتابة باسم ملفّ مؤقّت ثمّ أعِد تسميته لنشره بعد الإغلاق (temp -> rename)، وأحِط عمليّات القراءة والكتابة بإعادة محاولة، مع التمييز بين خطأ الشبكة المؤقّت وخطأ العمل. انتبه إلى أنّ File.Exists يُرجع false حتّى عند تعذّر الوصول، فلا يمكن التمييز بين «الملفّ غير موجود» و«تعذّر الوصول إلى الخادم». والدرع الأخير هو جعل المعالجة idempotency (أي أنّها لا تفسد حتّى لو أُعيد تنفيذها).

الملف الشخصي للمؤلف

صفحة الملف الشخصي لمؤلف المقالة.

غو كومورا

مؤسّس شركة كومورا سوفت ذ.م.م.

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

روابط عامة

العودة إلى المدونة