يوميات مقالات تعليقات تعليقات خارجية
 
السلام عليكم، أهلا بك في صفحتي الشخصية... الساعة الآن 11:26 AM دقيقة بتوقيت الرياض
 
 

ملاحظة: الموضوع برمجي بحت! المعذرة لغير المبرمجين،،،

اليوم تم إرسالة الحلقة الثانية من مسلسل ليه أنا للمشتركين في القائمة البريدية، و هذه الرسالة تختلف عن الرسائل التي قبلها في أن القائمة البريدية و صل مشتريكها لعدد يحمل ثلاث أصفار :)... بكلام آخر،،، تم القيام بـعملية أختبار قدرة Robustness Test للقائمة البريدية :)....

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

 في الأيام الأولى لتطوير الموقع، قال لي ثامر انه يريد قائمة بريدية في الموقع، و وقتها حاولت أن أتهرب، لأنني أعرف ان هذه القائمة ستسبب لي بالكثير من المتاعب، فكتابة قائمة بريدية سهل جدا، و لكن هل ستكون هذه القائمة قادرة على تحمل مشتركين بالالاف؟؟!!،،

هذا كان السؤال الحقيقي،،،و هذا ما كان يخيفني،،،،

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

طبعا قمت بتصميم هذا الجدول، و اسميته Outgoing_Emails و تمت إضافة الحقول الخاصة بأي رسالة بريد في هذا الجدول، و هي كالتالي: :


From: المرسل
To: المرسل إليه
CC: نسخة كربونية
BCC: نسخة كربونية مخفية
Subject: الموضوع
Body: نص الرسالة
Attachments: المرفقات
صورة1: جدول Outgoing_Emails

بالإضافة لهذه الحقول، أعتقدت انني احتاج إلى حقل إضافي، و هو SendAttempts يوضح كم محاولة تمت لإرسال الرسالة، لأنه يمكن ان يحدث الكثير من الأخطاء خلال عملية الإرسال، فالرسالة تحتوي على مرفق Attachment كبير الحجم، و يمكن ان يكون حجم بريد المستقبل قد و صل إلى سعته العليا Maximum Usage، لذلك، يجب أن اعرف كم محاولة تمت لإرسال رسالة معينة، و بعد أن تتجاوز عدد المحاولات رقم معين threshold، سوف ارمي هذه الرسالة في سلة المهملات، لأنها ببساطة، غير قابلة للإرسال!... و من الأفضل أن يقوم صاحب البريد بتسجيل ايميل جديد في gmail ثم يعيد تسجيل نفسه!..

بعد ذلك قمت بتصميم الجدول المشتركين في القائمة، و هو جدول بسيط، يحتوي على ثلاث حقول فقط، و هي كالتالي:


Email: البريد الإلكتروني
JoinDate: تاريخ الإشتراك في القائمة
MailListName: اسم القائمة البريدية
صورة2: جدول MailList

 أعتقد انكم ستسغربون الحقل الأخير - اسم القائمة البريدية MailListName -، هذا الحقل قم بإضافته لأنه من المناسب ان اقوم ببرمجة قوائم بريدية متعددة و ليس قائمة بريدية واحدة، بمعنى انه يمكن ان يشترك زوار الموقع في قائمة "الحلقات الجديدة" أو قائمة "الأخبار الجديدة" حسب اهتمام المشترك،،،، و بهذا الشكل، تكون القائمة البريدية متعددة الإستخدام, و مرنة جداً..

 حتى الان كلام جميل، و لكن لم أغطي كل جوانب المشكلة، فالمشتركين تم حفظهم، و الرسائل التي سوف ترسل لهم بإذن الله تم حفظها في طابور Queue لتنتظر فيها وقت إرسالها، و لكن كيف سأقوم بتعبئة هذا الطابور، و متى و كيف سيتم إرسالها بالفعل؟؟.... هنا، أخذت المشكلة شكل آخر،،،،

في تطبيقات الويب بشكل عام، في الـ ASP.NET بشكل خاص، يتم تنفيذ الأكواد الخاص بالصفحات عندما يقوم زائر فضولي بزيارة موقعك، و لا يمكنك تنفيذ أكواد خارج نطاق هذا السياق التنفيذي Execution Context...

اممممم،، اسف، أعتقد أن هذا الكلام غير صحيح، خصوصا مع بداية نقل مرض المسالك التنفيذية الإضافية Background Threads إلى عالم تطبيقات الويب،، حيث أستطيع استخدام كلاس System.Thread، في صنع مسلك Thread خارج نطاق HttpContext، و اجعله يقوم بهذه المهمة الصعبة!!!..
و لكن أين و متى سأقوم بصنع هذا المسلك Thread السحري!!!؟؟ ... من هنا،،، دخلت HttpModule في الصورة... حيث انها المكان الوحيد، الذي تستطيع التلاعب فيه على قوانين اللعبة D:.... فهي تعمل عند بداية تشغيل ال AppDomain الخاص بالموقع، حيث يقوم ASP.NET Runtime بمناداة الدالة Init و يسمح لها بالقيام بالتعبير عن مشاعرها عند تشغيل الموقع....

قمت بكتابة HttpModule اسميته EmailModule، يقوم بعمل Timer عند مناداة الدالة Init، بالشكل التالي:

private static Timer _timer;
public void Init(HttpApplication HttpApplication)
{
     if (_timer == null)
     {
         _timer = new Timer(new TimerCallback(SendEmails), 
             HttpApplication.Context, #Interval#, #Interval#);
     }
}      
كود 1: تهيئة عمل EmailModule.

هنا تستطيع ملاحظة الدالة SendEmails، هذه الفنكوشة سيتم تنفيذها بشكل متكرر حسب الوقت المحدد في البراميتر #interval#... و فيها سأقوم بقراءة الرسائل المسجلة في الجدول Outgoing_Emails،، ثم أقوم بإرسالها....

طبعا يمكن أن يكون الجدول فارغ و ساعتها لن تقوم الفنكوشة بعمل شيء، و ستنتظر فترة من الوقت لتنفيذها مره أخرى لإعادة التشييك على الجدول، أو يمكن أن تجد الجدول ممتليء بالرسائل، و في حينها ستأخذ مجموعة من الرسائل و سترسلها، و عدد هذه المجموعة يمكن التحكم فيه بوضعه كمتغير محفوظ في ملف الإعدادات web.config، بقي أن اضيف نقطتين في غاية في الأهمية، الأولى أن الفنكوشة SendEmails ستعمل خارج نطاق ASP.NET Runtime، بمعنى أن جمل التتبع Trace و رسائل الأخطاء Debug Messages لن تظهر في صفحة Trace.axd، و هذا سيجعلني مثل الأعمى! لا أعرف ماذا يحدث داخل هذا الفنكوشة، و من هنا نشأت فكرة الكلاس ExceptionLogger، و لكن هذه الكلاس لوحدها، موضوع آخر،،،، يمكن ان اتكلم عنها في موضوع آخر،



صورة 2: عملية إرسال الرسالة.

النقطة الثانية، و هي نقطة معروفة لأغلب مبرمجي تطبيقات الويب بال ASP.NET، و هي أن الـ AppDomain الخاص بالموقع سيكون عرضه لعملية تدوير recycle من ASP.NET Runtime، و في هذه الحالة سيموت ال Timer، و لكن  و لله الحمد، ففريق  ASP.NET ذكيين بما فيه الكفاية بحيث يقومون بمناداة الدالة Init عند كل عملية تدوير Recycle، و لكن للأسف عملية التدوير recycle تكون أحيانا عبارة عن عملية تنويم Shutdown، خصوصا عندما تتوقف الطلبات للموقع، و هي عملية يقوم بها ASP.NET Runtime لتخفيف الحمل على الخادم، و هي تعني ببساطة، أن الفنكوشة SendMails لن تنادى ما دام التطبيق نائم، و في هذه الحالة ليس بيدي عمل أي شيء، و هي خارج نطاق السيطرة، و لكن الأمل كبير في انه سيكون هناك زائر فضولي آخر في كل نصف ساعة قادمة D:...


public static void SendEmails(object sender)
{

EmailQueue.SendQueuedMail();

}

كود 2: فنكوشة SendEmails التي يناديها المؤقت Timer كل فترة.

public static void SendQueuedMail()
        {
            string SmtpServer = EmailProvider.Instance().SmtpServer;
            string UserName = EmailProvider.Instance().UserName;
            string Password = EmailProvider.Instance().Password;
            int Port = EmailProvider.Instance().Port;
            EnumEmailFormat format1 = EmailProvider.Instance().EmailFormat;
            int emailPerCycle = EmailProvider.Instance().EmailsPerCycle;
            int maxAttempts = EmailProvider.Instance().EmailAttempts;
            // قائمة بالرسائل التي أرسلت بنجاح، نحفظها هنا بشكل مؤقت
            //  لحذفها لاحقا من الجدول
            ArrayList sentEmails = new ArrayList();
            // قائمة بالرسائل التي فشل إرسالها، نحفظها هنا بشكل مؤقت لكي 
            // نقوم بزيادة عدد محاولات الإرسال لاحقاً
            ArrayList failedEmails = new ArrayList();
            // سحب مجموعة من الرسائل لإرسالها الان
            EmailCollection collection1 = 
                    EmailQueue.SelectQueuedEmails(emailPerCycle);

            if (collection1 != null)
            {
                int count = collection1.Count - 1;
                for (int i = 0; i <= count; i++)
                {
                    try
                    {
                        MailMessage message1 = new MailMessage(
                            collection1[i].EmailFrom,
                            collection1[i].EmailTo);
                        message1.Subject = collection1[i].EmailSubject;
                        message1.Body = EmailQueue.CleanEmailBody(
                            collection1[i].EmailBody,
                            format1);
                        if (format1 == EnumEmailFormat.PlainText)
                        {
                            message1.IsBodyHtml = false;
                        }
                        else
                        {
                            message1.IsBodyHtml = true;
                        }

                        
                        SmtpClient sc = new SmtpClient(SmtpServer, Port);
                        
                        System.Net.Mime.ContentType ct = new 
                                       System.Net.Mime.ContentType();
                        
                        if (collection1[i].EmailAttachment != null &&
                            collection1[i].EmailAttachment 
                                            != string.Empty)
                        {
                            Attachment attach = new Attachment(
                                  collection[i].EmailAttachment,
                                  "audio/x-pn-realaudio"
                                  );
                            message1.Attachments.Add(attach);
                        }
                        // خادم ارسال البريد يحتاج تحقق من المستخدم
                        if (UserName != null)
                        {
                            sc.UseDefaultCredentials = false;
                            sc.Credentials = new 
                                System.Net.NetworkCredential(UserName,
                                Password);
                            sc.DeliveryMethod = 
                                  SmtpDeliveryMethod.Network;
                        }

                        sc.Send(message1);
                        message1 = null;
                        sentEmails.Add(collection1[i].EmailID);
                    }
                    catch (Exception exception2)
                    {
                        ExceptionLogger.LogException(exception2);

                        if (maxAttempts > 0)
                        {
                            if (collection1[i].EmailAttempts
                                     >= maxAttempts)
                            {
                                // considered it sent,, 
                                // coz it failed many times
                                sentEmails.Add(collection1[i].EmailID);
                            }
                            else
                            {
                                // adde it to failed list,,, 
                                // to update Email Attempts later
                                failedEmails.Add(collection1[i].EmailID);
                            }
                        }
                        
                    }
                }

                if (sentEmails.Count > 0)
                {
                    EmailQueue.Delete(sentEmails);
                }

                if (failedEmails.Count > 0)
                {
                    EmailQueue.UpdateSendAttempts(failedEmails);
                }
            }
        }      

كود 3: الدالة SendQueuedMail الدالة التي تقوم بالإرسال فعلياً.

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

لم أفكر بصراحة في هذا الموضوع بشكل كبير، فقد قمت بكل استهتار بإستخدام الكلاس الرائعة من مايكروسفت ManagedThreadPool، و استخدمها في صناعة مسلك سريع Background Thread، يقوم بعمل نسخة من الرسالة لكل المسجلين في القائمة، و إضافتهم في Outgoing_Emails،، و هذه النقطة بالذات هي ما سبب فشل القائمة اليوم :( .....
ياااب،،، فشلت اليوم القائمة البريدية في إرسال الرسالة لجميع المشتركين، لم يتم إرسال الرسالة إلا لستمائة مشترك فقط!،، و البقية نساهم هذا المسلك السريع اللعين لسبب ما لا أعرفه، و عندي إحساس كبير انه بسبب سحب كامل المسجلين دفعة واحدة،،،، كان يجب أن اقوم بسحبهم حبة حبة و برفق، تماما كما فعلت في الرسائل، و لكن كيف؟،،، هذا ما أعمل عليه الأن.... و أتمنى أن انتهي منه قبل نزول الحلقة المقبلة من المسلسل،،،، و إلا سأغني و أرقص،، و أقول مثل طلال،، ليييه أنااا؟

مقالات مفيدة في نفس الموضوع:
- مقال جيد عن HttpModule، بقلم جورج شيبرد #
- كيف ترسل ايميل في ASP.NET 2 بقلم مبرمج هندي ما أعرف أنطق اسمه #
- مقال آخر من سكوت ميتشل #
- مقال عن كيفية تجاوز عملية التحقق Authentication التي يقوم بها SMTP Server #

أي اسئلة حول برمجة قائمة بريدية خاصة بك،، أحنا في الخدمة D:

تقبلوا تحياتي،،،
نشر بتاريخ Saturday, January 27, 2007 10:46 AM

التعليقات

# re: القائمة البريدية الحمقاء! Mohannad 1/27/2007 10:29 PM

مقال رائع كالعادة والله يعينك على هذي القائمة

تحياتي ...


# re: القائمة البريدية الحمقاء! حرباز - باسم السلوم 1/28/2007 10:31 AM

ماشاء الله تبارك الله عليك حسام

الله يعطيك الف عافية ..


استفدت كثير .. وان شاء الله تشوف الي كلمتك مره عنه :D


# re: القائمة البريدية الحمقاء! سوالفي 1/28/2007 12:07 PM

أمم القوائم البريدية شغلتها شغلة وتأثر على السيرفر كثير خصوصا لما يكون مشترك وماتقدر تتحكم في عدد الرسايل المسموح فيها بس شغل نظيف ومتعوب عليه صراحة :) أذكر مرة سويت قائمة كل اللي احتجته فورلوب وفنكوشة الارسال خخخخخ بس كانت قائمة صغيرة مافيها الا كم واحد :)

فيه قائمة قوية ومشهورة مكتوبة بالبيرل على ما اظن اسمها سبسكرايب مي أو زي كذا لو تشوفها ممكن تفيدك

وبعدين ليش تعتمد في تشغيل الملف على دخول زائر ليش ماتستخدم الكورن جوب ..

وبالتوفيق :)


# re: القائمة البريدية الحمقاء! بطاطس 1/29/2007 6:15 AM

حسام

انت بطاطس

يعطيك العافية والله يصبر ثامر عليك :p


وعندي سؤال سأوجهه لك عندما أقابلك بس ذكرررني


بطاطس والأجر على الله


# re: القائمة البريدية الحمقاء! حسام 1/29/2007 11:04 PM

مهند,,,
حياك الله D: ، و بصراحة يحرجني حضورك الدائم، و كلماتك اللطيفة دائماً،،،

حرباز،،
الله يعافيك، و احنا بالخدمة يا افندم، أنت أشر بس،،، و على فكرة، ترى أخوك ما عنده رأس، وش هاللي كلمتني عنه؟ ..

سوالفي،،
شيكت على سبسكرايب مي، و لقيتها بفلوس،
قسم بالله إغراء، يعني شكلي بأنزل هالقائمة البريدية بفلوس، و ابيعها، خصوصا انه ما فيه قائمة محترمة مجانية بال ASP.NET، إذا عندك علم عن وحده، لا تبخل علينا،،
امممم الكورن جوب في لينكس ما للأسف ما هي موجودة في ويندوز :)... بس على فكرة دخول الزائر ما له علاقة في تشغيل الملف!، و جود طلب جديد كل نصف ساعة مهم لأن ASP.NET Runtime تطفي البرنامج لما ما يكون فيه اي زيارات للموقع،،
و EmailModule تشتغل بمؤقت Timer خاص فيها يعني ماني محتاج مؤقت خارجي...

بطاطس....
"وعندي سؤال سأوجهه لك عندما أقابلك بس ذكرررني"
جالس تقرأ كتاب عربي فصيح هاليومين؟

حسام والأجر على الله D:
تحياتي للجميع


# re: القائمة البريدية الحمقاء! أيمن 2/3/2007 1:08 PM

هلا أبومقحم ..

شغل ..

أعتقد ممكن تحل مشكلة النصف ساعة انك تشغل صفحة عشوائية من داخل السيرفر بنفسك .. يعني كل 25 دقيقة مثلاً ..

أعتقد ممكن راح تحل المشكلة .. وما راح تكون بحاجة إلى متطفلين :)


# re: القائمة البريدية الحمقاء! أيمن 2/3/2007 1:20 PM

ملاحظة بسيطة ..

الحين كل واحد راح يكون له Entry في الجدول Outgoing Emails ..

هذا راح يزيد الضغط على السيرفر .. وممكن تسوي تعديل بسيط .. راح يخفف الضغط على السيرفر ..

لأن كل واحد راح يكون له انتري كاملة .. من مرفقات وعنوان ونص و و و و..

ليش مايكون فيه جدول باسم Messages .. وكل Outgoing mail .. راح يكون فيه انتري تشير إلى المستخدم اللي راح ترسل له .. وللرسالة .. وحالة الارسال ..

هذي مجرد ملاحظة .. ويمكن تكون ناقصة .. فأنا لا أملك فكرة كاملة عن المشروع ..


ملاااااااااحظة بسيطة ..
الكابتشا .. لما تمر فترة طويلة شوي .. واضيف موضوع .. يصادف Null Pointer Exception .. تأكد من التشييك على الـ Session ..


وتحيااااتي ..
أيمن ،،،


# re: القائمة البريدية الحمقاء! حُس حُس 2/4/2007 3:13 PM

امممممم,,, كلامك صحيح أبو لمن،، ممكن استفيد من المجدول Scheduler و اخليه يختلق Session جديدة و همية علشان ما يحصل Shutdown للـ AppDomain....
بس بيني و بينك، و دي يصير Shutdown لل AppDomain...

بالنسبة لخطأ Null Pointer Exception فسبق ان قاله لي الأخ بطاطس، بس للأسف عجاز اصلحه هنا، مع انه تم تصليحه لموقع ليه أنا...

دمتم بود جميعاً...


# ablink95@yahoo.com ابوانيسة 4/17/2007 8:06 PM

كيف اضع القائمة البريدية في موقعي ما هو الكود النهائي من فضلك


# esam_2025@hotmail.com esam 6/9/2007 5:49 PM

dfd dfd dfdfd


# re: القائمة البريدية الحمقاء! الكاسر 7/17/2007 2:00 PM

رووووووووووعه

ايميلي alkasir20@hotmail.com


# hamad321@gawab.com حمد يحياء 9/2/2007 9:55 AM

بدريه


# re: القائمة البريدية الحمقاء! لميس 7/15/2008 9:36 AM

eelkhteeb2007@hotmail.com

هذا ايميلي


# re: القائمة البريدية الحمقاء! فواز 10/17/2008 6:32 AM

هذا ايميلي fo.az.20@hotmail.com


# re: القائمة البريدية الحمقاء! الكاسر 11/18/2008 6:50 AM

يبيبيب


العنوان  
الإسم  
الموقع
التعليق   
نص الصورة:
 • التصفح
 » RSS
 

 • المقالات

 » ASP.NET










 • الأرشيف





















 • اليوميات












 • الصور



جميع الحقوق محفوظة،
حسام المقحم 2006م