فری سوئیچ در مقایسه با استریسک چطور عمل میکند؟ چرا شما ایجاد یک نرمافزار جدید را شروع کردید؟ اینها سوالاتی هستند که اخیرا از من پرسیده میشوند و باعث شدند که تصمیم به ارائه پاسخ در مورد آنها به فعالان و مشتاقان در زمینه سیستمهای تلفنی نرمافزاری و کسانی که علاقهمند به دانستن تفاوتهای موجود بین این دو نرمافزار هستند، بگیرم. تجربه بسیار زیادی در زمینه کارکردن با هردوی این نرمافزارها داشتهام، در حدود ۳ سال در زمینه توسعه استریسک و به عنوان بنیانگذار فری سوئیچ. در ابتدا تجربیات خود در مورد استریسک را مطرح نموده سپس انگیزه و نحوه ایجاد فری سوئیچ را شرح خواهم داد.
اولین بار در سال ۲۰۰۳ از استریسک استفاده کردم. در آن زمان نسخه پیش از ۱٫۰ استفاده میشد و آشنایی کمی با VoIP داشتم. پس از دانلود و نصب، از اینکه پس از اتصال تلفن به کامپیوترم صدای بوق در آن شنیده میشد بسیار خوشحال بودم. چند روز مشغول کار با dial plan بودم و ایدههایی که با یک تلفن متصل به لینوکس قابل پیادهسازی بودند اجرایی میکردم. به علت سابقه زیاد در زمینه برنامهنویسی وب انواع ایدهها مانند تطابق شماره تماسگیرنده با شماره اکانت به منظور تشخیص علت تماس و موارد مشابه به ذهنم میرسید. سپس زمانی که قصد استفاده از تطابق الگوهای تماس در dial plan را داشتم نوشتن اولین ماژول را آغاز کردم. به این ترتیب نسخه ابتدایی app_perl، که اکنون res_perl نامیده میشود، شکل گرفت و یک مفسر Perl5 را به استریسک اضافه کرده بودم.
پس از کسب این تجربیات، ایجاد زیرساختی مبتنی بر استریسک را برای صف تماسهای ورودی شروع کردم. نمونه اولیه را بر اساس app_queue و واسط مدیریت استریسک که به اختصار AMI نامیده میشود ایجاد کردم (استفاده از مخفف احساس خوبی ایجاد میکند). واقعا جالب بود! این امکان وجود داشت که از خطوط تلفن با یک خط T1 تماس گرفت و وارد صف تماس شد و اپراتوها نیز پس از تماس وارد صف شده و تماسها را پاسخ میدادند. وقتی که از طریق وب وضعیت صفها و حضور اپراتورها را مشاهده میکردم به نظرم همه چیز عالی بود. حتی صفحه به طور خودکار هر چند ثانیه بروزرسانی میشد اما زمانیکه مرورگر مدت زیادی در حال بارگزاری باقی ماند برای اولین بار این لغت را شنیدم، Deadlock! لغتی که هیچوقت نمیتوانم فراموش کنم.
این اولین بار بود که چنین اتفاقی رخ میداد اما آخرین بار نبود. مطالب زیادی در مورد GNU Debugger یادگرفتم که آغاز اتفاقات زیادی بود. وقوع Deadlock در ماژول صف، Deadlock در ماژول مدیریت و کنسول. این اتفاقات اندکی باعث ناامیدی من شد اما ادامه دادم. در همین حین با Segmentation Fault برخورد کردم که برنامهنویسان با آن آشنایی کامل دارند. پس از حدود یک سال درگیری با باگها، بر زبان c تسلط زیادی پیدا کرده بودم و مهارتم در زمینه دیباگ کردن افزایش پیدا کرده بود. بستر مورد استفاده من شامل ۷ سرور استریسک بود که تعداد زیادی تماس در حد DS3 را سرویسدهی میکرد و مقدار زیادی کد برای استریسک نوشته بودم که کپیرایت آنها را در اختیار دارم http://www.cluecon.com/anthm.html.
در سال ۲۰۰۵ شهرت خوبی بین برنامهنویسان استریسک داشتم. حتی در فایل CREDITS و در کتاب “Asterisk, The Future of Telephony” از من قدردانی شد. علاوه بر نرمافزارهای متعددی که در سورس کد اصلی استریسک داشتم مجموعهای از کدها که برای استریسک نوشته بودم روی سایت خود ایجاد و نگهداری میکردم. (هنوز در آدرس http://www.freeswitch.org/node/50 در دسترس هستند).
علیرغم این تجربیات همچنان نتوانسته بودم از deadlockها و crashها رها شوم. با استفاده از اسکریپتهای راهاندازی مجدد و استفاده از ۷ سرور، به خوبی این مشکلات را مخفی کرده بودم اما هیچ راهی برای توسعه بیشتر بستر مورد استفاده خود پیدا نمیکردم. باید از برخی قابلیتها چشمپوشی میکردم چرا که به علت نحوه طراحی استریسک به درستی عمل نمیکردند.
استریسک ازطراحی ماژولار برخوردار است بهطوریکه یک هسته مرکزی سایر ماژولها را بارگذاری میکند که این ماژولها هر کدام قابلیتهای بیشتری را به سیستم اضافه میکنند. ماژولها برای پیادهسازی پروتکلهای خاصی مانند SIP و یا کاربردهایی نظیر IVRها و هچنین اینترفیسهای خارجی مانند Management Interface استفاده میشوند. هسته استریسک از مدل threading استفاده میکند اما در مصرف threadها بسیار محافظهکارانه عمل میکند. تنها کانالهایی که آغاز کننده تماس و یا اجرا کننده یک کاربرد هستند دارای thread مجزا هستند. طرف B تماس (دریافت کننده تماس) از thread مربوط به طرف A تماس (ایجاد کننده تماس) استفاده میکند مگر اینکه اتفاقی مانند ترانسفر رخ دهد. در این حالت کانال وارد مد thread شده و فرآیندی به نام channel masquerade آغاز میشود. در این فرآیند تمامی دادههای داخلی یک کانال از یک شئ موجود در حافظه به شئ دیگری در حافظه منتقل میشوند. روشی که در توضیحاتی که در کد استریسک آورده شده ناخوشایند توصیف شده است. همین اتفاق در جهت عکس نیز رخ میدهد یعنی زمانیکه که یک thread پس از ایجاد نسخه کپی از یک کانال از بین میرود و کانال اصلی قطع میشود. برای این کار ساختار cdr باید به نحوی تغییر کند که کانال کپی ایجاد شده را به عنوان یک تماس جدید در نظر نگیرد. این باعث میشود هنگام ترانسفر کردن یک تماس ۳ یا ۴ کانال در سیستم مشاهده شود.
بخش زیر از توضیحات موجود درکد استریسک گرفته شده است:
” این یک عمل واقعا کشنده است. ما یک کانال جدید ایجاد کرده و دادههای کانالی که قرار است thread آن ازبین برود را در کانال جدید قرار میدهیم. این کار بعد از حذف دادههای داخلی شئ مربوط به کانالی که جدیدا ایجاد شده است آغاز میشود. مطمئن نیستم که قراره در آینده هم این کار حفظ بشه، چراکه علیرغم قابلیتهای مفید، روش بسیار ناخوشایندی هست.”
این روش برای انتقال یک کانال از یک thread به thread دیگر بوده و سرآغاز بسیاری از مشکلات برای برنامهنویسان است. این روش نامطئن استفاده از threadها یکی از انگیزههای من برای کدنویسی مجدد استریسک بود.
استریسک از linked-listها برای نگهداری دادههای کانالهای موجود در سیستم استفاده میکند که مجموعهای از بخشهای حافظه است که به طور زنجیروار با استفاده از اشارهگرها به هم متصل هستند و قابلیت ایجاد و حفظ کانالها را به طور نامحدود (تنها محدودیت میزان حافظه سیستم میباشد) ایجاد میکنند.
این لیستها در برنامهنویسی کاربردهای بسیاری دارند اما زمانیکه در یک برنامه که threadهای متعددی در آن ایجاد میشود مورد استفاده قرار میگیرند مدیریت آنها بسیار مشکل میشود. در چنین برنامهای نیاز به استفاده از میوتکس میباشد که نقش چراغ راهنما را برای threadها ایفا میکند تا تنها یک thread اجازه دسترسی به لیست را داشته باشد چرا که در غیر این صورت هنگامی یک thread در حال دنبال کردن زنجیره لیست است ممکن است یک thread دیگر ساختار این زنجره را تغییر دهد. نتیجه این اتفاق میتواند بسیار مهلک باشد، به عنوان مثال زمانیکه یک thread در حال از بین بردن یک کانال میباشد ممکن است thread دیگر به آن دسترسی پیدا کند که این امر باعث وقوع segmentation fault شده که نتیجه آن قطع اجرای برنامه و باالتبع قطع تمامی تماسها میشود. اکثر ما پیام Avoiding initial deadlock را دیدهایم، این پیام نشانگر تلاش برای دسترسی انحصاری به یک کانال است که ممکن است تا ۱۰ بار انجام شود و در صورت عدم موفقیت بدون دسترسی انحصاری کانال در اختیار گرفته شود.
واسط مدیریت یا AMI این قابلیت را ایجاد میکند که در سوکت ایجاد شده توسط کلاینت برای کنترل استریسک به طور مستقیم هر گونه دادهای به فرمت Manager Event نوشته شود. این دادهها اغلب خیلی ساختیافته نیستند که همین موضوع تحلیل پیامهای این پروتکل را مشکل میسازد.
هسته استریسک به بعضی از ماژولهای آن وابستگی دارد، نتیجه این وابستگی آنست که استریسک بدون وجود این ماژولها قابل اجرا نیست چرا که بخشی از کد مورد نیاز برای اجرا در فایل مربوط به ماژول مورد نظر قرار دارد. برای برقراری تماس در استریسک، حداقل در نسخه ۱٫۲، هیچ راهی غیر از بکاربردن app_dial و res_features وجود ندارد چرا که کد مربوط به ایجاد تماس در این دو ماژول قرار دارد. منطق ایجاد یک تماس و انجام اعمالی مانند یک تماس با چند مقصد (forked) در app_dial قرار دارد نه در هسته استریسک، و res_features شامل اعمال سطح بالا مانند برقراری ارتباط صدا میباشد.
استریسک هیچ نظارتی بر API خود ندارد. اکثر توابع و ساختمان دادهها دارای دسترسی عمومی هستند و امکان استفاده اشتباه و یا دورزدن روشهای مشخص از پیش تعیین شده وجود دارد. کد هسته بر این فرض استوار است که هر کانال دارای یک file descriptor است در صورتیکه این امر همیشه الزامی نیست. در بسیاری از قسمتهای کد یک الگوریتم که عمل مشخصی را انجام میدهد، در کاربردهای مختلف به اشکال مختلف پیادهسازی و تکرار شده است.
این تنها مختصری از مشکلاتی است که در استفاده از استریسک با آنها روبهرو بودم. وقت خود را به عنوان یک کدنویس و سرورهای خود را برای نگهداری کد استریسک استفاده کردم و در نگهداری کد و رفع باگهای آن نقش داشتم. یک کنفرانس تلفنی هفتگی برای برنامهریزی در مورد آینده استریسک و رفع مشکلاتی که عنوان شد سازماندهی کردم. مشکل این بود که وقتی کسی به لیست تغییرات اساسی که باید صورت گیرند نگاه میکرد و یا به حجم کاری که باید انجام شود و مقدار کدی که باید پاک شده و یا بازنویسی شود فکر میکرد انگیزه خود برای رفع این مشکلات را از دست میداد. من میدانستم که افراد زیادی حاضر به همراهی من در ایجاد نسخه ۲ و بازنویسی کد نیستند. این موارد علت تصمیم من برای انجام چنین کاری در سال ۲۰۰۵ بود.
تمرکز اصلی من در فری سوئیچ این بود که از هسته شروع کرده و تمام اعمال مشترک را در آن قرار داده و در اختیار لایههای بالاتر برنامه قرار دهم. مانند استریسک، وب سرور آپاچی الهام بخش من بود و باعث شد یک طراحی ماژولار را در نظر بگیرم. از همان ابتدا اساس کار این بود که هر کانال thread مربوط به خود را داشته باشد بدون در نظر گرفتن اینکه چه کاری انجام میدهد و آن thread از یک ماشین حالت برای تعیین اعمالی که باید در هسته روی کانال انجام شوند استفاده میکند. این کار باعث میشود تا هر کانال مسیر مشخص و قابل پیشبینی را طی کند. در این صورت میتوان در صورت لزوم برای انجام اعمال مهم در حالتهای مختلفی که کانال در آنها قرار میگیرد توابعی را برای اجرا مشخص کرد، مانند نحوهی عملکرد متدها در ارثبری و برنامه نویسی شئگرا.
انجام این کار آسان نبود. در حین نوشتن کد فری سوئیچ بارها و بارها با مشکلاتی مانند segmentation fault و deadlock مواجه شدم (البته مورد اول بسیار بیشتر از مورد دوم بود). اما کد را از هسته ایجاد کرده و ادامه دادم. از آنجایی که هر کانال دارای thread مجزا می باشد، در مواقعی که نیاز به انجام عملی روی کانال میباشد از lock کردن برای خواند/نوشتن استفاده میشود و به جای استفاده از link list از الگوریتمهای درهمسازی (hashing) استفاده شده. در نتیجه به هیچ عنوان امکان از بین رفتن یک کانال تا زمانی که یک thread به آن دسترسی دارد وجود ندارد. این عمل به تنهایی میتواند نیاز به فرایندی مانند Channel Masquerade را برطرف کند.
از اکثر توابع و اشیاء که در هسته فری سوئیچ وجود دارند در مقابل فراخوانی نادرست محافظت میشود. این کار با مجبور کردن فراخوان به استفاده از روشهای از پیش تعیین شده برای دسترسی صورت میگیرد. هر مفهومی که قابلیت توسعه داشته باشد یا توسط یک ماژول ارائه شود دارای یک اینترفیس کاملا مشخص میباشد که امکان استفاده از آن را فراهم میکند، بنابراین هسته هیچ وابستگی به ماژولها ندارد.
API قابل استفاده در فری سوئیچ به صورت لایه لایه ایجاد شده است، به این صورت که توابع هسته در پایینترین سطح قرار داشته و در لایههای بالاتر تعداد توابع با افزایش قابلیتها کمتر میشود.
به عنوان مثال این امکان وجود دارد که یک تابع طولانی نوشته شود که برای بازکردن و پخش یک فایل صوتی به فرمت دلخواه در یک کانال از یک ماژول استفاده میکند. در لایه بعدی یک تابع ساده وجود خواهد داشت که یک فایل را برای یک کانال پخش میکند و این تابع تبدیل به یک کاربرد ساده میشود که روی یک کانال قابل اجراست. بنابر این شما میتوانید آن را در dial plan یا در کد c خود استفاده کنید و یا در صورت لزوم یک ماژول دلخواه بنویسید که فایل را باز کرده و با استفاده از سایر ماژولهای پخش فایل آن را در کانال پخش نمائید.
فری سوئیچ شامل اینترفیسهای مختلفی برای دستههای مختلف ماژولها میباشد.
Dialplan: نحوه برخورد با تماس را مشخص کرده و اطلاعات تماس را دریافت و مقصد تماس را مشخص میکند.
Endpoint: اینترفیس پروتکلهایی مانند SIP، TDM و . . .
ARS/TTS: تشخیص گفتار و خواند متن
Directory: جستجوی پایگاه داده با فرمت درخواستهایی مشابه با LDAP
Events: ماژولها میتوانند رخدادهای پیشفرض تعریف شده در هسته و یا رخدادهای مختص خود را وارد سیستم رخداد فری سوئیچ نمایند. این رخدادها توسط مصرف کنندههای مورد نظر که میتوانند سایر ماژولها باشند قابل دریافت و استفاده هستند.
Event Handlers: دسترسی به رخدادها و CDR از راه دور
Formats: پخش فایلها با فرمتهای مختلف مانند wav
Loggers: ثبت لاگ در کنسول یا فایل
Languages: استفاده از زبانهای embedded مانند Python و Javascript برای ارتباط با فری سوئیچ و استفاده از API فری سوئیچ
Say: ماژولهای مربوط به زبانهای مختلف برای ایجاد و پخش پیام
Timers: شمارندههای دقیق برای کاربردهایی مانند تعیین زمانبندی بین بستهها
Applications: کاربردهای قابل اجرا روی یک تماس، مانند پیامگیر
FSAPI (FreeSWITCH API interface) : توابع قابل اجرا در کنسول، توابع XMLRPC، توابعی از نوع CGI و توابعی که در dial plan قابل اجرا هستند و ورودی و خروجی آنها به صورت رشته است.
XML: روشهایی برای دسترسی به تنظیمات XML از طریق هسته وجود دارد که امکان بروزرسانی پیکربندی و تنظیمات فری سوئیچ را به صورت بلادرنگ و در حین اجرا فراهم میکنند.
ارتباط و تعامل بین همهی ماژولهای فری سوئیچ تنها از طریق API هسته و سیستم رخداد داخلی آن میباشد. تلاش بسیاری برای انجام این کار و جلوگیری از رفتار ناخواسته ماژولهای خارجی انجام شده است.
سیستم رخداد در فری سوئیچ تا حد ممکن با هدف ایجاد امکان پیگیری وقایع ایجاد شده است. طراحی فری سوئیچ با این فرض انجام شده است که بیشتر کاربران این نرمافزار از راه دور به آن متصل میشوند و یا از یک ماژول دلخواه برای دسترسی به اطلاعات تماس استفاده میکنند. بنابراین هر چیز مهمی که در فری سوئیچ اتفاق میافتد باعث ایجاد یک رخداد میشود. ساختار رخدادها بسیار شبیه ایمیل میباشد که دارای چندین سرآیند و متن اصلی هستند. رخدادها را میتوان به صورت یک رشته با فرمت استاندارد و یا XML نمایش داد. میتوان به تعداد دلخواه ماژول ایجاد کرد که به سیستم رخداد فری سوئیچ متصل شوند و رخدادهایی در مورد presence، وضعیت تماس و خطاهای موجود در سیستم دریافت کنند. ماژول mod_event_socket که جزو ماژولهای اصلی فری سوئیچ میباشد امکان برقراری اتصال TCP و دریافت رخدادها را به منظور استفاده و ثبت لاگ فراهم میکند. به علاوه از همین اینترفیس میتوان برای ارسال دستورات کنترل تماس و یا پخش و دریافت صوت به صورت دو طرفه استفاده کرد. این ارتباط میتواند هنگام برقراری یک تماس (ایجاد سوکت به یک سرور خارجی برای دریافت اطلاعات مورد نیاز برای مسیریابی تماس و سایر اعمال) ایجاد شود و یا توسط یک کاربر از راه دور به صورت ورودی برقرار شود.
یک مفهوم مهم دیگر در فری سوئیچ XML Registry متمرکز میباشد. زمانیکه فری سوئیچ اجرا میشود یک فایل xml اصلی را باز و پس از پارس کردن محتوای آن در صورت لزوم چندین فایل xml دیگر را باز کرده و مقادیر موجود در آنها را وارد حافظه میکند (این کار بدون محدودیت قابل تکرار است). برخی از این مقادیر متغیرهایی هستند که پس از بارگذاری، در سایر فایلهای xml نیز قابل دسترسی هستند. به عنوان مثال با استفاده از قطعه xml زیر میتوان یک متغیر global را مقداردهی کرد:
<X-PRE-PROCESS cmd=”set” data=”moh_uri=local_stream://moh”/>
پس از این قطعه xml در تمام خطوط بعدی هر جایی که $${moh_uri} قرار گرفته باشد مقدار local_stream://moh با آن جایگزین میشود. در نهایت بخشهای که وارد رجیستری شده و توسط ماژولها و هسته قابل استفاده هستند موارد زیر هستند:
Configuration: دادههای مربوط به پیکربندی فری سوئیچ که رفتار نرمافزار را تعیین میکنند.
Dialplan: نمایش dial plan به شکل xml که توسط mod_dialplan_xml برای مسیریابی تماسها و اجرای کاربردها روی تماسها استفاده میشود.
Phrases: xmlهایی که برای ایجاد قابلیت چندزبانه بودن و پخش عباراتی که شامل چند فایل صوتی هستند، استفاده میشوند.
Directory: مجموعهای از domainها و کاربرها که برای registration و مدیریت اکانتها استفاده می شود.
با استفاد از ماژولهای ارتباط با XML، شما میتوانید ماژول خود را به XML Registry متصل کرده و به صورت بلادرنگ اطلاعات مورد نظر را هنگام ایجاد تماس، به جای فایلهای XML استاتیک موجود استفاده کنید. با استفاده از این قابلیت قادر خواهید بود که registration را به صورت کاملا پویا انجام داده و یا پیامگیرهای صوتی و پیکربندی چندین نمونه فری سوئیچ در حال کار را به طور پویا ایجاد نمائید، درست مانند مدل استفاده شده در ارتباط مرورگرها با یک برنامه CGI.
با استفاده از زبانهایی که در فری سوئیچ embed شدهاند مانند Javascript، Java و Perl این امکان وجود دارد که کاربردهایی به شکل اسکریپت ایجاد کرد که میتوانند قابلیتهای موجود در فری سوئیچ را در قالب زبانهای سطح بالاتر بهکار گیرند.
اولین جمله در تعریف پروژه فری سوئیچ این بود که یک هسته پایدار ایجاد شود تا بر مبنای آن سایر کاربردهای قابل توسعه ایجاد شوند. خوشحالم از اینکه اعلام کنم این پروژه در تاریخ ۲۶ می ۲۰۰۸ با ارائه نسخه ۱٫۰ فری سوئیچ به نام phonix کامل خواهد شد. به شهادت کسانی که نسخه فعلی را تست کردهاند کارآیی فری سوئیچ در مقابل استریسک در شرایط مشابه تا ۱۰ برابر بهتر گزارش شده است.
امیدوارم این توضیحات برای مشخص نمودن تفاوتهای فری سوئیچ و استریسک کافی و علل آغاز پروژه فری سوئیچ را روشن کرده باشند. به علت درگیری گسترده با پروژه استریسک همواره به عنوان یک برنامهنویس آن باقی خواهم ماند و برای همه افراد آن پروژه در طراحی آینده آن آرزوی موفقیت دارم. حتی در صورت امکان برای نشان دادن حسن نیت خود در رابطه با استریسک، که شروع کار من در عرصه تلفن بود، کدهای قدیمی خود را برای عموم در دسترس قرار خواهم داد.
استریسک یک PBX کدباز است و فری سوئیچ یک سوییچ نرمافزاری کدباز٫ فضای بسیار زیادی برای هر دو نرمافزار در بین سایر نرمافزارها مانند Call Weaver، Bayonne، sipX، OpenSER و سایرین وجود دارد. امیدوارم هر ساله با توسعهدهندگان این نرمافزارها و پروژهها در ClueCon در Chicago ملاقات و گفتگو داشته باشم. http://www.cluecon.com
ما میتوانیم الهامبخش یکدیگر برای پیشرفت صنعت تلفن باشیم. مهمترین سوالی که شما میتوانید از خود بپرسید اینست که “آیا این ابزار مناسب کار شما هست؟”