سوکت نویسی با پایتون چیست؟ — آموزش از صفر تا صد

۵۲۶۳ بازدید
آخرین به‌روزرسانی: ۲۱ دی ۱۴۰۱
زمان مطالعه: ۲۹ دقیقه
سوکت نویسی با پایتون چیست؟ — آموزش از صفر تا صد

امروزه برنامه نویسی تقریباً در همه بخش‌های علوم کامپیوتر مورد استفاده قرار می‌گیرد. یکی از این مباحث، برنامه نویسی سوکت یا همان سوکت نویسی (Socket Programming) به حساب می‌آید. برنامه نویسی سوکت روشی برای اتصال دو «گره» (Node | دستگاه فیزیکی) در شبکه جهت داشتن ارتباط با یکدیگر است. نود در شبکه‌های کامپیوتری به دستگاه‌هایی گفته می‌شود که امکان اتصال به یکدیگر و تبادل اطلاعات را دارند. از زبان‌های برنامه نویسی مختلفی می‌توان برای برنامه نویسی سوکت یا همان سوکت نویسی استفاده کرد. یکی از این زبان‌های برنامه نویسی، پایتون (Python) است. به دلیل سادگی یادگیری پایتون، سینتکس ساده آن و انواع کتابخانه‌های مختلف، بسیاری تمایل دارند از پایتون در برنامه نویسی سوکت استفاده کنند. بنابراین در این مقاله، از پایه به سوال سوکت نویسی با پایتون چیست ، پاسخ داده شده است.

فهرست مطالب این نوشته

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

سوکت نویسی یکی از فناوری‌های اساسی و مهم برنامه نویسی شبکه‌های کامپیوتری (Network Programming) به حساب می‌آید که با استفاده از زبان برنامه نویسی پایتون نیز می‌توان آن را انجام داد. سوکت نقطه انتهایی یک پیوند ارتباطی دو طرفه بین دو برنامه در حال اجرا در یک شبکه است. «کلاینت» (Client) و «سرور» (Server) می‌توانند با «نوشتن» write() و «خواندن» read()  کدها در سوکت‌هایشان با یکدیگر ارتباط برقرار کنند. استفاده از زبان برنامه نویسی پایتون یکی از ساده‌ترین شیوه‌های شروع ایجاد رابط یا «اینترفیس» (Interface) سوکت‌ها به شمار می‌رود.

ماژول‌های سوکت پایتون، دسترسی به رابط سوکت «توزیع نرم افزار برکلی» (Berkeley Software Distribution | BSD) را فراهم می‌کنند. BSD یک سیستم عامل شبیه به «یونیکس» (Unix) به حساب می‌آید. این رویکرد در همه سیستم‌های مدرن یونیکس، ویندوز، OS/2، «مک BeOS» ،«OS X» و سایر پلتفرم‌های احتمالی در دسترس است. زبان برنامه نویسی پایتون دو سطح از دسترسی به خدمات شبکه را فراهم می‌کند.

سوکت نویی با پایتون چگونه است؟

در سطوح پایین می‌توان به پشتیبانی پایه سوکت‌ها در سیستم عامل اصلی دسترسی داشت که به برنامه نویس امکان پیاده‌سازی کلاینت‌ها و سرورها را برای پروتکل‌های «اتصال گرا» (Connection Oriented) و «بدون اتصال» (Connectionless) می‌دهد. همچنین زبان پایتون دارای کتابخانه‌هایی است که دسترسی سطح بالاتری را به پروتکل‌های شبکه از جمله FTP و HTTP در سطح برنامه‌های خاص خواهد داد. در بخش بعدی مقاله «برنامه نویسی سوکت با پایتون چیست» به بررسی چیستی سوکت پرداخته شده است.

سوکت چیست؟

سوکت‌ها نقطه انتهایی کانال‌های ارتباطی دو طرفه به حساب می‌آیند. سوکت‌ها می‌توانند با برقراری ارتباط در یک فرآیند و ماشین یکسان، در پردازش‌های آن یا پردازش‌های فرآیندهای گوناگون ارتباط برقرار کنند. همچنین سوکت‌ها می‌توانند روی چندین کانال گوناگون از جمله «UDP»، «پروتکل کنترل ارتباط» یا «TCP» و سایر موارد پیاده‌سازی شوند. کتابخانه «Socket» در زبان پایتون کلاس‌های خاصی را برای مدیریت ارتباطات معمولی و همچنین یک واسط عمومی برای مدیریت بقیه ارتباطات فراهم می‌کند. سوکت‌ها دارای اصطلاحات و واژگان مخصوص به خود هستند که در جدول زیر ارائه شده‌اند.

اصطلاحات و توصیف آن‌ها
«دامنه» یا همان «Domain»دامنه به خانواده‌ای از پروتکل‌ها گفته می‌شود که به عنوان ساز و کارهای انتقال مورد استفاده قرار می‌گیرند. این مقادیر ثابت هستند. به عنوان مثال می‌توان به AF_INET،PF_INET،PF_UNIX ،PF_X25 و سایر موارد اشاره کرد.
«نوع» یا همان «Type»Type نوع انتقال بین دو نقطه انتهایی ارتباط‌ها را نشان می‌دهد. به عنوان نمونه نوع «SOCK_STREAM» برای ارتباط‌های پروتکل‌های ارتباط‌گرا و نوع «SOCK_DGRAM» برای پروتکل‌های بدون اتصال استفاده می‌شوند.
«پروتکل» (Protocol)پروتکل معمولاً صفر است و برای شناسایی نوع یک پروتکل در یک دامنه و نوع استفاده می‌شود.
«نام میزبان» یا همان «Hostname»شناسایی واسط شبکه در موارد زیر انجام می‌شود: یک «رشته» یا همان «استرینگ» (String)، می‌تواند نام میزبان، آدرس چهار بخشی عددی یا آدرس «IPV6» بین علامت نقطه (شاید دو نقطه) باشد. رشته <broadcast> ، یک آدرس INADDR_BROADCAST  را مشخص می‌کند. «رشته با طول صفر» (Zero Length String)، «INADDR_ANY» یا یک عدد صحیح را مشخص می‌کند. عدد صحیحی است که به عنوان یک نشانی دودویی با ترتیب بایت میزبان تفسیر می‌شود.
«پورت» (Port)هر سرور به کلاینت‌هایی گوش می‌دهد که یک یا چند پورت (درگاه) را فراخوانی می‌کنند. هر پورت ممکن است دارای یک شماره پورت «Fixnum»، رشته‌ای حاوی شماره پورت یا نام سرویس باشد.

معمولاً یک شبکه دارای دو سوکت یعنی یکی برای ارتباط دستگاه‌ها و یکی برای ارتباط برنامه است. این سوکت‌ها ترکیبی از یک آدرس آی‌پی یا همان IP و یک پورت هستند. یک دستگاه واحد می‌تواند بر اساس شماره پورت‌هایی که استفاده می‌کند، تعداد «n» سوکت داشته باشد. پورت‌های گوناگونی برای انواع مختلف پروتکل‌ها وجود دارند. برای اطلاعات بیشتر در مورد برخی از شماره پورت‌های رایج و پروتکل‌های مرتبط با آن‌ها، جدول زیر ارائه شده است.

پروتکلشماره پورتکتابخانه پایتونعملکرد
HTTP۸۰httplib ،urllib ،xmlrpclibصفحات وب
FTP۲۰ftplib ،urllibانتقال فایل‌ها
NNTP۱۱۹nntplibحذف ارسال کردن اخبار
SMTP۲۵smtplibارسال ایمیل
Telnet۲۳telnetlibخط‌های فرمان
POP3۱۱۰poplibواکشی ایمیل
Gopher۷۰gopherlibانتقال سند

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

برنامه نویسی سوکت چیست؟

برنامه نویسی سوکت یا سوکت نویسی به عملکردی گفته می‌شود که به دو سوکت امکان ارسال و دریافت اطلاعات در لحظه داده شده است. این برنامه نویسی با اتصال دو سوکت یا گره به یکدیگر انجام می‌شود و امکان برقراری ارتباط در «زمان واقعی» (Real Time) را فراهم می‌کند. همچنین این روش یک گزینه عالی برای ساخت تعداد زیادی برنامه است. در سوکت نویسی، یک سوکت یا همان گره به یک پورت خاص در IP گوش می‌دهد، در حالی که سوکت دیگر برای ایجاد اتصال با دیگری ارتباط برقرار می‌کند.

همچنین، در حالی که کلاینت قصد ارتباط برقرار کردن با سرور را دارد، سرور می‌تواند سوکت شنونده باشد. این رویکردها «ستون فقرات» اصلی «مرور صفحه‌های وب» (Web Browsing) را تشکیل می‌دهند. به بیان ساده‌تر، در سوکت نویسی حداقل یک کلاینت و یک سرور وجود دارند. در ادامه مقاله «سوکت نویسی با پایتون چیست» به بررسی دلیل استفاده از سوکت‌ها برای ارسال اطلاعات پرداخته شده است.

چرا از سوکت ها برای ارسال داده استفاده می شود؟

برنامه‌هایی که به اینترنت متصل می‌شوند، دارای رویکردهایی در زمان واقعی (در لحظه) هستند. به همین دلیل، پیاده‌سازی سوکت‌ها در کدهای شبکه، یکی از مسائل مهم آن‌ها به حساب می‌آید. در ادامه برخی از مثال‌هایی ارائه شده‌آند که در آن‌ها از سوکت نویسی با پایتون استفاده می‌شود:

  • صفحات وبی از جمله فیسبوک، Twitch و eBay که نشان دهنده «اعلان‌های لحظه‌ای» (Live Notification) هستند از سوکت نویسی در برنامه‌های خود استفاده می‌کنند.
  • در بازی‌های آنلاینی که دارای چندین بازیکن هستند از سوکت نویسی استفاده می‌شود. به عنوان مثال می‌توان به League of Legends ،WoW و Strike اشاره کرد.
  • در برنامه‌های چت و گفتگو از جمله واتس‌اَپ، WeChat و Slack سوکت نویسی مورد استفاده قرار می‌گیرد.
  • «داشبوردهای داده زمان واقعی» (Real-Time Data Dashboard) مانند Robinhood و Coinbase از سوکت نویسی با پایتون استفاده می‌کنند.
  • در دستگاه‌های «اینترنت اشیا» (Internet Of Things | IOT) از جمله Nest و August Locks برنامه نویسی سوکت مورد استفاده قرار می‌گیرد.

زبان برنامه نویسی پایتون، بر خلاف برخی دیگر از زبان‌ها از جمله «جاوا اسکریپت» (JavaScript) پیاده‌سازی را به صورت همزمان انجام می‌دهد. این یکی از دلایلی به حساب می‌آید که برنامه نویسی «ناهمگام» (asyncio) در پایتون به وجود آمده است. این نوع برنامه نویسی، پایتون را به ویژه برای سوکت نویسی قدرتمندتر می‌کند. با «سوکت‌های جریانی» (Socket Stream)، داده‌ها در هر زمانی ارسال و دریافت می‌شوند.

چرا از سوکت ها برای ارسال داده استفاده می شود؟

در حالتی که برنامه پایتون در میانه پیاده‌سازی کدها باشد، «نخ‌های» (Thread) دیگر می‌توانند داده‌های سوکت جدید را مدیریت کنند. کتابخانه‌هایی مانند asyncio، نخ‌های چندگانه پیاده‌سازی می‌کنند، بنابراین برنامه‌های پایتون می‌توانند به صورت غیرهمزمان یا ناهمگام عمل کنند. در بخش بعدی مقاله «سوکت نویسی با پایتون چیست» به بررسی سوکت‌های «رابط برنامه نویسی کاربردی» (Application Programming Interface | API) پرداخته می‌شود.

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

زبان برنامه نویسی پایتون برای انجام برنامه نویسی سوکت دارای ماژولی به نام «Socket» است. ماژول Socket پایتون رابطی برای «API سوکت‌های برکلی» (Berkeley Sockets API) فراهم می‌کند. در این مقاله برای سوکت نویسی از این ماژول پایتون استفاده می‌شود. «تابع‌ها» و «متُدهای» (Method) اساسی سوکت API این ماژول در ادامه ارائه شده‌اند.

  • socket()
  • bind()
  • listen()
  • accept()
  • connect()
  • connect_ex()
  • send()
  • recv()
  • close()

زبان پایتون یک API مناسب و سازگار را فراهم می‌کند که به طور مستقیم در سیستم نگاشت می‌شود و برنامه‌های مرتبط با زبان C را نیز فراخوانی می‌کند. در بخش‌های بعدی این مقاله، نحوه استفاده از این APIها ارائه می‌شود. این زبان برنامه نویسی دارای کلاس‌هایی است که به عنوان بخشی از کتابخانه‌های استاندارد خود، استفاده از توابع سوکت سطح پایین را ساده‌تر می‌کنند.

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

ماژول socketserver به عنوان «فریمورکی» (Framework) برای سرورهای شبکه در نظر گرفته می‌شود. همچنین ماژول‌های بسیاری وجود دارند که پروتکل‌های اینترنت سطح بالاتر از جمله HTTP و SMTP را پیاده‌سازی می‌کنند. در ادامه این مقاله به بررسی سوکت‌های «پروتکل کنترل انتقال» (Transmission Control Protocol | TCP) برای سوکت نویسی با پایتون پرداخته شده است.

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

زمانی که با استفاده از تابع socket.socket()  یک شی سوکت با نوع مشخص سوکت «socket.SOCK_STREAM» ایجاد می‌شود، پروتکل پیش‌فرضی مورد استفاده قرار گرفته است که همان پروتکل کنترل انتقال یا TCP است. دلیل استفاده از پروتکل انتقال TCP در ادامه ارائه شده است:

  • قابل اعتماد بودن: بسته‌هایی که وارد شبکه می‌شوند، توسط فرستنده تشخیص و مجدداً فرستاده خواهند شد.
  • تحویل داده به صورت سفارشی: داده‌ها به همان ترتیب ارسال شده توسط فرستنده به وسیله برنامه خواهند می‌شوند.

در مقابل این سوکت، سوکت‌های «پروتکل بسته داده کاربر» (User Datagram Protocol | UDP) که با نوع سوکت «socket.SOCK_DGRAM» ایجاد می‌شوند، پروتکل‌هایی قابل اعتماد به حساب نمی‌آیند و ممکن است داده‌های دریافتی گیرنده از فرستنده دارای ترتیب ارسالی ابتدایی نباشند. شبکه‌ها برای رد و بدل ارتباط‌ها نهایت تلاش خود را انجام می‌دهند. اما باز هم ممکن است هیچ تضمینی وجود نداشته باشد که داده‌های ارسالی به مقصد برسند یا همه داده‌های ارسال شده برای گیرنده، دریافت شوند.

دستگاه‌هایی از جمله «مسیریاب‌ها» (Router) و «سوییچ‌ها» (Switch) که در شبکه‌ها مورد استفاده قرار می‌گیرند، محدودیت‌هایی نسبت به سیستم خود دارند، برای مثال ممکن است «پهنای باند» (Bandwidth) محدودی داشته باشند. این دستگاه‌های شبکه دارای پردازنده CPU، حافظه، «گذرگاه» (Bus)، «بافرهای بسته واسط» (Interface Packet Buffer) هستند.

TCP نگرانی‌های متخصصین شبکه را از «اتلاف بسته» (Packet Loss)، رسیدن داده‌ها بدون داشتن نظم اولیه آن‌ها و سایر مشکلاتی برطرف می‌کند که همیشه هنگام برقراری ارتباط در شبکه رخ می‌دهند. برای بهتر متوجه شدن این مفهوم، دنباله فراخوانی‌های سوکت API و «جریان داده» (Data Flow) برای TCP در فلوچارت زیر مورد بررسی قرار می‌گرفته‌اند.

فلوچارت جریان داده TCP | سوکت نویسی با پایتون چیست

در فلوچارت فوق، ستون سمت چپ نشان دهنده سرور و ستون سمت راست کلاینت را نشان می‌دهد. با شروع از سمت بالا و چپ فلوچارت باید به فراخوانی‌های API توجه شود که سرور برای راه‌اندازی سوکت «شنود» (Listening) انجام می‌دهد. در این بخش از توابع و متدهای زیر استفاده می‌شود:

  • socket()
  • bind()
  • listen()
  • accept()

سوکت شنود دقیقاً همان کاری را انجام می‌دهد که از نامش مشخص است. این سوکت به ارتباطات از سمت کلاینت گوش می‌دهد. زمانی که کلاینت متصل می‌شود، سرور .accept() را فراخوانی می‌کند تا ارتباط را تأیید و کامل شود. کلاینت متد connect() را برای ایجاد ارتباط با سرور و «دست‌دهی سه طرفه» (Three Way Handshake) فراخوانی می‌کند. مرحله دست‌دهی از اهمیت بالایی برخوردار است؛ زیرا با استفاده از آن نسبت به وجود بخش‌های قابل دسترسی در شبکه اطمینان حاصل می‌شود. به عبارتی دیگر، به این وسیله، کلاینت می‌تواند به سرور دسترسی پیدا کند و برعکس این موضوع نیز صادق است.

برای مثال امکان دارد که فقط یک میزبان، کلاینت و سرور بتوانند به دیگری دسترسی داشته باشند. در بخش‌های میانی رفت و برگشت این فلوچارت، اطلاعات بین کلاینت و سرور با استفاده از فراخوانی متدهای .send() و .recv() مبادله می‌شوند. در نهایت و در بخش پایانی مفاهیم این فلوچارت، کلاینت و ارتباط‌های سوکت برقرار شده را قطع می‌کنند و می‌بندند. حال پس از بررسی مفاهیم اولیه سوکت نویسی با پایتون، در بخش بعدی شرح روش برنامه نویسی سوکت در این زبان برنامه نویسی ارائه شده است.

در این بخش از مقاله «سوکت نویسی با پایتون چیست» به بررسی سوکت‌های TCP در سوکت نویسی با پایتون پرداخته شد. اکنون در ادامه پیش از پرداختن به آموزش سوکت نویسی با پایتون ، تعدادی از دوره‌های آموزش برنامه نویسی پایتون فرادرس برای علاقه‌مندان به این مبحث معرفی شده‌اند.

فیلم های آموزش برنامه نویسی پایتون

معرفی فیلم های آموزش برنامه نویسی پایتون (Python) — مقدماتی تا پیشرفته فرادرس

دوره‌های آموزش ویدیویی در وب سایت فرادرس بر اساس موضوع در قالب مجموعه‌های آموزشی متفاوتی دسته‌بندی می‌شوند. یکی از این مجموعه‌ها مربوط به آموزش زبان پایتون در سطح‌های مقدماتی تا پیشرفته است با توجه به اینکه این زبان برنامه نویسی در حوزه بسیار گوناگون علوم کامپیوتر مورد استفاده قرار می‌گیرد، این به معرفی مجموعه آموزش‌های ویدیویی پایتون برای یادگیری بیش‌تر این زبان در کاربردهای گوناگون اختصاص دارد. در زمان تدوین این مقاله، مجموعه دوره‌های برنامه نویسی فرادرس حاوی ۲۶۰ ساعت محتوای ویدیویی است و شامل ۴۷ دوره می‌شود. در ادامه برخی از دوره‌های این مجموعه آموزشی به طور خلاصه معرفی شده‌اند:

  • فیلم آموزش برنامه نویسی پایتون Python - مقدماتی (طول مدت: ۱۹ ساعت و ۵۳ دقیقه، مدرس: پژمان اقبالی شمس آبادی): در این فرادرس زبان برنامه نویسی پایتون از پایه‌‌ای‌ترین مباحث تدریس می‌شود و برای دانشجویان و علاقه‌مندان به پایتون مناسب است. برای مشاهده فیلم آموزش برنامه نویسی پایتون Python - مقدماتی + کلیک کنید.
  • فیلم آموزش برنامه نویسی پایتون + مثال های عملی در Python (طول مدت: ۱۳ ساعت و ۲۰ دقیقه، مدرس: دکتر فرشید شیرافکن): با استفاده از این دوره آموزشی، آموزندگان می‌توانند به وسیله زبان پایتون برنامه نویسی کنند. مفاهیم این آموزش با شرح مبانی نظری و سپس با پیاده‌سازی مثال‌های عملی، آموزش داده می‌شوند. برای مشاهده فیلم آموزش برنامه نویسی پایتون + مثال های عملی در Python + کلیک کنید.
  • فیلم آموزش برنامه نویسی شی گرا در پایتون Python (طول مدت: ۷ ساعت و ۲۹ دقیقه، مدرس: دکتر فرشید شیرافکن): در این فرادرس مفاهیم شی گرایی (Object Oriented) در پایتون با ساده‌ترین روش و همراه با ذکر مثال آموزش داده شده است. برای مشاهده فیلم آموزش برنامه نویسی شی گرا در پایتون Python + کلیک کنید.
  • فیلم آموزش کتابخانه های NumPy و Matplotlib در پایتون (طول مدت: ۴ ساعت و ۴۶ دقیقه، مدرس: میترا تجربه کار): این دوره آموزشی برای تکمیل مباحث موجود در دوره پایتون مقدماتی ارائه شده است. همچنین آشنایی با کتابخانه NumPy، بخش جدیدی از برنامه نویسی پایتون را در این دوره به دانشجویان و علاقه‌مندان معرفی می‌کند. برای مشاهده فیلم آموزش کتابخانه های NumPy و Matplotlib در پایتون + کلیک کنید.
  • فیلم آموزش پایتون گرافیکی - رابط های گرافیکی پایتون (طول مدت: ۵ ساعت و ۳ دقیقه، مدرس: سید رضا دهقان): برای برنامه نویسان پایتون، یادگیری حداقل یک واسط گرافیکی (Graphical User Interface | GUI) این زبان برنامه نویسی اهمیت بسیاری دارد. به همین دلیل، در این دوره آموزشی به بررسی واسط‌های گرافیکی پایتون پرداخته شده است. برای مشاهده فیلم آموزش پایتون گرافیکی - رابط‌های گرافیکی پایتون + کلیک کنید.
  • فیلم آموزش پروژه محور Python پایتون - ساخت نرم افزار برای ویندوز و لینوکس در Python (طول مدت: ۹ ساعت و ۳۴ دقیقه، مدرس: محمد حسینی): ابزار توسعه در این دوره آموزشی بر مبنای PyQt است. با استفاده از این فرادرس، علاقه‌مندان با نحوه تولید نرم افزار آشنا می‌شوند و می‌توانند برای هر زمینه‌ای نرم افزار مورد نیازشان را بسازند. برای مشاهده فیلم آموزش پروژه محور Python پایتون - ساخت نرم افزار برای ویندوز و لینوکس در Python + کلیک کنید.

اکنون پس از معرفی برخی از دوره‌های آموزش زبان برنامه نویسی پایتون، بخش بعدی مقاله «سوکت نویسی با پایتون چیست» به آموزش سوکت نویسی با پایتون اختصاص داده شده است.

آموزش سوکت نویسی با پایتون

در بخش‌های پیشین یک توضیح کلی از سوکت‌های API و نحوه برقراری ارتباط کلاینت و سرور ارائه شد. حال در این بخش به آموزش ایجاد اولین کلاینت و سرور پرداخته می‌شود. این آموزش با یک پیاده‌سازی ساده آغاز شده است که سرور به سادگی هر اطلاعاتی را دریافت و برای کلاینت ارسال می‌کند. در ادامه این بخش ابتدا به اصول کلی برنامه نویسی سوکت در پایتون پرداخته شده است.

اصول برنامه نویسی سوکت در پایتون چیست؟

به صورت کلی، زبان پایتون، «کلاس» (Class) سوکتی را فراهم می‌کند که برنامه نویسان می‌توانند به واسطه آن «اشیا سوکت» (Socket Object) را در کدهای منبع خود به راحتی پیاده‌سازی کنند. پیاده‌سازی سوکت‌ها در برنامه در طی سه مرحله کلی زیر انجام می‌شود.

ایمپورت کردن کتابخانه Socket پایتون

برای استفاده از اشیا سوکت در برنامه خود، ابتدا باید کتابخانه Socket در برنامه ایمپورت شود. نیازی نیست که این کتابخانه با استفاده از «مدیر بسته» (Package Manager) نصب شود و جزئی از کتابخانه‌های پیش‌فرض پایتون به حساب می‌آید. برای استفاده از این کتابخانه تنها باید آن را در برنامه ایمپورت کرد:

import socket

ساختن اشیا سوکت

حال با استفاده از کدهای زیر می‌توان اشیا سوکت را در برنامه خود ایجاد کرد:

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

کدهای فوق شی سوکتی را ایجاد و آن را در متغیر «sock» ذخیره می‌کنند. «سازنده» (Constructor) یک نوع پارامتر و خانواده برای سوکت ایجاد کرده و پارامتر خانواده مجموعه‌ای مقادیر پیش‌فرض با «فرمت آدرس اینترنتی» (Internet Address Format) است. نوع پارامتر در مجموعه‌ای از جریان‌های سوکت تنظیم می‌شود. همچنین به صورت پیش‌فرض جریان‌های بایتی دنباله‌ای، قابل اعتماد، دو طرفه و مبتنی بر اتصال از طریق TCP فعال می‌شوند.

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

زمانی که یک شی سوکت اولیه وجود داشته باشد، می‌توان برخی از متدها را برای باز کردن اتصال، ارسال داده‌ها، دریافت داده‌ها و در نهایت، بستن اتصال استفاده کرد. در ادامه مثالی از کدهایی برای باز کردن اتصال، ارسال و دریافت داده و بستن اتصال ارائه شده است.

1## Connect to an IP with Port, could be a URL
2sock.connect(('0.0.0.0', 8080))
3## Send some data, this method can be called multiple times
4sock.send("Twenty-five bytes to send")
5## Receive up to 4096 bytes from a peer
6sock.recv(4096)
7## Close the socket connection, no more data transmission
8sock.close()

در بخش بعدی از مقاله «سوکت نویسی با پایتون چیست» ابتدا به آموزش سوکت نویسی سرور پرداخته می‌شود.

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

در این بخش به آموزش «برنامه نویسی سمت سرور» (Server-Side Programming) با پایتون پرداخته می‌شود. ابتدا و قبل از هر کاری باید پایتون در سیستم مورد نظر نصب شده باشد. می‌توان آن را با استفاده از دستور «نصب کننده بسته پایتون» (Python Installation Package | PIP) زیر یا دانلود مستقیم آن از وب سایت رسمی پایتون دریافت و روی سیستم نصب کرد.

pip install python

برای شروع سوکت نویسی با پایتون، کتابخانه Socket با استفاده از «import» در ابتدای کدها وارد شده است و برنامه نویسی سوکت با استفاده از آن آغاز می‌شود. کدهای زیر در سوکت نویسی سمت سرور با پایتون استفاده می‌شوند:

1# echo-server.py
2
3import socket
4
5HOST = "127.0.0.1"  # Standard loopback interface address (localhost)
6PORT = 65432  # Port to listen on (non-privileged ports are > 1023)
7
8with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
9    s.bind((HOST, PORT))
10    s.listen()
11    conn, addr = s.accept()
12    with conn:
13        print(f"Connected by {addr}")
14        while True:
15            data = conn.recv(1024)
16            if not data:
17                break
18            conn.sendall(data)

در کدهای فوق، با استفاده از socket.socket() یک شی سوکت ایجاد شده است که از «نوع مدیریت متن» (Context Manager Type) پشتیبانی می‌کند. بنابراین می‌توان از آن در دستور «with» پایتون استفاده کرد. همچنین در این بخش از برنامه، نیازی به فراخوانی s.close()  وجود ندارد:

1with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
2    pass  # Use the socket object without calling s.close().

آرگومان‌هایی که وارد socket() می‌شوند، «ثابت‌هایی» (Constant) هستند که برای مشخص کردن «خانواده آدرس» (Address Family) و نوع سوکت مورد استفاده قرار می‌گیرند. در این مثال، «AF_INET» خانواده آدرس اینترنتی برای پروتکل «IPv4» به حساب می‌آید و «SOCK_STREAM» نوع سوکت برای پروتکل TCP را نشان می‌دهد. همچنین، پروتکلی هم برای انتقال پیام‌ها در شبکه مورد استفاده قرار می‌گیرد. متد bind() برای ارتباط سوکت با رابط شبکه خاص و شماره پورت به صورت زیر مورد استفاده قرار می‌گیرد:

1# echo-server.py
2
3# ...
4
5with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
6    s.bind((HOST, PORT))
7    # ...

مقادیری که وارد متد .bind() می‌شوند به خانواده آدرس سوکت بستگی دارند. در مثال فوق، از socket.AF_INET استفاده می‌شود که متعلق به خانواده آدرس اینترنتی IPv4 است. بنابراین، انتظار می‌رود که دو «تاپل» (Tuple) هاست و پورت وجود داشته باشند. هاست یا همان میزبان می‌تواند یک نام میزبان، آدرس آیپی یا رشته خالی باشد. اگر از آدرس آی‌پی استفاده شود، هاست باید یک رشته آدرس با فرمت IPv4 باشد. آدرس آیپی «127.0.0.1» یک آدرس IPv4 استاندارد برای اینترفیس یا رابط «Loopback» به حساب می‌آید. بنابراین، فقط پردازش‌های روی هاست امکان اتصال به سرور را خواهند داشت.

آموزش سوکت نویسی با پایتون

اگر از رشته خالی استفاده شود، سرور، اتصال‌ها را در همه رابط‌های IPv4 موجود می‌پذیرد. پورت نشان دهنده شماره پورت کانال TCP برای پذیرش اتصال‌ها از کلاینت است. پورت باید عدد «صحیحی» (Integer) از ۱ تا ۶۵۵۳۵ باشد. عدد صفر برای وظیفه دیگری رزرو شده است. اگر شماره پورت کمتر از عدد ۱۰۲۴ باشد، ممکن است برخی از سیستم‌ها به «امتیازات کاربر ویژه» (Superuser Privilege) احتیاج داشته باشند. زمانی که از نام میزبان استفاده می‌شود، می‌توان نتایج گوناگونی را دریافت کرد. این نتایج به آنچه بستگی دارند که از فرآیند حل نام برگردانده می‌شود، همچنین می‌توانند هر چیزی باشند. بار اولی که برنامه پیاده‌سازی می‌شود آدرس «10.1.2.3» به دست می‌آید.

بار دومی که برنامه اجرا شده است، آدرس متفاوتی یعنی «192.168.0.1» دریافت می‌شود. در سومین پیاده‌سازی برنامه، آدرس «172.16.7.8» به دست می‌آید و به همین ترتیب این دریافت آدرس‌ها ادامه دارند. در مثال مرتبط با سوکت نویسی سرور با پایتون، .listen() این امکان را به سرور می‌دهد که ارتباط‌ها را بپذیرد. یعنی می‌توان گفت که سرور را به یک سوکت شنود با استفاده از کدهای زیر تبدیل می‌کند:

1# echo-server.py
2
3# ...
4
5with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
6    s.bind((HOST, PORT))
7    s.listen()
8    conn, addr = s.accept()
9    # ...

متد listen() دارای «پارامتر بک لاگ» (Backlog Parameter) است. این پارامتر تعداد اتصال‌های غیر قابل قبول ممکنی را نشان می‌دهد که سیستم قبل از رد اتصال‌های جدید مشخص می‌کند. تعیین این پارامتر در نسخه ۳.۵ پایتون اختیاری است. همچنین، اگر این مقدار مشخص نشده باشد، یک مقدار بک لاگ پیش‌فرض برای برنامه مشخص خواهد شد. اگر سرور درخواست‌های اتصال زیادی را به طور «همزمان» دریافت کند، افزایش مقدار بک لاگ ممکن است به تنظیم حداکثر طول «صف» (Queue) برای اتصال‌های در انتظار کمک کند. مقدار حداکثر وابسته به سیستم است. برای مثال، در سیستم عامل «لینوکس» (Linux) به صورت زیر نوشته می‌شود:

/proc/sys/net/core/somaxconn

متد accept() اجرا را «مسدود» (Block) می‌کند و در انتظار یک اتصال ورودی می‌ماند. هنگامی که یک اتصال کلاینت ایجاد می‌شود، یک شی جدید سوکت ایجاد می‌کند که نشان دهنده اتصال است. همچنین یک تاپل را برمی‌گرداند که وظیفه نگهداری آدرس مشتری را به عهده دارد. این تاپل شامل (هاست، پورت) برای اتصال‌های پروتکل IPv4 یا (هاست، پورت، «جریان اطلاعات» (Flowinfo)، Scopeid) برای اتصال‌های پروتکل IPv6 است.

مسئله‌ای که در این مثال باید به آن توجه شود این است که یک شی جدید سوکت از تابع accept() وجود دارد. این شی سوکت از اهمیتی زیادی برخوردار است؛ زیرا همان شی به حساب می‌آید که برای برقراری ارتباط با کلاینت از آن استفاده می‌شود. این تابع با سوکت شنود تفاوت دارد، سرور از سوکت شنود برای برقراری ارتباط‌های جدید استفاده می‌کند. در ادامه کدهای این تابع نمایش داده شده‌اند:

1# echo-server.py
2
3# ...
4
5with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
6    s.bind((HOST, PORT))
7    s.listen()
8    conn, addr = s.accept()
9    with conn:
10        print(f"Connected by {addr}")
11        while True:
12            data = conn.recv(1024)
13            if not data:
14                break
15            conn.sendall(data)

بعد از اینکه تابع .accept() شی سوکت کلاینت conn را فراهم می‌کند، یک حلقه بی‌نهایت while برای مسدود کردن فراخوانی‌های conn.recv() استفاده می‌شود. این تابع داده‌های فرستاده شده توسط کلاینت را می‌خواند و آن‌ها را با استفاده از conn.sendall() برمی‌گرداند. اگر conn.recv() یک شی بایت خالی (''b) را برگرداند، نشان دهنده این موضوع است که سیگنال کلاینت اتصال را می‌بندد و حلقه به پایان می‌رسد. همچنین عبارت with همراه با شی سوکت conn برای بستن خودکار سوکت در انتهای بلوک استفاده شده است. بخش بعدی مقاله «سوکت نویسی با پایتون چیست» به بررسی روش سوکت نویسی کلاینت با پایتون اختصاص داده می‌شود.

سوکت نویسی کلاینت با پایتون چگونه انجام می شود؟

در این بخش به آموزش «برنامه نویسی سمت کلاینت یا کاربر» (Client-Side Programming) با پایتون پرداخته می‌شود. کدهای زیر به این بخش از سوکت نویسی با پایتون مربوط می‌شوند:

1# echo-client.py
2
3import socket
4
5HOST = "127.0.0.1"  # The server's hostname or IP address
6PORT = 65432  # The port used by the server
7
8with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
9    s.connect((HOST, PORT))
10    s.sendall(b"Hello, world")
11    data = s.recv(1024)
12
13print(f"Received {data!r}")

در مقایسه با کدها و برنامه نویسی سرور، نوشتن کدهای برنامه نویسی کلاینت بسیار ساده و روان است. برنامه فوق شی سوکت را با استفاده از تابع .connect() برای اتصال با سرور و فراخوانی s.sendall() برای ارسال پیام به آن، ایجاد می‌کند. در نهایت در کدهای فوق، تابع s.recv() برای خواندن پاسخ سرور و چاپ آن فراخوانی می‌شود. در ادامه این بخش از مقاله «سوکت نویسی با پایتون چیست» به بررسی روش پیاده‌سازی کدهای سوکت نویسی سرور و کلاینت پرداخته شده است.

پیاده سازی کدهای سرور و کلاینت چگونه است؟

در این مرحله از سوکت نویسی با پایتون به پیاده‌سازی کدهای کلاینت و سرور پرداخته می‌شود، همچنین نوع رفتار و آنچه مورد بررسی قرار گرفته است که در این خصوص رخ می‌دهد. برای شروع پیاده‌سازی این کدها باید وارد خط فرمان یا ترمینال سیستم مورد نظر شد و به پوشه‌ای (دایرکتوری) رفت که حاوی کدها و اسکریپت‌های نوشته شده برای سوکت است. همچنین برای استفاده از کتابخانه Socket پایتون باید اطمینان حاصل کرد که نسخه ۳.۶ یا بالاتر پایتون نصب شده و خط فرمان در مسیر صحیح قرار گرفته است. سپس با بررسی همه این موارد، کدهای سرور با استفاده از عبارت زیر اجرا می‌شوند:

1$ python echo-server.py

ترمینال به‌گونه‌ای به نظر خواهد رسید که گویی اجرای آن متوقف شده و به اصطلاح در حالت «Hang» قرار گرفته است، دلیل آن، این موضع است که سرور در تابع .accept()  مسدود شده یا به حالت «تعلیق» (Suspend) درآمده است. این موضوع در کدهای زیر مشخص می‌شود.

1# echo-server.py
2
3# ...
4
5with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
6    s.bind((HOST, PORT))
7    s.listen()
8    conn, addr = s.accept()
9    with conn:
10        print(f"Connected by {addr}")
11        while True:
12            data = conn.recv(1024)
13            if not data:
14                break
15            conn.sendall(data)

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

1$ python echo-client.py 
2Received b'Hello, world'

حال در پنجره ترمینال یا کد فرمانی که برای کدهای سرور باز شده بود، باید عباراتی شبیه به عبارت‌های زیر مشاهده شود:

$ python echo-server.py 
Connected by ('127.0.0.1', 64623)

در خروجی فوق، سرور، تاپل adr را چاپ می‌کند که از تابع accept() برگردانده شده بود. این خروجی آدرس آیپی کلاینت و شماره پورت TCP را نشان می‌دهد. زمانی که این برنامه روی سیستم‌های گوناگون پیاده‌سازی می‌شود. احتمالاً شماره پورت 64623 متفاوت خواهد بود. در ادامه این مقاله به بررسی و مشاهده وضعیت سوکت‌ها در برنامه نویسی پایتون پرداخته شده است.

پیاده‌سازی سوکت نویسی با پایتون

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

برای مشاهده وضعیت فعلی سوکت در هاست از دستور netstat استفاده می‌شود. این عبارت به صورت پیش‌فرض در سیستم عامل‌های «مک OS»، لینوکس و ویندوز وجود دارد. در کدهای زیر خروجی netstat در سیستم عامل مک OS بعد از شروع پیاده‌سازی کدهای سوکت نویسی سرور مشاهده می‌شود:

1$ netstat -an
2Active Internet connections (including servers)
3Proto Recv-Q Send-Q  Local Address          Foreign Address        (state)
4tcp4       0      0  127.0.0.1.65432        *.*                    LISTEN

باید به این موضوع توجه داشت که «آدرس محلی» (Local Address) به صورت «127.0.0.1.65432» نشان داده می‌شود. اما اگر در برنامه echo-server.py از «"" = HOST» به جای «"HOST = "127.0.0.1» استفاده شود، netstat به صورت زیر نمایش داده خواهد شد:

1$ netstat -an
2Active Internet connections (including servers)
3Proto Recv-Q Send-Q  Local Address          Foreign Address        (state)
4tcp4       0      0  *.65432                *.*                    LISTEN

آدرس محلی به صورت «65432.*» است و همه رابط‌های هاست در دسترس که خانواده آدرس را پیشتیبانی می‌کنند، برای ارتباط‌های ورودی استفاده خواهند شد. در این مثال، socket.AF_INET از پروتکل IPv4 در فراخوانی socket()  استفاده می‌شود. همچنین می‌توان این موضوع را در ستون Proto این پروتکل نیز مشاهده کرد. خروجی فوق برای نشان دادن سرور خلاصه شده است.

اگر کدهای فوق، روی سیستم دیگری پیاده‌سازی شوند، خروجی وسیع‌تری نمایش داده خواهد شد. چیزی که در خصوص این خروجی‌ها مورد توجه است، ستون‌های Proto ، Local Address و حالت سرور به حساب می‌آید. در آخرین مثال فوق، netstat نشان می‌دهد که سرور از پروتکل IPv سوکت TCP، پورت 65432 همه رابط‌ها و حالت شنود استفاده کرده است.

روش دیگری برای دستیابی به این خروجی همراه با اطلاعات مفید اضافی، استفاده از lsof (List Open Files) یا همان فهرست فایل‌های باز است. این عبارت به صورت پیش‌فرص روی سیستم عامل مک او اس وجود دارد و می‌تواند در سیستم عامل لینوکس به وسیله «مدیر بسته» (Package Manager) مورد استفاده قرار بگیرد. در ادامه روش استفاده از این عبارت مشخص شده است:

1$ lsof -i -n
2COMMAND     PID   USER   FD   TYPE   DEVICE SIZE/OFF NODE NAME
3Python    67982 nathan    3u  IPv4 0xecf272      0t0  TCP *:65432 (LISTEN)

lsof هنگامی که همراه با گزینه -i استفاده می‌شود، مانند عبارات فوق، اطلاعاتی درباره زبان نوشته شده کدها، PID یا شناسه پردازه و USER یا همان شناسه کاربر از سوکت‌های باز اینترنت ارائه می‌دهد. netstat و lsof دارای گزینه‌های در دسترس زیادی هستند و این گزینه‌ها نسبت به سیستم عامل در حال استفاده، تفاوت دارند. در ادامه، خطای رایجی ارائه شده است که هنگام تلاش برای اتصال به پورتی بدون سوکت شنود ایجاد خواهد شد:

1$ python echo-client.py 
2Traceback (most recent call last):
3  File "./echo-client.py", line 9, in <module>
4    s.connect((HOST, PORT))
5ConnectionRefusedError: [Errno 61] Connection refused

در کدهای فوق، احتمالاً شماره پورت مشخص شده اشتباه است یا سرور در حال اجرا نیست. احتمال دیگری نیز وجود دارد و آن این است که شاید یک فایروال در مسیر پیاده‌سازی وجود دارد که باعث مسدود شدن اتصال می‌شود و به راحتی می‌توان آن را غیرفعال کرد. همچنین ممکن است خطای Connection timed out مشاهده شود و برای برطرف کردن آن باید یک Rule فایروال به سیستم اضافه شود که به کلاینت امکان اتصال به پورت TCP را می‌دهد. در بخش بعدی از مقاله «سوکت نویسی با پایتون چیست» به بررسی عدم ارتباط در سوکت نویسی با پایتون پرداخته شده است.

بررسی عدم ارتباط در سوکت نویسی با پایتون

حال در این بخش به صورت دقیق‌تری به این موضوع پرداخته می‌شود که کلاینت و سرور با یکدیگر در ارتباط هستند. در تصویر زیر یک هاست نمایش داده شده است:

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

هنگام استفاده از رابط Loopback یعنی آدرس 127.0.0.1 پروتکل IPv4 یا آدرس 1: : پروتکل IPv6 داده‌ها هرگز از هاست خارج نمی‌شوند و با شبکه‌های خارجی دیگر در ارتباط نیستند. در تصویر فوق، رابط Loopback در داخل هاست قرار دارد. این نشان دهنده ماهیت داخلی رابط Loopback است و نشان می‌دهد که داده‌ها و ارتباط‌هایی که آن را انتقال می‌دهند دارای هاست محلی هستند. این همان دلیلی است که رابط Loopback و آدرس آیپی 127.0.0.1 یا 1: : به عنوان «هاست محلی» یا همان «Localhost» شناخته می‌شود.

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

در این صورت میزبان‌های دیگر در شبکه نمی‌توانند به این پایگاه داده متصل شوند. زمانی که از آدرس‌های آیپی دیگری در برنامه استفاده می‌شود، احتمالاً به یک رابط «اترنت» (Ethernet) و شبکه خارجی متصل شده است. تصویر زیر «دروازه» (Gateway) شبکه به میزبان‌های خارج از محدوده هاست محلی را نشان می‌دهد:

«دروازه» (Gateway) شبکه به هاست‌های خارج از محدوده هاست محلی

بخش بعدی از مقاله «سوکت نویسی با پایتون چیست» به مدیریت اتصال‌های چندگانه اختصاص داده می‌شود.

مدیریت اتصال های چندگانه در سوکت نویسی با پایتون چگونه است؟

سرور ارائه شده در این مقاله دارای محدودیت‌هایی است. بزرگ‌ترین محدودیت این سرور این موضوع به حساب می‌آید که فقط به یک کلاینت سرویس می‌دهد و سپس از پردازش خارج می‌شود. کلاینت نیز دارای همین محدودیت‌ها است. هنگامی که کلاینت از s.recv() استفاده می‌کند، ممکن است فقط یک بایت «'b 'H» از «'b 'Hello, world» را برگرداند، کدهای این مسئله در ادامه نمایش داده شده‌اند:

1# echo-client.py
2
3# ...
4
5with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
6    s.connect((HOST, PORT))
7    s.sendall(b"Hello, world")
8    data = s.recv(1024)
9
10print(f"Received {data!r}")

آرگومان bufsize (اندازه بافر که مقدار آن در کدهای بالا ۱۰۲۴ در نظر گرفته شده است) حداکثر مقدار داده‌ای است که باید در یک بار مبادله دریافت شود. این موضوع به این معنی نیست که .recv() ۱۰۲۴ بایت را برمی‌گرداند. رفتار متد .send() هم به همین صورت است. این متد تعداد بایت‌هایی را برمی‌گرداند که ممکن است کمتر از اندازه داده‌های ارسال شده باشند. در این بخش دو مسئله زیر پیش می‌آید که باید بررسی شوند:

  • چطور می‌توان اتصال‌های چندگانه در سوکت نویسی با پایتون را به طور همزمان مدیریت کرد؟
  • برای ارسال یا دریافت همه اطلاعات نیاز است که متد .recv() یا .send() در سوکت نویسی با پایتون فراخوانی شوند.

برای از بین بردن این مشکلات، چندین رویکرد وجود دارد. رایج‌ترین رویکرد موجود، استفاده از روش «ناهمگام ورودی و خروجی» (Asynchronous I/O) است. این روش با کتابخانه asyncio نمایش داده می‌شود و در کتابخانه استاندارد نسخه ۳.۴ پایتون به بعد ارائه شده است. یکی از روش‌های سنتی حل این مشکل استفاده از «نخ» به حساب می‌آید. یکی از مشکلات همزمانی، سختی حل مسائل آن است. نکات ریز بسیاری در رابطه با این موضوع وجود دارند که باید در نظر گرفته شوند.

اما موضوع همزمانی همیشه مورد استفاده قرار نمی‌گیرد. برای مثال، اگر برنامه نیاز به مقیاس‌بندی داشته باشد یا اگر نیاز باشد در برنامه بیش از یک پردازنده یا هسته مورد استفاده قرار بگیرد، برنامه نیازمند استفاده از همزمانی خواهد بود. در ادامه برای استفاده از همزمانی، روشی سنتی اما با کارایی بالا ارائه شده است. این روش از موارد استفاده نخ‌ها هم قدیمی‌تر به حساب می‌آید.

روش استفاده از متد ()select برای همزمانی

در این روش از فراخوانی سیستمی .select() استفاده شده است. متد .select() در سوکت نویسی با پایتون این امکان را به شبکه می‌دهد تا تکمیل ورودی‌ها و خروجی‌ها را برای بیش از یک سوکت بررسی کند. بنابراین این متد پایتون را می‌توان فراخوانی کرد تا مشاهده شود که کدام سکوت دارای ورودی و خروجی آماده برای خواندن و نوشتن است. برای انجام این رویکرد می‌توان از ماژول selectors پایتون در کتابخانه استاندارد آن استفاده کرد تا بدون توجه به سیستم عاملی که اجرا روی آن انجام می‌شود، کارآمدترین پیاده‌سازی ارائه شود. در بخش بعدی از مقاله «سوکت نویسی با پایتون چیست» به بررسی کلاینت و سرور اتصال‌های چندگانه پرداخته شده است.

کلاینت و سرور اتصال های چندگانه در سوکت نویسی با پایتون چیست ؟

در این بخش، برای سوکت نویسی با پایتون، سرور و کلاینتی ایجاد می‌شود که اتصال‌های چندگانه را با استفاده از شی selector می‌سازند. این شی به وسیله ماژول selectors تولید شده است. در ادامه این بخش از مقاله «سوکت نویسی با پایتون چیست» ابتدا به بررسی «سرور اتصال چندگانه» (Multi-Connection Server) پرداخته می‌شود.

کلاینت و سرور اتصال های چندگانه در سوکت نویسی با پایتون چیست ؟

سرور اتصال چندگانه چیست؟

در این بخش با استفاده از کدهای ارائه شده زیر، یک سرور اتصال چندگانه ایجاد شده است. بخش اول کدهای زیر، سوکت شنود را تنظیم می‌کنند:

1# multiconn-server.py
2
3import sys
4import socket
5import selectors
6import types
7
8sel = selectors.DefaultSelector()
9
10# ...
11
12host, port = sys.argv[1], int(sys.argv[2])
13lsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
14lsock.bind((host, port))
15lsock.listen()
16print(f"Listening on {(host, port)}")
17lsock.setblocking(False)
18sel.register(lsock, selectors.EVENT_READ, data=None)

بزرگ‌ترین تفاوت بین این سرور و سرور قبلی در فراخوانی متد lsock.setblocking(False) برای پیکربندی سوکت در «حالت غیر مسدود» (Non-Blocking Mode) به حساب می‌آید. در این رویکرد، فراخوانی‌های این سوکت دیگر مسدود نمی‌شوند. زمانی که در این کدها از متد sel.select() استفاده می‌شود، همان‌طور که در ادامه نشان داده شده است، می‌توان منتظر رویدادها در یک یا چند سوکت بود و سپس داده‌ها را هنگامی خواند و نوشت که آماده هستند. متد sel.register() سوکت‌ها را برای نظارت با متد sel.select() جهت رویدادهای مورد نظر ثبت می‌کند.

برای رویکرد شنود سوکت، رویداد selectors.EVENT_READ خوانده می‌شود. برای ذخیره هر نوع داده دلخواهی همراه با سوکت از data استفاده شده است. نتیجه این متد زمانی برمی‌گردد که متد .select() بازگردد. همچنین از متد data برای پیگیری انواع اطلاعاتی استفاده شده است که به سوکت ارسال و از آن دریافت می‌شوند. کدهای حلقه رویداد در ادامه ارائه شده‌اند:

1# multiconn-server.py
2
3# ...
4
5try:
6    while True:
7        events = sel.select(timeout=None)
8        for key, mask in events:
9            if key.data is None:
10                accept_wrapper(key.fileobj)
11            else:
12                service_connection(key, mask)
13except KeyboardInterrupt:
14    print("Caught keyboard interrupt, exiting")
15finally:
16    sel.close()

در کدهای فوق، متد sel.select(timeout=None) تا زمانی که سوکتی برای ورودی یا خروجی (I/O) آماده شود، وظیفه مسدودسازی را انجام می‌دهد. این متد فهرستی از تاپل‌ها را برمی‌گرداند که هر یک از آن‌ها مختص یک سوکت هستند. هر تاپل شامل یک «کلید» (Key) و «ماسک» (Mask) می‌شود. کلید یک SelectorKey namedtuple است که شامل «ویژگی» (Attribute) fileobj می‌شود. key.fileobj شی سوکت است و mask رویداد ماسک برای عملیاتی آماده اجرا به حساب می‌آید. اگر key.data خالی باشد، نشان می‌دهد که سوکت شنود وجود دارد و اتصال باید پذیرفته شود.

تابع ()accept_wrapper فراخوانی می‌شود تا شی سوکت جدید را دریافت کرده و در selector ثبت شود. همچنین اگر key.data خالی نباشد، می‌توان متوجه شد که یک سوکت کلاینت وجود دارد که قبلاً پذیرفته شده است و باید خدماتی برای آن ارائه شود. بنابراین متد service_connection() با آرگومان‌های کلید و ماسک فراخوانی می‌شود. در ادامه کدهای تابع accept_wrapper() و روش عملکرد آن ارائه شده است:

1# multiconn-server.py
2
3# ...
4
5def accept_wrapper(sock):
6    conn, addr = sock.accept()  # Should be ready to read
7    print(f"Accepted connection from {addr}")
8    conn.setblocking(False)
9    data = types.SimpleNamespace(addr=addr, inb=b"", outb=b"")
10    events = selectors.EVENT_READ | selectors.EVENT_WRITE
11    sel.register(conn, events, data=data)
12
13# ...

به دلیل اینکه سوکت شنود برای رویداد selectors.EVENT_READ ثبت شده است، باید برای خواندن آماده باشد. sock.accept() و سپس conn.setblocking(False) برای قرار دادن سوکت در حالت غیر مسدود فراخوانی می‌شود. یکی از اهداف اصلی در سرور، مسدود نشدن آن است. اگر سرور مسدود شود کل سرور تا زمانی متوقف خواهد شد که مسدودسازی غیرفعال شود. این موضوع به این معنی است که در این حالت سایر سوکت‌ها در انتظار باقی می‌مانند، حتی اگر سرور به طور فعال کار نکند. به این حالت به اصطلاح «هنگ» (Hang) کردن گفته می‌شود که باید تا جایی که امکان دارد از رخ دادن آن جلوگیری شود.

سپس در مرحله بعد، با استفاده از SimpleNamespace یک شی برای نگهداری داده‌های همراه با سوکت ایجاد می‌شود. این کار به این دلیل انجام می‌گیرد که توسعه دهنده باید بداند چه زمانی اتصال کلاینت برای خواندن و نوشتن آماده است. هر دو این رویدادها با عملگر «Bitwise OR» تنظیم می‌شوند و کدهای فوق این موضوع را نشان می‌دهند. رویدادهای ماسک، سوکت و اشیا داده‌ها به متد sel.register() ارسال می‌شوند. حال در کدهای زیر، متد service_connection() نشان می‌دهد که چگونه یک اتصال کلاینت در زمانی مدیریت می‌شود که آماده است:

1# multiconn-server.py
2
3# ...
4
5def service_connection(key, mask):
6    sock = key.fileobj
7    data = key.data
8    if mask & selectors.EVENT_READ:
9        recv_data = sock.recv(1024)  # Should be ready to read
10        if recv_data:
11            data.outb += recv_data
12        else:
13            print(f"Closing connection to {data.addr}")
14            sel.unregister(sock)
15            sock.close()
16    if mask & selectors.EVENT_WRITE:
17        if data.outb:
18            print(f"Echoing {data.outb!r} to {data.addr}")
19            sent = sock.send(data.outb)  # Should be ready to write
20            data.outb = data.outb[sent:]
21
22# ...

این کدها بخش مهمی یا به عبارتی قلب یک سرور چندگانه ثابت به حساب می‌آیند. کلید namedtuple برگردانده شده از متد .select()  شامل شی سوکت ( fileobj ) و شی داده است. ماسک حاوی رویدادهایی می‌شود که آماده هستند. اگر سوکت آماده خواندن باشد، ماسک و selectors.EVENT_READ به «True» تغییر پیدا می‌کنند، بنابراین، sock.recv() فراخوانی می‌شود. داده‌های خوانده شده به data.outb اضافه می‌شوند تا بعداً ارسال شوند. باید در کدهای فوق و در خط ۱۰ام تا ۱۵ام به این نکته توجه داشت که آیا با استفاده از else داده‌ای دریافت شده است؟

اگر داده‌ای دریافت نشود به این معنی است که کلاینت سوکت خود را بسته است، بنابراین سرور نیز باید این کار را انجام دهد. اما نباید این موضوع فراموش شود که قبل از بستن سوکت، فراخوانی متد sel.unregister() انجام بگیرد. بنابراین در این حالت نظارتی توسط .select() صورت نمی‌پذیرد. زمانی یک سوکت برای نوشتن آماده است که سالم باشد. هر داده ذخیره شده در data.outb با استفاده از متد sock.send() ارجاع داده می‌شود. سپس بایت‌های ارسال شده از بافر، حذف خواهند شد. کدهای زیر این موضوع را نشان می‌دهند:

1# multiconn-server.py
2
3# ...
4
5def service_connection(key, mask):
6
7    # ...
8
9    if mask & selectors.EVENT_WRITE:
10        if data.outb:
11            print(f"Echoing {data.outb!r} to {data.addr}")
12            sent = sock.send(data.outb)  # Should be ready to write
13            data.outb = data.outb[sent:]
14
15# ...

متد .send() تعداد بایت‌های ارسالی را برمی‌گرداند. سپس این عدد را می‌توان در بافر .outb برای دور انداختن بایت‌های ارسال شده استفاده کرد. در ادامه این بخش از مقاله «سوکت نویسی با پایتون چیست» به بررسی «کلاینت اتصال چندگانه» پرداخته می‌شود.

کلاینت اتصال چندگانه چیست؟

کلاینت اتصال چندگانه، با سرور اتصال چندگانه شباهت بسیاری دارد. تنها تفاوت آن این است که به جای استفاده از شنود برای اتصال‌ها از مقداردهی اولیه به وسیله start_connections() استفاده می‌کند. در ادامه کدهای این کلاینت ارائه شده است:

1# multiconn-client.py
2
3import sys
4import socket
5import selectors
6import types
7
8sel = selectors.DefaultSelector()
9messages = [b"Message 1 from client.", b"Message 2 from client."]
10
11def start_connections(host, port, num_conns):
12    server_addr = (host, port)
13    for i in range(0, num_conns):
14        connid = i + 1
15        print(f"Starting connection {connid} to {server_addr}")
16        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
17        sock.setblocking(False)
18        sock.connect_ex(server_addr)
19        events = selectors.EVENT_READ | selectors.EVENT_WRITE
20        data = types.SimpleNamespace(
21            connid=connid,
22            msg_total=sum(len(m) for m in messages),
23            recv_total=0,
24            messages=messages.copy(),
25            outb=b"",
26        )
27        sel.register(sock, events, data=data)
28
29# ...

num_conns از خط فرمان خوانده می‌شود و تعداد اتصال‌ها برای ایجاد سرور را نشان می‌دهد. در کدهای فوق نیز کاملاً مانند کدهای بخش سرور، هر سوکت روی حالت غیر مسدود تنظیم شده است. از .connect_ex() به جای .connect() استفاده می‌شود؛ زیرا .connect() در کدهای فوق بلافاصله یک استثنا «BlockingIOError» ایجاد می‌کند. متد .connect_ex() در ابتدا به جای اینکه استثنایی ایجاد کند که در اتصال در حال انجام اختلال ایجاد کند، یک «نشانگر» (Indicator) خطا به نام «errno.EINPROGRESS» را برمی‌گرداند. زمانی که اتصال کامل شد، سوکت برای خواندن و نوشتن آماده است و با متد .select() برگردانده می‌شود.

پس از اینکه سوکت تنظیم شد، داده‌هایی که قرار بود با استفاده از سوکت ذخیره شوند به وسیله متد SimpleNamespace ایجاد خواهند شد. پیامی که کلاینت باید به سرور بفرستد با استفاده از messages.copy() کپی می‌شود، زیرا هر اتصال، socket.send() را فراخوانی می‌کند و لیست را تغییر می‌دهد. همه مواردی که نیاز است کلاینت ارسال کند، ارسال و دریافت می‌شوند. از جمله این موارد می‌توان به تعداد کل بایت‌ها در پیام‌ها اشاره کرد که در شی data  ذخیره می‌شوند. در ادامه کدهای تغییرات ایجاد شده متد سرور service_connection() برای نسخه کلاینت ارائه شده است:

1 def service_connection(key, mask):
2     sock = key.fileobj
3     data = key.data
4     if mask & selectors.EVENT_READ:
5         recv_data = sock.recv(1024)  # Should be ready to read
6         if recv_data:
7-            data.outb += recv_data
8+            print(f"Received {recv_data!r} from connection {data.connid}")
9+            data.recv_total += len(recv_data)
10-        else:
11-            print(f"Closing connection {data.connid}")
12+        if not recv_data or data.recv_total == data.msg_total:
13+            print(f"Closing connection {data.connid}")
14             sel.unregister(sock)
15             sock.close()
16     if mask & selectors.EVENT_WRITE:
17+        if not data.outb and data.messages:
18+            data.outb = data.messages.pop(0)
19         if data.outb:
20-            print(f"Echoing {data.outb!r} to {data.addr}")
21+            print(f"Sending {data.outb!r} to connection {data.connid}")
22             sent = sock.send(data.outb)  # Should be ready to write
23             data.outb = data.outb[sent:]

به طور کلی کدهای سوکت نویسی با پایتون سرور و کلاینت چندگانه با یکدیگر مشابه هستند. اما تفاوت‌های کمی نیز دارند. کلاینت، تعداد بایت‌های ارسالی از سرور را دریافت می‌کند تا بتواند سمت اتصال خود را ببندد. سرور هم زمانی که این موضوع را تشخیص می‌دهد، سمت اتصال خود را می‌بندد. باید به این نکته توجه داشت که با استفاده از این رویکرد، عملکرد سرور به رفتار صحیح کلاینت بستگی دارد. سرور از کلاینت انتظار دارد که پس از ارسال پیام، بخش اتصال خود را ببندد.

اگر کلاینت این بخش را نبندد، سرور نیز اتصال را باز می‌گذارد. در یک برنامه واقعی، ممکن است قصد محافظت از یک سرور با استفاده از پیاده‌سازی «Timeout» وجود داشته باشد. بنابراین باید اتصال بسته شود تا از انباشته شدن اتصالات کلاینت در صورت عدم ارسال درخواست پس از مدت زمان معین جلوگیری شود. ادامه این بخش از مقاله «سوکت نویسی با پایتون چیست» به بررسی روش پیاده‌سازی سرور و کلاینت چندگانه اختصاص داده شده است.

روش پیاده سازی سرور و کلاینت چندگانه در سوکت نویسی با پایتون چگونه است؟

حال در این بخش پس از ایجاد کدهای سرور و کلاینت چندگانه، زمان آن است که روش پیاده‌سازی multiconn-server.py  و multiconn-client.py ارائه شود. هر دو این بخش از کدها با استفاده از آرگومان‌های خط فرمان اجرا می‌شوند. همچنین می‌توان برای مشاهده همه گزینه‌ها، بدون آرگومان آن‌ها را پیاده‌سازی کرد. برای بخش سرور نیاز است که اعداد «Host» و «Port» نیز مشخص شوند. عبارت‌های زیر برای اجرا در خط فرمان نوشته می‌شوند:

$ python multiconn-server.py
Usage: multiconn-server.py <host> <port>

برای کدهای سمت کلاینت، به جز قرار دادن اعداد «Host» و «Port» جهت پیاده‌سازی، نیاز است که تعداد اتصال‌های ایجاد شده به سرور نیز ارسال شوند. این تعداد با نام «num_connections» نمایش داده شده است. عبارت زیر برای پیاده‌سازی کلاینت چندگانه مورد استفاده قرار می‌گیرد:

$ python multiconn-client.py
Usage: multiconn-client.py <host> <port> <num_connections>

کدهای زیر نشان دهنده خروجی سرور در زمانی هستند که شنود در واسط Loopback روی پورت ۶۵۴۳۲ انجام می‌شود:

$ python multiconn-server.py 127.0.0.1 65432
Listening on ('127.0.0.1', 65432)
Accepted connection from ('127.0.0.1', 61354)
Accepted connection from ('127.0.0.1', 61355)
Echoing b'Message 1 from client.Message 2 from client.' to ('127.0.0.1', 61354)
Echoing b'Message 1 from client.Message 2 from client.' to ('127.0.0.1', 61355)
Closing connection to ('127.0.0.1', 61354)
Closing connection to ('127.0.0.1', 61355)

 کدهای زیر نشان دهنده خروجی کلاینت در زمانی است که دو اتصال با سرور فوق ایجاد می‌کند:

$ python multiconn-client.py 127.0.0.1 65432 2
Starting connection 1 to ('127.0.0.1', 65432)
Starting connection 2 to ('127.0.0.1', 65432)
Sending b'Message 1 from client.' to connection 1
Sending b'Message 2 from client.' to connection 1
Sending b'Message 1 from client.' to connection 2
Sending b'Message 2 from client.' to connection 2
Received b'Message 1 from client.Message 2 from client.' from connection 1
Closing connection 1
Received b'Message 1 from client.Message 2 from client.' from connection 2
Closing connection 2

با استفاده از پیاده‌سازی دو عبارت فوق برای کلاینت و سرور چندگانه، پیاده‌سازی این بخش به طور کامل به اتمام رسید. در ادامه مقاله «سوکت نویسی با پایتون چیست» به بررسی انواع سوکت در برنامه نویسی شبکه پرداخته شده است.

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

سوکت‌ها معمولاً در برنامه‌های کاربردی کلاینت-سرور مورد استفاده قرار می‌گیرند. سرور، سوکت‌ها را ایجاد، آن‌ها را به آدرس‌های پورت شبکه الصاق و سپس صبر می‌کند تا کلاینت با آن ارتباط برقرار کند. کلاینت یک سوکت ایجاد و سپس سعی می‌کند که به سوکت سرور متصل شود. در نهایت، هنگامی که اتصال انجام شد، انتقال داده انجام می‌شود. در ادامه این بخش به بررسی انواع سوکت‌های موجود در برنامه نویسی شبکه پرداخته شده است. ابتدا در بخش بعدی به بررسی «سوکت دیتاگرام» (Datagram Socket) پرداخته می‌شود.

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

سوکت دیتاگرام نوعی شبکه به حساب می‌آید که به صورت بدون اتصال، بسته‌ها را ارسال و دریافت می‌کند. برای مثال می‌توان گفت که عملکرد آن شبیه به «صندق ایمیل» (Mailbox) است. در صندق ایمیل داده‌ها یا همان حروف به یک صندق یا جعبه ارسال و جمع‌آوری می‌شوند و سپس آن‌ها را به سوکت گیرنده می‌فرستند. در این نوع از سوکت‌ها ارتباط‌ها در پروتکل UDP انجام شده و نوع آن با «SOCK_DGRAM» نشان داده می‌شود. در بخش بعدی به معرفی «سوکت جریان» (Stream Socket) پرداخته شده است.

سوکت جریان در برنامه نویسی شبکه چیست؟

در سیستم عامل‌های کامپیوتر، سوکت جریان نوعی سوکت «اتصال‌های بین فرآیندی» (Interprocess Communication) یا سوکت شبکه به حساب می‌آید که جریانی به صورت «اتصال گرا» (Connection Oriented)، دنباله‌ای، منحصربه‌فرد و بدون مرزهای «رکورد» (Record) و با ساز و کارهای خوبی برای ایجاد و از بین بردن اتصال‌ها و تشخیص خطا فراهم می‌کند. این اتصال‌ها معمولاً بین تلفن‌ها یعنی اتصال «دو طرفه» (Two End) و یک مکالمه یعنی همان انتقال داده‌ها انجام می‌شود. در این نوع از سوکت‌ها ارتباط‌ها با پروتکل TCP انجام شده و نوع آن «SOCK_STREAM» است. ادامه این بخش به بررسی «سوکت‌های خام» (Raw Socket) اختصاص داده می‌شود.

سوکت های خام در برنامه نویسی شبکه چیست؟

سوکت‌های خام دسترسی به «پروتکل کنترل پیام‌های اینترنتی» (Internet Control Message Protocol | ICMP) را فراهم می‌کنند. این نوع از سوکت‌ها به طور معمول «مبتنی بر دیتاگرام» (Datagram Oriented) به حساب می‌آیند. مشخصات دقیق آن‌ها به اینترفیس ارائه شده توسط پروتکل بستگی دارند. سوکت‌های خام در برنامه‌های زیادی مورد استفاده قرار نمی‌گیرند. این سوکت‌ها برای پشتیبانی از توسعه پروتکل‌های ارتباطی جدید یا برای دسترسی به امکانات محرمانه پروتکل‌های موجود استفاده شده‌اند. فقط در فرآیندهای افراد «Superuser» از سوکت‌های خام استفاده می‌شود و معمولاً برای کاربران معمولی کاربردی ندارند. نوع این سوکت «SOCK_RAW» است.

جمع‌بندی

در این مقاله سعی شد به طور جامع به این سوال رایج در برنامه نویسی شبکه پاسخ داده شود که سوکت نویسی با پایتون چیست ؟ هدف اصلی برنامه نویسی سوکت، بررسی و انتقال اطلاعاتی در شبکه است که بین فرستنده و گیرنده یا همان کلاینت و سرور مبادله می‌شوند. با مطالعه مقاله «برنامه نویسی سمت سرور چیست» می‌توان این موضوع را متوجه شد که سوکت نویسی با پایتون بسیار کارآمد است و با روش‌های گوناگون می‌توان این کار را انجام داد.

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

بر اساس رای ۲۱ نفر
آیا این مطلب برای شما مفید بود؟
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.
منابع:
Net-informations.comGeeksforGeekstutorialspointPubNubedureka!Real PythonGeeksforGeeksOracle
۳ دیدگاه برای «سوکت نویسی با پایتون چیست؟ — آموزش از صفر تا صد»

بسیار عالی بود .خیلی ممنون

سلام راه ارتباطی با شما وجود داره؟ چندتا سوال داشتم…

با سلام؛‌

سوالات مرتبط با مطالب را در بخش نظرات بنویسید و همکاران ما درصورتی‌که پاسخ آن‌ها را بدانند، حتما راهنمایی خواهند کرد.

با تشکر از همراهی شما با مجله فرادرس

نظر شما چیست؟

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