پروژهء کلاینت پروتکل STUN

این پروژه رو بعنوان قسمتی ضروری از یک پروژهء ارتباط P2P تهیه کردم. در واقع یک زیرپروژه هست. اما بنظرم چیز خوبی از آب درآمد و کار مفیدی بود؛ بخاطر همین بصورت شیء گرا و تر و تمیز درآوردم و در اینجا اون رو ارائه میکنم.

الگوریتمی که در اینجا ارائه میشه یک کلاینت برای پروتکل STUN هست. STUN پروتکلی هست که برای گرفتن آدرس و پورت عمومی قابل استفاده برای یک برنامه استفاده میشه. وقتی که برنامه پشت NAT قرار داره (دقت کنید که اکثر مودمهای ADSL هم بخاطر تنظیم بودن روی حالت PPPoE، یک NAT رو پیاده سازی میکنن)، آدرس محلی و آدرس عمومی اون متفاوت هست و از آدرس عمومی خودش اطلاعی نداره. برای برقراری ارتباط P2P ما نیاز داریم که آدرس عمومی برنامه (IP و Port) رو پیشاپیش بدست بیاریم و بعد اون آدرس رو از راه جانبی ای در اختیار برنامهء دیگر که میخواد با برنامه ارتباط مستقیم (بدون سرور واسط) برقرار کنه قرار بدیم. کار پروتکل STUN صرفا اطلاع دادن IP و پورت خارجی برنامه بهش هست.
در برنامه های P2P از پروتکل UDP بخاطر بعضی خصوصیاتش و امکان موفقیت زیادی که در برقرار ارتباط دو طرفه داره زیاد استفاده میشه. البته این پروتکل در کاربردهایی مثل صدا و تصویر زنده از طریق اینترنت هم کاربرد زیادی داره و بنابراین از دو جهت مورد نیاز و استفاده هست.
ما این کار رو (گرفتن پیشاپیش آدرس و پورت خارجی قابل استفاده توسط برنامه) نمیتونیم با سرورهای معمولی وب انجام بدیم. بخاطر همین از یک سرور STUN استفاده میکنیم. در کلاسی که بنده ساختم تعدادی سرور STUN عمومی/مجانی تعریف شدن که میتونید براحتی از اونها استفاده کنید، اما اگر بخواید یا نیاز بود میتونید آدرس یا پورت دیگری رو هم برای برقراری ارتباط معرفی کنید.

ناگفته نماند که پیاده سازی الگوریتمی که تمامی امکانات و حالتهای پروتکل و سرورهای STUN رو پشتیبانی بکنه از پروژه ای که بنده انجام دادم پیچیده تر و خیلی حجیم تر هست؛ بنده نوعی رو پیاده کردم که برای کاربرد بنده کافی بود؛ یعنی یک ارتباط UDP رمزگذاری نشده؛ و فکر میکنم در خیلی از کاربردهای دیگر هم متداول هست و کاملا کفایت میکنه.

پروتکل STUN ابتدا در RFC 3489 تعریف شد (تاریخش رو 2003 زده)، اما بعدا (2008) RFC 5389 جایگزین اون شده که تغییرات و اصلاحاتی و اعمال کرد. البته این دو RFC تاحد زیادی با هم سازگار نگه داشته شدن. اگر به موردی برخورد کردید که الگوریتم بنده با سرور STUN خاصی کار نمیکرد، خوبه که بهم اطلاع بدید. بنده این الگوریتم رو اساسا با مطالعهء RFC 5389 تهیه کردم؛ بنظرم کم و بیش سازگاری خوبی داره، و فکر میکنم با سرورهای RFC 3489 هم باید بدون مشکل کار کنه. البته یک مورد خاص بود که در RFC 5389 نیامده بود که چون با تست و بررسی فهمیدم که سرورهای مورد نظر دارن از اون روش و کد خاص برای ارسال آدرس XOR شده استفاده میکنن، اون رو به برنامه اضافه کردم.

لینک دانلود برنامه

و اینهم قطعه کد مثالی برای طرز استفاده:

import stun_client
import socket

udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

sc=stun_client.STUNClient()
sc.print_debug_msgs=True

try:
print(sc.get_public_address_of_udp_socket(udp_socket))
except Exception as e:
print('\nAn exception occured in get_public_address_of_udp_socket:\n', e)

input("\nhit Enter to quit...")

به متد get_public_address_of_udp_socket سوکتی رو که میخواید برای ارتباط ازش استفاده کنید پاس میکنید و این متد بهتون IP و پورت خارجی اون Socket رو بصورت یک Tuple برمیگردونه؛ البته درصورت موفقیت. بخاطر خطاهای پروتکل و خطاهای سرور و کلاینت و کانکشن اینترنت و غیره، فراخوانی این متد رو در بلاک try قرار بدید و exception های ایجاد شده رو هندل کنید.

موقع ایجاد یک شیء از کلاس STUNClient میشه یک آدرس (IP یا نام دامین) و بعدش بصورت اختیاری یک پورت رو پاس کرد تا الگوریتم از اون سرور خاصی که شما معرفی میکنید استفاده کنه. پورت روی پورت پیشفرض پروتکل STUN هست و نیازی نیست شما پورت رو هم مشخص کنید، مگر اینکه پورت غیراستانداردی رو مجبور باشید یا به دلیل دیگری بخواید معرفی کنید.
ضمنا با پاس کردن صرفا یک عدد صحیح از صفر تا ایندکس آخرین سروری که در stun_servers_list در کلاس STUNClient تعریف شده که محتوی آدرس سرورهای عمومی/مجانی STUN هست، میتونید سرور مورد نظر برای برقراری ارتباط رو انتخاب کنید (اگر یک سرور جواب نمیداد، این روش راحتی برای تعویض سرور هست). پیشفرض سرور اول هست که ایندکس اون عدد صفر هست.

چنتا مثال میزنم تا روشن بشید:

STUNClient('stun.example.com')
STUNClient('stun.example.com', 10000)
STUNClient(5)
STUNClient(3, 40005)

دست آخر باید بگم نظر به اینکه هیچ نمونه کدی رو برای پیاده سازی این الگوریتم جایی مطالعه نکردم، نمیتونم بگم کدم ممکن نیست اشتباهی داشته باشه. اما تاجایی که تست کردم به خوبی کار میکنه.
بخاطر این این کد رو با پایتون نوشتم چون برای نوشتن برنامهء آزمایش ارتباط P2P خودم، پایتون رو بخاطر سادگی و خوانایی و سرعت توسعه انتخاب کردم. باید بگم علاوه بر ویژگیهای دیگر، اسکریپتی بودن این زبان باعث میشه بخصوص در برنامه های نمونهء اولیه (Prototype) و آزمایش الگوریتمهای مختلف و نو که نیاز به بارها تغییر و اجرا دارن، سرعت توسعه واقعا بالا بره و زحمت برنامه نویس کم بشه. خیلی ساده در یک ادیتور برنامه نویسی سبک هم میشه کدهای پایتون رو نوشت و تغییر داد و فقط Save کرد و بعدش هم فوراً اجرا!
البته پایتون زبانی نیست که بگیم کاربرد و مزایاش فقط Prototype و تست هست. بلکه بنظرم خیلی از برنامه ها رو هم میشه بصورت کامل و نهایی و حتی برای همیشه با پایتون نوشت و استفاده کرد.

با زبانهای دیگر مثل سی و سی++ حتما کتابخانهء آماده برای استفاده از پروتکل STUN وجود داره، اما با زبان پایتون خبر ندارم هست یا نه. بهرحال خواستم این رو خودم بنویسم چون بنظرم تجربهء خوبی بود و تحلیل و کدنویسیش باعث تقویتم میشد. خیلی وقته همش تئوری کار کردم و کدنویسی خیلی کم داشتم و دنبال اینطور پروژه های غیر حجیم ولی در عین حال جذاب، خلاق، و نو میگردم چون احساس میکنم باید با کار عملی آموخته های خودم رو تثبیت کنم و محک بزنم.

پاسخ دهید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

*

شما می‌توانید از این دستورات HTML استفاده کنید: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>