برنامه نویسی شبکه در پایتون

برقراری ارتباطات در دنیای شبکه و اینترنت را میتوان مهم ترین رکن اساسی به وجود آمدن این مفاهیم و ساختارها دانست. جهت ایجاد ارتباط بین دو مقصد می بایست بر بستر شبکه برنامه نویسی کنیم، در اصل در هر مقصد برنامه ای اجرا میکنیم که به عنوان یک پردازه (process) شناخته میشود.

 

سوکت چیست ؟

سوکت (socket) در حقیقت یک لینک ارتباطی بین دو پردازش (process) یا به طور دقیق تر بین دو نخ (thread) بوده که برنامه ها میتوانند از طریق آن با یکدیگر ارتباط برقرار کنند. این دو برنامه (یا thread) میتوانند روی یک سیستم و یا روی دو سیستم مختلف در مکان های متفاوت باشند؛ مهم این است که یک ارتباطی از طریق شبکه با یکدیگر داشته باشند.

سوکت ها به عنوان لینک های ارتباطی بین دو کاربرد استفاده میشوند تا امکان انتقال اطلاعات بین آن دو فراهم شود. به عنوان مثال، یک برنامه با معماری کلاینت سروری (سرویس دهنده – سرویس گیرنده یا Client-Server) را در نظر بگیرید. سرور منتظر دریافت درخواست از سمت کلاینت و در نهایت واکنش نشان دادن به آن است و سرویس گیرنده نیز با ارسال درخواست به سرویس دهنده، خدماتی را دریافت میکند.

 

سوکت نویسی (برنامه نویسی سوکت)

هنگامی که در یک برنامه مخواهیم از سوکت ها برای برقرار کردن ارتباطات استفاده کنیم، می بایست سوکت نویسی (برنامه نویسی سوکت – socket programming) کنیم.

در فرآیند سوکت نویسی در ابتدا اطلاعات مربوط به مقصدی که میخواهیم به آن متصل شویم را تعریف کرده و سپس تلاش میکنیم تا به آن متصل شویم؛ پس از موفق بودن اتصال، میتوانیم اطلاعات مورد نظر خود را میان این دو، تبادل کنیم.

 

چگونه سوکت نویسی را شروع کنیم ؟

برای ایجاد سوکت، به اطلاعات مقصد در ارتباط مورد نظر نیاز خواهیم داشت. در یک شبکه کامپیوتر، هر نود با آدرسی موسوم به آدرس IP شناخته میشود که میتوان با استفاده از آن مقصد را شناسایی و بسته ها را بر روی شبکه به سمت آن ارسال کرد.

همانطور که گفته شد، سوکت به یک پردازش یا در حالت کوچکتر به یک نخ (thread) تخصیص می یابد. در صورتی که صرفاً از یک آدرس IP برای شناساسیی مقصد استفاده کنیم، بر روی هر سیستم کامپیوتری، در هر زمان حداکثر میتوان یک سوکت داشت و به عبارتی فقط یک برنامه قابلیت استفاده از شبکه را دارا بود؛ به همین دلیل، هر سوکت با پورت مشخصی در آن سیستم شناخته شده و در اصل هر سوکت به همراه یک پورت مشخص، به thread ما تخصیص خواهد یافت.

 

معماری کلاینت سروری در ارتباطات شبکه

هنگامی که دو نود در یک شبکه (روی یک یا دو سیستم) میخواهند با هم ارتباط برقرار کنند، می بایست ساختاری رعایت شود، این ساختار معروف است به معماری سرویس دهنده – سرویس گیرنده (Client – Server)

به زبان ساده، در این معماری یکی از نودهای طرفین ارتباط میخواهد سرویسی را ارائه دهد که به آن سرور (Server) نیز گفته میشود؛ نود دیگر در آن سوی ارتباط، میخواهد خدماتی را در این ارتباط دریافت کند و به آن کلاینت یا مشتری (Client) گفته میشود.

شما زمانی که آدرس این صفحه را در مرورگر خود وارد یا روی لینک این صفحه کلید کردید، درخواستی از سمت شما به سرور ما ارسال شده و درخواست محتوای موجود در این صفحه را کرده اید. سرور میزبان سایت ما، محتوای صفحه (در ساختار کدهای html و css) را در پاسخ به درخواستتان برای شما ارسال کرده است.

یعنی در این ارتباط، کامپیوتر شما سرویس گیرنده و سرور ما سرویس دهنده بوده است.

 

برای درک بهتر کدهای ارائه شده در این مقاله آموزشی، سعی شده در قالب آموزش، یک سیستم چت کلاینت سروری بسیار ساده پیاده سازی شود. در این سیستم، یک سرور و یک مشتری داریم؛ کاربر پیام Hello را به سرور ارسال کرده، سرور این پیام را دریافت و چاپ میکند؛ در نهایت پیامی مبنی بر دریافت درخواست به سمت کلاینت ارسال میکند.

 

سوکت نویسی با پایتون

برای برنامه نویسی سوکت در زبان پایتون، میتوان از کتابخانه socket استفاده کرد. کتابخانه socket یک کتابخانه ساده برای برقراری ارتباطات بر بستر شبکه و سوکت نویسی می باشد.

 

ایجاد سوکت جدید

برای ایجاد سوکت از تابع socket() در کتابخانه اضافه شده استفاده میکنیم.

 

این تابع چهار ورودی میگیرد که هر چهارتای آن ها به صورت پیشفرض تعریف شده، اما بهتر است دو مورد اول آن را تعیین کنیم.

  • socket_family : این مورد خانواده نوع آدرس دهی برای ارتباط را مشخص میکند. این ورودی یک مقدار عددی است اما میتوان مقادیر ثابت موجود در کتابخانه socket را نیز استفاده کرد. معمولاً ما در ارتباطاتمان از مقدار ثابت socket.AF_INET برای آدرس دهی IP اینترنتی (Internet Protocol) نسخه 4 و از socket.AF_INET6 برای ارتباط IPv6 استفاده میکنیم.
  • socket_type : مشخص کننده نوع سوکت است. این ورودی یک مقدار عددی است و میتوان برای تعریف آن از مقادیر ثابت موجود در کتابخانه استفاده کرد. نوع سوکت در اصل نشان دهنده نحوه اتصال بین دو نود خواهد بود. معمولاً از مقدار socket.SOCK_STREAM برای ارتباط TCP و از socket.SOCK_DGRAM برای اتصال UDP استفاده میشود.

 

توابع مربوط به سوکت سرور

bind

با استفاده از تابع bind() که روی شئ ساخته شده از سوکت صدا زده میشود، میتوان آدرس IP و پورت مورد نظر برای فعال شدن روی آن را تعیین کرد. توجه داشته باشید که پورتی که وارد میشود باید بر روی سیستم سرور آزاد بوده و توسط برنامه دیگری استفاده نشود. (همانطور که میدانید پورت های 1 تا 1024 به صورت پیشفرض توسط سیستم عامل رزرو شده اما میتوانید پورت های 1025 تا 65536 را بررسی کنید)

 

listen

این تابع میتواند محدودیتی برای تعداد کانکشن های ایجاد شده با سرور را مشخص کند. حداقل مقدار تعریف شده برای آن 0 و سقف آن با توجه به منابع سیستم سرور تعیین میشود. در صورتی که سرور این تعداد کانکشن را برقرار کرده باشد، در صورت دریافت درخواست برای ایجاد کانکشن جدید، آنرا رد خواهد کرد.

ما در مثال مربوط به سیستم چت، مقدار 1 را برای این مورد در نظر میگیریم.

 

accept

این تابع درخواست هایی که برای سرور ارسال میشود را دریافت کرده و در صورت توان قبول خواهد کرد. برنامه بر روی تابع accept() به صورت بلاک شده خواهد ماند تا زمانی که درخواستی به سرور ارسال و از سمت سرور پذیرفته شود.

پس از دریافت و قبول یک ارتباط (connection)، این تابع یک تاپل (tuple) را به عنوان خروجی باز میگرداند؛ این تاپل دو مقدار دارد، مقدار اول شئ کانکشن و مقدار دوم تاپل حاوی مشخصات کلاینت متصل شده در ارتباط می باشد.

 

توابع سوکت کلاینت

connect

با استفاده از تابع اتصال، سرویس گیرنده میتواند درخواست ایجاد کانکشن را به سرور مقصد ارسال کرده و ارتباط خود را با آن آغاز کند.

ورودی تابع connect() یک زوج دوتایی مرتب است که مشخص کننده آدرس IP و پورت مقصد می باشد. (ورودی یک tuple است.)

 

توابع اصلی سوکت

توابع زیر را میتوان در هر دو طرف ارتباط (سرور یا کلاینت) استفاده کرد.

recv

این تابع که بر روی کانکشن ایجاد شده صدا زده میشود، مقدار ارسال شده در اتصال TCP را دریافت کرده و به عنوان خروجی به ما خواهد داد. هنگام استفاده از این تابع، برنامه روی این خط بلاک شده و تا زمانی که پیامی در ارتباط دریافت نکند، پیشروی نخواهد کرد.

به عنوان ورودی اول تابع recv() یک مقدار عددی دریافت میکند؛ این عدد (buffer size) نشان دهنده تعداد حداکثر بایت هایی است که باید از خروجی بخواند. یعنی مشخص میکنیم حداکثر چند بایت از پیام دریافت شده را بخواند.

خروجی تابع به صورت byte است؛ پس اگر بخواهیم آن را به عنوان یک رشته استفاده کنیم، بهتر است تابع decode() را روی آن صدا بزنیم.

 

send

تابع send() یک ورودی به صورت اجباری میگیرد که همان اطلاعاتی است که باید به مقصد (آن سوی ارتباط TCP) ارسال شود.

ورودی تابع می بایست به صورت byte و با encode مشخص باشد؛ بنابراین در صورتی که بخواهیم یک رشته را ارسال کنیم، باید بر روی رشته مورد نظر ابتدا تابع encode() را صدا زده و خروجی آن را ارسال کنیم.

 

recvfrom

این تابع برای دریافت پیام در کانکشن ایجاد شده UDP استفاده میشود.

 

sendto

تابع sendto() برای ارسال پیام به مقصد در یک کانکشن UDP استفاده میشود. به عنوان سومین خروجی این تابع، میتوان مقصد (زوج آدرس و پورت) را تعریف کرد.

 

close

در ابتدای ایجاد کانکشن، ما یک کانکشن به آن طرف ارتباط ایجاد کرده و یک پورت را مشغول کرده ایم. با صدا زدن تابع close() روی کانکشن ایجاد شده، کانکشن را بسته و پورت را آزاد میکنیم. بدیهی است که این کار در انتهای کارمان صورت خواهد پذیرفت.

 

ساخت برنامه چت با پایتون

برای ساخت برنامه چت نیاز به دو فایل مختلف داریم، اولی به عنوان سرور (server.py) و دومی به عنوان کلاینت (client.py) شناخته خواهند شد.

ایجاد سوکت سرور و اتصال به سرور

ابتدا می بایست در سرور یک سوکت روی پورت مورد نظر ایجاد کرده، شنود TCP را روی آن فعال کنیم تا منتظر دریافت درخواست ایجاد کانکشن بماند. ابتدا با استفاده از دستور socket() یک سوکت ایجاد میکنیم.

چون قرار هست هر دو فایل روی یک سیستم اجرا شوند و خبری از دو سیستم با دو آدرس IP متفاوت نیست، آدرس مقصد را localhost یا آدرس 127.0.0.1 معرفی میکنیم؛ یعنی مقصد روی همین سیستم است.

 

همچنین در سمت مشتری (client) نیز یک سوکت ایجاد کرده و تلاش میکنیم تا به مقصد localhost و پورتی که در سرور تعریف کرده ایم (در اینجا 14200) متصل شویم.

 

هنگامی که سرور در حال اجرا و منتظر دریافت درخواست ایجاد ارتباط باشد و خط ششم قطعه کد فوق اجرا شود، یک ارتباط TCP بین دو برنامه ایجاد خواهد شد. پس از ایجاد شدن ارتباط، سرور که منتظر دریافت پیام بود، از حالت بلاک از روی خط نهم آزاد شده و ادامه کدها را اجرا میکند.

ارسال و دریافت پیام در شبکه

حال که ارتباط ما به سرور ایجاد شد، میخواهیم از طریق کلاینت پیامی به به سرور ارسال کنیم. پس در ادامه اتصال کانکشن در کلاینت خواهیم داشت.

 

همانطور که گفته شد، پیام ها بر روی سوکت ها به صورت byte ارسال میشوند و می بایست رشته متنی را با استفاده از تابع encode() به رشته ی بایتی تبدیل کنیم.

میخواستم پس از آنکه پیاممان را به سرور ارسال کردیم، منتظر دریافت تایید از سمت سرور باشیم و پیام سرور را نیز دریافت کنیم. این کار را به راحتی با استفاده از تابع recv() انجام خواهیم داد.

 

و در پایان کانکشن ایجاد شده را خواهیم بست.

 

برای دریافت و ارسال پیام در سمت سرور، به طور کاملاً مشابه عمل خواهیم کرد. یعنی در ادامه کدهای قبلی server.py قطعه کدهای زیر را خواهیم نوشت.

 

در قطعه کد بالا، پس از برقراری کانکشن، اعلام میکنیم که یک کلاینت به سرور متصل شد و اطلاعات (آدرس IP و پورت) آن را چاپ میکنیم.

سپس منتظر دریافت پیام از کلاینت خواهیم ماند و پس از دریافت و چاپ پیام دریافت شده، پیام موفقیت آمیز بودن دریافت را برای client ارسال میکنیم.

 

به طور کلی برای یک ارتباط کلاینت سروری بسیار ساده کدهای زیر را خواهیم داشت.

کدهای سرور چت با پایتون

 

کدهای کلاینت چت پایتون

 

سیستم چت پیشرفته

در این مثال، از سمت کلاینت یک پیام ارسال و یک پیام نیز دریافت کردیم. در صورتی که این توابع را درون یک حلقه شرطی گذاشته و تا زمان دلخواهی آن را اجرا کنیم، میتوانیم بار ها و بار ها پیام های مختلفی را به سمت سرور ارسال کرده و برعکس، دریافت کنیم.