سلام وقت همگی بخیر.
امیرحسین ناظوری هستم و خیلی خوش اومدید به یکی از مقالات سنگین و حجیم و عجیبم داخل ویرگول 😉

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

برای درک این آموزش، شما باید با زبان Python و مقدمات شبکه آشنا باشید.
من یه ویدیو طولانی داخل کانال یوتیوبم قرار دادم و مقدمات شبکه رو به ساده ترین روش ممکن که در توانم بود، توضیح دادم. در صورت لزوم از اون ویدیو شروع کنید. (مشاهده ویدیو)
در ابتدا لازمه بررسی کنیم که برنامه نویسی شبکه چیه و کجاها کاربرد داره.
برنامه نویسی شبکه یعنی نوشتن برنامه هایی که بتونن با برنامه های دیگه، داخل کامپیوترهای دیگه ارتباط بگیرن. به زبان ساده تر یعنی برنامت فقط روی کامپیوتر خودت کار نمیکنه، بلکه میتونه با کامپیوترهای دیگه ارتباط بگیره و داده ارسال و دریافت کنه.
وقتی شما وارد مرورگر میشی و سایت google رو باز میکنی، مرورگرت یه برنامه ایی هست که تحت شبکه نوشته شده و میره متصل میشه به سرور گوگل.
برنامه نویسی شبکه کجاها کاربرد داره؟ تقریبا همه جا. چندتا از مهمترین کاربردهاش:
وب و اینترنت: برنامه هایی مثل مرورگر، نرم افزارهای چت محور (مثل تلگرام)، بازی های آنلاین، وب سرویس ها و...
ابزارهای مدیریت شبکه: برنامه هایی که کمک میکنن شبکه رو مدیریت کنیم، از برنامه نویسی شبکه استفاده میکنن تا ارتباط بین کامپیوترها و شبکه ها رو فراهم کنن.
هک و امنیت: ابزارها یا برنامه هایی که این امکان رو میدن تا بتونیم ترافیک های مختلف رو شنود کنیم، دارن از برنامه نویسی شبکه استفاده میکنن. یا بدافزاری که روی کامپیوتر هدف اجرا میشه و وصل میشه به سرور کنترلش، داره از مدل برنامه نویسی شبکه استفاده میکنه.
تو اندروید، نرم افزارهایی مثل SHAREit یا Zapya تحت شبکه برنامه نویسی شدن تا شما اون هارو نصب کنید و از طریقشون، فایل هایی رو داخل یک شبکه ردوبدل کنید.
Socket چیه و چه ارتباطی با برنامه نویسی شبکه داره؟
یه رستوران رو تصور کن. این رستوران یه آشپزخونه داره که پخت و پز داخلش انجام میشه، آشپزخونه ساختار و پیچیدگی خودشو داره و اصولا کسی از بیرون واردش نمیشه تا سفارش ثبت کنه. وقتی شما میری رستوران، باید به پیشخدمت سفارشت رو ثبت کنی. پیشخدمت رابط بین تو و آشپزخونه ایی میشه که قراره درخواستت رو آماده کنه و اینم در نظر بگیر که آشپزخونه داره چندین غذارو باهم آماده میکنه و کلا یه پیچیدگی و شلوغی خاصی اونجا حکم فرماست.
طبق این مثال، پیشخدمت نقش Socket رو بازی میکنه.
وقتی یک نرم افزار قراره ارتباط شبکه ایی با یک نرم افزار دیگه برقرار کنه، باید بتونه با لایه های مختلف OSI صحبت کنه، سخت افزار و کارت شبکه رو مدیریت کنه، پورت هارو مدیریت کنه و... سیستم عامل نمیزاره هرکسی که از راه رسید بیاد و این بخش هارو به روش خودش مدیریت کنه و ازشون استفاده کنه، سیستم عامل میاد Socket رو ارائه میده، تا من بتونم به روش امن تر و کنترل شده تر از شبکه و برنامه نویسی شبکه استفاده کنم.
درنتیجه، برای برقراری ارتباط بین دو کامپیوتر، باید درون هرکدوم یک سوکت ساخته بشه و به واسطه این سوکت ها، ارتباط رو برقرار میکنیم.
سوکت به زبان ساده ترکیبی از IP و Portعه که در ادامه بیشتر باهاش آشنا میشیم.
شروع سوکت نویسی با پایتون.
نکته. برنامه نویسی شبکه (سوکت نویسی) فقط مختص به پایتون نمیشه و با زبان های مختلف میتونیم تحت شبکه برنامه نویسی کنیم.
برای برنامه نویسی شبکه در پایتون، کتابخونه های مختلفی وجود داره که تو این آموزش قراره با کتابخونه socket کار کنیم.
همینطور که بالاتر گفته شد، باید هم سمت Server یک سوکت ایجاد کنیم و هم سمت Client و در نتیجه از طریق این سوکت ها ارتباط رو برقرار کنیم. کد زیر مربوط میشه به ساخت Socket سمت سرور:
import socket # بصورت پیشفرض داخل پایتون وجود داره s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # AF_INET = IPv4 # SOCK_STREAM = TCP (Connection Oriented)
یه متغیر ساختم به اسم s و برای مقدارش از کلاس socket که داخل ماژول socket وجود داره استفاده کردم. تو ورودی کلاس socket دوتا آرگومان رو نوشتم. با آرگومان اول مشخص کردم، Socketیی که میخوام بسازم، باید از IPv4 استفاده کنه. با آرگومان دوم مشخص کردم که باید از پروتکل TCP برای ساخت Socket استفاده کنه.
حالت های دیگش:
s2 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) # AF_INET6 = IPv6 # SOCK_DGRAM = UDP (Connection Less) # ترکیبی هم میشه استفاده کرد
یه فایل داریم به اسم server.py و کدهای زیر داخلش قرار داره:
import socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
این کد تا به الان فقط قالب اون سوکت رو آماده کرده. به این صورت قالب بندی کرده که سوکت باید از IPv4 و TCP استفاده کنه.
تو قدم بعدی باید تعریف کنم که سوکت، روی چه آیپی و پورتی باید سوار بشه. کد:
import socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind(("127.0.0.1", 50111))
متد bind به سوکت میگه IP و پورتی که به عنوان ورودی گرفتم رو، رزرو کن. تا قبل از bind سوکت فقط یه در ارتباطی خالیه. بعد از bind یه آدرس داره که سیستم عامل میدونه باید روی اون آدرس، بسته های مربوط به سوکت رو ارسال/دریافت کنه.
متد bind یک ورودی Tuple میگیره. داخل Tuple باید ابتدا آدرس IP که میخوام سوکت روی اون فعال بشه رو وارد کنم. برای این آدرس مقادیر مختلفی میتونم بنویسم.
localhost: اگه این کلمه رو بنویسم یعنی سوکت فقط روی کامپیوتر خودم ساخته بشه و کسی نمیتونه بهش متصل بشه (حتی اگه داخل یک LAN باشیم)
127.0.0.1: اگه این IP رو قرار بدم فرقی با localhost نداره (برای زمان هایی که بخوام سرور و کلاینت رو روی یک کامپیوتر اجرا کنم)
192.168.1.100: میتونم IP محلی خودم رو قرار بدم و بگم درخواست ها باید سمت این IP ارسال بشه که با این حرکت دیگر کامیپوترهای موجود در LAN میتونن وصل بشن.
ورودی دوم Tuple مربوط میشه به شماره پورتی که سوکت باید روی اون فعال بشه. پیشنهاد میکنم از پورت 1024 تا 65535 یه عددی رو انتخاب کنید.
تا الان سوکت رو ساختیم و با متد bind یک IP و Port رو براش مشخص کردیم.
تو قدم بعد باید سوکت رو ببرم تو حالت انتظار یا گوش دادن. با این حرکت به سیستم عامل میگم اون سوکتی که ساختم رو بزارش روی حالت گوش دادن تا اگه درخواستی از سمت Client اومد متوجه بشه.
import socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind(("127.0.0.1", 50111)) s.listen(1)
وقتی چند Client همزمان میخوان وصل بشن، سیستم باید یک صف از درخواست های درحال انتظار درست کنه.
ورودی که متد listen دریافت میکنه تعداد حداکثر اتصالاتی هست که میتونن توی اون صف، منتظر بمونن. مثال:
s.listen(5)
تو کد بالا گفتم اگه 5 تا Client همزمان درخواست بدن و Server هنوز هیچکدوم رو accept نکرده باشه، درخواست ششم رو رد کن. این حرکت باعث نمیشه که سرور بیشتر از 5 تا Client رو قبول نکنه، فقط گفته که 5 تا بیشتر تو صف نمونن. (توی فایل server.py همون کد s.listen(1) رو نوشتم)
برای درک بهتر مراحل، فرض کن یه مغازه باز کردی!
socket: ساختن یا اجاره مغازه.
bind: نصب تابلو با آدرس و شماره تماس.
listen: باز کردن در مغازه و گذاشتن صف برای مشتری ها.
accept: پذیرش هر مشتری و شروع گفتگو.
درنتیجه، بعد از اینکه socket رفت روی حالت listen، باید درخواست هایی که ارسال میشه رو accept کنیم.
import socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind(("127.0.0.1", 50111)) s.listen(1) client, address = s.accept()
وقتی متد accept میاد یک درخواست رو قبول میکنه یا به عبارتی، میاد یک ارتباطی رو ایجاد میکنه، دوتا مقدار رو برمیگردونه و من این مقادیر رو داخل client و address ذخیره کردم.
client: زمانی که accept اجرا میشه، یه سوکت جدید ساخته میشه که این سوکت فقط مختص به همون کلاینتیه که درخواستش قبول شده. این سوکت جدید با سوکت اصلی که تو متغیر s ذخیره کردم متفاوته و کتابخونه سوکت اینکارو انجام میده تا سوکت/ارتباط هر کلاینت مجزا باشه و از طرفی، سوکت اصلی، رو همون حالت و آدرس ها درحال listen بمونه تا بتونه به دیگر درخواست ها واکنش نشون بده. پس تو متغیر client سوکت جدید قرار میگیره و از این لحظه به بعد هرکاری با اون کلاینت داشته باشم رو باید با این متغیر انجام بدم.
address: تو این متغیر IP و Port کلاینت در قالب Tuple ذخیره میشه.
عکس پایین مراحل ارتباط دوتا socket رو نشون میده.

همینطور که قبلا گفتیم، سوکت باید هم سمت Server ساخته بشه هم سمت Client. طبق عکس، تو سمت Server اول از همه باید یک socket تعریف بشه. اینکارو با کد زیر انجام دادیم:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
بعدش باید سوکت رو روی یک آدرسی bind کنیم و با کد زیر اینکارو انجام دادیم:
s.bind(("127.0.0.1", 50111))
تو قدم بعدی، سوکت باید روی حالت listen بره و منتظر بمونه تا درخواست ها سمتش ارسال بشن.
s.listen(1)
تو قدم بعدی باید متد accept رو تعریف کنیم تا درخواست هایی که از سمت Client ها ارسال میشه رو قبول کنیم.
client, address = s.accept()
حالا به سمت Client دقت کن.

تو سمت کلاینت هم اولین قدم، ساخت socket هست. (دارم فایل client.py رو مینویسم)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
قدم بعدی ارسال درخواست به سمت Serverعه.
s.connect(("127.0.0.1", 50111))
فایل server.py:
import socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind(("127.0.0.1", 50111)) s.listen(1) client, address = s.accept() print(f"New Connection:\nclient: {client}\naddress: {address}") client.send(b"Hello Client")
فایل client.py:
import socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(("127.0.0.1", 50111)) print("Ok") print(s.recv(1024))
من میام server.py رو اجرا میکنم و برنامه میره رو حالت listen.
بعدش میام client.py رو اجرا میکنم. به محض اجرا شدن، هر دو فایل خروجی هایی رو برمیگردونن.
خروجی server.py:
New Connection: client: <socket.socket fd=180, family=2, type=1, proto=0, laddr=('127.0.0.1', 50111), raddr=('127.0.0.1', 63716)> address: ('127.0.0.1', 63716)
خروجی client.py:
Ok b'Hello Client'
تو فایل server.py گفتم هرموقع درخواستی رو accept کردی بیا مقدار متغیر client و server رو نمایش بده.
بعدش متغیر client رو صدا زدم، این متغیر اختصاص داره به همون کلاینتی که از طریق سوکت بهم متصل شده و، متد send رو فراخانی کردم، یعنی میخوام یه داده ایی رو برای اون کلاینت بفرستم. توی ورودی متد متن Hello Client رو بصورت byte ارسال کردم (راجب این متد بعدا توضیح میدم)
توی فایل client.py متد connect رو نوشتم و آدرس سروری که میخوام بهش وصل بشم رو در قالب Tuple وارد کردم. بعدش print رو قرار دادم که اگه تونست به سرور متصل بشه، متن OK رو print کنه. در آخر متغیر s رو که مربوط میشه به ارتباط من با سوکت هدف رو صدا زدم و متد recv رو فراخانی کردم. این متد منتظر میمونه تا داده ایی از طرف مقابل ارسال بشه و دریافت کنه، حالا من چیزی که تو سمت سرور ارسال کردم رو اینجا دریافت و print میکنم که خروجی همون Hello Client هست.
به مقدار متغیر address که موقع accept کردن ارتباط ساخته شده دقت کن:
address: ('127.0.0.1', 63716)
اینجا IP و Port کلاینت رو میبینم. کلاینت وقتی میخواد به یک کامپیوتری متصل بشه، میاد یک Port تصادفی ایجاد میکنه و ارتباطش رو به این پورت گره میزنه. چرا اینکارو میکنه؟ شما در آن واحد با چندین سرور و کامپیوتر دیگه ارتباط دارید، برای مثال داخل مرورگر تب های مختلفی بازه که هرکدوم سایت خاصی رو بالا اوردن، از طرفی ممکنه نرم افزارهایی مثل تلگرام هم روی سیستمتون باز باشه که اون هم اتصالات خاص خودشو داره. کامپیوترها این Port های تصادفی رو ایجاد میکنن که وقتی بسته ایی رو ارسال میکنن به مقصد و جوابش برمیگرده، بدونن این جوابی که اومده مربوط به کدوم سرویس یا نرم افزار میشه.
یه دستور وجود داره به اسم netstat -n که میتونم درخواست های ارسال/دریافت شده رو به همراه Portشون ببینم.
فایل server.py:
import socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind(("localhost", 50111)) s.listen(1) input("Accept?") client, address = s.accept() print(f"New Connection:\nclient: {client}\naddress: {address}") client.send(b"Hello Client")
فایل client.py:
import socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(("localhost", 50111)) print("Ok") print(s.recv(1024))
یه ترمینال باز کردم و فایل server.py رو اجرا کردم. به محض اجرا شدن عبارت ?Accept رو چاپ کرد که یعنی سوکت ساخته شد، bind شد و رفت روی حالت listen با تعداد 1 کاربر ولی تا زمانی که من Enter نزنم، دستور بعدی که accept هست اجرا نمیشه. میزارم روی همین input بمونه.
سه تا ترمینال دیگه باز میکنم و داخل هرکدوم، فایل client.py رو فراخانی میکنم.
ترمینال اول که فایل رو داخلش اجرا کردم مقدار OK رو برگردوند. یعنی تونست به سرور connect بشه.
داخل ترمینال دوم هم فایل رو اجرا میکنم و مجدد مقدار OK چاپ میشه.
داخل ترمینال سوم هم فایل رو اجرا میکنم ولی خروجی برنمیگرده، بعد از مدتی خطای Connection timed out میده.
سوال! من تو سمت Server متد accept رو نزاشتم فراخانی بشه، پس چرا خروجی client.py مقدار OK بود و خطا نداد؟ متد connect که تو سمت Client وجود داره میاد یک Three-Way Handshake رو با Server انجام میده و وقتی این Three-Way Handshake کامل بشه، میره سراغ خط بعد که در نتیجه OK چاپ میشه. connect ارتباطی با accept نداره چون اون سمت Server هست و connect فقط وظیفش اینه که یک Connection-Oriented با سرور برقرار کنه، حالا اینکه سرور اون درخواست رو accept کنه یا نه به خودش مربوطه ولی تا اینجای داستان connect تو وظیفه ی خودش موفق بوده و خطا نداد.
سوال بعدی! من برای listen تعیین کردم که فقط 1 نفر بتونه تو صف انتظار بمونه. چرا وقتی ترمینال اول هنوز accept نشده، ترمینال دوم تونسته یک Three-Way Handshake انجام بده و همراه با ترمینال اول بره داخل صف انتظار؟ بزار خیلی ساده بگم! عددی که من به listen ورودی میدم، میتونه تو سیستم عامل های مختلف یکم بزرگتر در نظر گرفته بشه، هر سیستم عامل ممکنه رفتار متفاوتی نسبت به این عدد نشون بده که دلایل فنی داره و من نمیخوام بیام ریز بشم که چرا و چطور! فقط این رو بدونید که برمیگرده به کرنل سیستم عامل.
فایل server.py:
import socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind(("localhost", 50111)) s.listen(1) client, address = s.accept() print(f"New Connection: {address}") client.send(b"Hello Client")
فایل client.py:
import socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(("localhost", 50111)) print(s.recv(1024))
وقتی Server تو حالت listen باشه و یک Client بهش درخواست بزنه، اون رو accept میکنه، پیام Hello Client رو براش ارسال میکنه و برنامه یا سوکت بسته میشه. من میخوام Server همیشه تو حالت listen بمونه. برای اینکار میتونم کدم رو بدین شکل تغییر بدم:
import socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind(("localhost", 50111)) s.listen(1) while True: client, address = s.accept() print(f"New Connection: {address}") client.send(b"Hello Client")
داخل حلقه while متد accept رو فراخانی کردم، برنامه روی اون متد منتظر میمونه تا درخواستی بیاد و قبولش کنه. وقتی درخواستی رو قبول کرد، print اجرا میشه و بعد از اون هم یک پیغامی به سمت Client فرستاده میشه. بعد از این موارد، مجدد حلقه تکرار میشه و برنامه روی accept منتظر میمونه تا درخواست دیگه ایی بیاد و قبولش کنه.
اگه بخوام ارتباطم رو یکبار accept کنم و دستورات مربوط به ارسال پیام مدام اجرا بشه:
import socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind(("localhost", 50111)) s.listen(1) client, address = s.accept() print(f"New Connection: {address}") while True: msg = input("Enter message: ").encode() client.send(msg)
وقتی این کد رو برای Server نوشتم باید سمت Client هم به شکلی کد رو بنویسم که مدام بیاد داده ایی رو دریافت و نمایش بده:
import socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(("localhost", 50111)) print("Connect Successfully") while True: print(s.recv(1024).decode())
فایل server.py:
import socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind(("localhost", 50111)) s.listen(1) while True: client, address = s.accept() print(f"New Connection: {address}") msg = input("Enter Message: ").encode() client.send(msg)
فایل client.py:
import socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(("localhost", 50111)) print("Connect Successfully") print(s.recv(1024))
یه ترمینال باز میکنم و فایل server.py رو اجرا میکنم که روی accept منتظر میمونه.
یه ترمینال دیگه باز میکنم و client.py رو اجرا میکنم. به محض اجرا شدن عبارت Connect Successfully رو چاپ میکنه و روی متد recv منتظر میمونه تا داده ایی از سمت سرور ارسال بشه. همچنین تو ترمینال Server عبارت New Connection به همراه اطلاعات Client چاپ میشه.
توی ترمینال مربوط به Server پیام Hi رو مینویسم و Enter میزنم که این پیام سمت کلاینت ارسال میشه و نمایش میده.
بعد این مراحل سوکت کلاینت بسته میشه اما سوکت سرور همچنان باز و روی حالت listen قرار داره (چون داخل حلقه نوشتم)
یبار دیگه client.py رو اجرا میکنم ولی تو سمت سرور پیامی رو ارسال نمیکنم، یعنی رو همون recv منتظر میمونه.
یه ترمینال دیگه هم باز میکنم و client.py رو اجرا میکنم، به محض اجرا شدن عبارت Connect Successfully رو نمایش میده که یعنی Three-Way Handshake رو تونسته با سرور انجام بده اما تو ترمینال مربوط به Server عبارت New Connection چاپ نمیشه چون درخواست هنوز accept نشده و توی صف قرار گرفته.
اگه من توی ترمینال سرور عبارت Hello رو بنویسم و Enter بزنم، پیغام برای ترمینال اول ارسال میشه و سوکت بسته میشه، بعدش درخواست ترمینال دوم که داخل صف قرار داشت، accept میشه و حالا میتونم به اون یک پیغامی رو ارسال کنم.
سوالی که به وجود میاد اینه که هر سوکت یا Client رو چطور جداگانه ذخیره کنم تا بصورت همزمان بتونم با چندین Client ارتباطم رو داشته باشم؟! برای این مورد باید از برنامه نویسی چندنخی کمک بگیریم و هر Client رو به یک thread اختصاص بدیم. شاید بعدها این مورد رو توضیح دادم.
یه سوال دیگه. چطور با Client (فرضا یه دونه کلاینت) ارتباط Full-Duplex داشته باشم؟ ارتباط Full-Duplex یعنی بصورت همزمان هم بتونم ارسال انجام بدم و هم دریافت داشته باشم. خود پروتکل TCP بصورت Full-Duplex کار میکنه اما متدهایی مثل recv توی socket باعث میشن برنامه بلاک بشه تا زمانی که داده ایی دریافت کنه. برای حل این مشکل میتونیم send و recv رو داخل thread های جدا تعریف کنیم.
سوکت نویسی با UDP به چه شکله؟
UDP چون Connectionlessعه به متدهایی مثل listen و accept تو سمت سرور و به متد connect تو سمت کلاینت احتیاجی نداره.
فایل server.py:
import socket s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.bind(("localhost", 50111)) data, client = s.recvfrom(1024) print(f"from: \"{client}\" data: \"{data}\"") s.sendto(b"Hi Client", client)
فایل client.py:
import socket s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.sendto(b"Hi", ("localhost", 50111)) data, server = s.recvfrom(1024) print(f"from \"{server}\" data: \"{data}\"")
متد send و recv متدهایی هستن که داخل Object مربوط به Client در TCP وجود دارن. چون از UDP استفاده کردم، سوکت خاصی رو نساختم و بنابراین باید از متدهای sendto و recvfrom که داخل خود s یا Object اصلی socket وجود دارن استفاده کنم.
متد recvfrom تا زمانی که بسته ایی براش ارسال نشه بلاک میمونه و وقتی چیزی ارسال بشه، اون رو دریافت میکنه و داده رو داخل data ذخیره میکنه. همچنین اطلاعات کسی که داده رو ارسال کرده بصورت Tuple درون server قرار میده.
متد sendto داده و مشخصات کسی که داده رو باید براش ارسال کنه رو میگیره.
بررسی متد send و recv در TCP Connection.

زمانی که ارتباط بین دو سوکت برقرار میشه، باید با متدهای send و recv بسته هایی رو ارسال و دریافت کنیم و بعد از اتمام کار، با متد close سوکت رو ببندیم.
متد send یک ورودی رو بصورت byte دریافت میکنه و به سمت مقصد ارسال میکنه. این متد، هم برای Client وجود داره و هم برای Server.
زمانی که داده ایی به سمت مقصد ارسال میشه، اون داده وارد بافر سیستم عامل میشه. متد recv میاد از بافر، اطلاعاتی که به سمت سوکت اومده رو دریافت و برمیگردونه. تو ورودی این متد باید حداکثر تعداد بایتی که میخوام از بافر بیرون بکشه رو مشخص کنم. برای مثال:
s.recv(1024)
اینجا گفتم 1024 بایت رو برام بیرون بکش. اگه متد recv صدا زده بشه ولی چیزی توی بافر نباشه (یعنی send تو طرف مقابل انجام نشده باشه) برنامه بلاک میشه و منتظر میمونه داده ایی داخل بافر قرار بگیره و بعد بیرون بکشه.
فایل server.py:
import socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind(("localhost", 50111)) s.listen(1) client, address = s.accept() client.send(b"Hello")
فایل client.py:
import socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(("localhost", 50111)) print(s.recv(1))
خروجی فایل client.py:
b'H'
ورودی متد recv رو 1 قرار دادم که یعنی فقط 1 بایت از بافر بیرون بکشه و فقط کاراکتر اول عبارت Hello رو برگردوند. فایل client.py:
import socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(("localhost", 50111)) while True: msg = s.recv(1).decode() if msg == "": break print(msg)
خروجی:
H e l l o
از سمت سرور یکبار send انجام شد و اون مقادیر داخل بافر مقصد قرار گرفتن، حالا client.py هربار میره 1 بایت از بافر بیرون میکشه، بررسی میکنه که آیا چیزی که بیرون کشیده با یک رشته خالی برابر هست یا نه، اگه برابر نبود همون رو چاپ میکنه و اگه برابر بود حلقه شکسته میشه.
متد recv محدودیتی برای تعیین حداکثر بایت نداره ولی باید به منابع سیستم نگاه کنیم و یه عدد معقول رو قرار بدیم.
یه متد دیگه وجود داره به اسم sendall که اگه بخوام خلاصه بگم، نسخه بهتر و قابل اطمینان تر متد sendعه و پیشنهاد میکنم از این متد استفاده کنید.
یه توضیح راجب non-blocking بدم. متد recv همینطور که گفته شد وقتی داده ایی داخل بافر نباشه میره روی حالت بلاک. به دو روش میتونیم این رفتار رو تغییر بدیم. روش اول:
s.setblocking(False) # معادل s.settimeout(0.0)
تو این حالت، recv اصلا منتظر نمیمونه، به محض اجرا شدن و نبودن داده داخل بافر، خطای BlockingIOError رو اجرا میکنه. روش دوم:
s.settimeout(3)
تو این روش، recv سه ثانیه منتظر میمونه و بعد خطای BlockingIOError رو اجرا میکنه :)
چطوری فایل ارسال/دریافت کنم؟
فرقی نداره حجم فایل چقدره، همیشه باید فایل رو با یک اندازه مشخص به قطعه های کوچیک تقسیم و ارسال کنیم. فرضا یه فایل دارم با حجم 50MB و میخوام ارسالش کنم. میام هربار 64KB از فایلم رو میخونم و ارسال میکنم و تو مقصد هربار 64KB دریافت میشه و داخل فایل، write میکنه.
فایل server.py:
import socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind(("localhost", 50111)) s.listen(1) client, address = s.accept() print("Connection OK") input("Send?") with open("send.mp4", "rb") as mp4: chunk = mp4.read(65536) # 64KB while chunk: client.sendall(chunk) chunk = mp4.read(65536)
فایل client.py:
import socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(("localhost", 50111)) print("Connec with Server is OK") with open("recv.mp4", "wb") as mp4: data = s.recv(65536) # 64KB while data: mp4.write(data) data = s.recv(65536)
تو سمت Server وقتی فایل به انتها میرسه و متد recv فراخانی میشه، چون چیزی برای خوندن نمونده مقدار
b''
داخل متغیر chunk قرار میگیره. میدونید که این مقدار حالت False داره و وقتی برای شرط مربوط به حلقه while ازش استفاده کردم، یعنی تا زمانی حلقه اجرا بشه و عمل خوندن/ارسال انجام بشه که، چیزی داخل chunk قرار گرفته باشه. با این کار هم تو سمت Client و هم تو سمت Server حلقه رو تا زمانی جلو میبرم که مقادیری برای خوندن و ارسال وجود داشته باشه و حلقه همیشه اجرا نمونه.
متد close و بستن سوکت.

بعد از ساخت سوکت، برقراری ارتباط و تبادل داده ها، نوبت به پایان ارتباط و بستن سوکت میرسه.
متد close زمانی که صدا زده میشه، سوکت ساخته شده رو نابود و منابع مربوط به اون هم آزاد میکنه، همچنین مقدار زیر هم به سمت طرف مقابل ارسال میکنه تا طرف مقابل متوجه بشه که سوکت بسته شده:
b''
فایل server.py:
import socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind(("localhost", 50111)) s.listen(1) client, address = s.accept() print(f"New Connection: {address}") while True: msg = input("Message for send to client: ") if msg != "exit": client.send(msg.encode()) else: client.close() break
فایل client.py:
import socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(("localhost", 50111)) print("Connect Successfully") while True: msg = s.recv(1024) if msg: print(f"from server: {msg.decode()}") else: s.close() break
اگه ارتباط از یک سمت بصورت ناگهانی قطع بشه (یعنی بدون close)، بسته ایی برای طرف مقابل ارسال نمیشه و ما باید با مدیریت خطاها یا موارد مربوط به timeout این موضوع رو مدیریت کنیم.
فایل server.py:
import socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind(("localhost", 50111)) s.listen(1) client, address = s.accept() print(f"New Connection: {address}") while True: msg = input("Message for send to client: ") if msg != "exit": client.send(msg.encode()) else: client.close() break
فایل client.py:
import socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(("localhost", 50111)) print("Connect Successfully") while True: msg = s.recv(1024) if msg: print(f"from server: {msg.decode()}") else: s.close() break
فایل server و client رو اجرا میکنم و برنامه تو سمت سرور روی input بلاک میشه و تو سمت client هم روی recv بلاک میشه. تو سمت سرور داده ایی رو مینویسم و enter میزنم، این داده به سمت client ارسال میشه و client از حالت بلاک خارج، و داده رو نمایش میده. چون از while استفاده کردم، کلاینت تو recv دوباره بلاک میشه و server هم تو input.
میام فایل client رو میبندم، تو سمت سرور داده ایی رو مینویسم و enter میزنم، سرور اون داده رو با متد send ارسال میکنه و خطایی برنمیگرده (در صورتی که من فایل client رو بستم و اون سوکت وجود نداره)
مجدد روی input قفل میشه که من چیزی مینویسم و enter میزنم، الان دوباره متد send میخواد اجرا بشه ولی خطای BrokenPipeError میده.
چرا بار اول که متد send اجرا شد و سوکت کلاینت بسته شده بود خطا دریافت نکردم و بار دوم خطا داد؟
حقیقتا دلیل فنیش خیلی پیچیده و تخصصیه ولی اگه بخوام ساده توضیح بدم، باید بگم که...
ارسال (send) اولی موفق بود چون داده رو تو بافر خروجیِ، کرنل گذاشت! یعنی هنوز کرنل خبر نداشت یا FIN/RST از طرف مقابل پردازش نشده بود که ارتباط قطع شده. بین send اول و دوم، کرنل فهمید که طرف مقابل ارتباط رو بسته، پس وقتی دوباره send کردم، خطای EPIPE تولید شد که پایتون تبدیلش میکنه به BrokenPipeError.
خطای BrokenPipeError یعنی میخوام با سوکتی ارتباط بگیرم یا به سوکتی ارسال و دریافت داشته باشم که بسته شده.
فایل server.py:
import socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind(("localhost", 50111)) s.listen(1) client, address = s.accept() print(f"New Connection: {address}") while True: try: msg = input("Message for send to client: ") if msg != "exit": client.send(msg.encode()) else: client.close() break except BrokenPipeError: print("Connection Closed") client.close() break
فایل client.py:
import socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(("localhost", 50111)) print("Connect Successfully") while True: msg = s.recv(1024) if msg: print(f"from server: {msg.decode()}") else: s.close() break
تو کدهای بالا من سوکت اصلی که تو متغیر s ذخیره شده رو نبستم. دقت کنید هرجا که ارتباط قراره کلا قطع بشه، باید سوکت اصلی رو هم close کنیم.
ساخت یک Port Scan با کمک socket.
پورت اسکن یعنی بررسی کنیم چه پورت هایی روی یک سیستم باز هستن و گوش میدن.
موقع اتصال به یک پورت، سه حالت ممکنه رخ بده:
Open: سوکت به مقصد وصل میشه. یعنی روی اون پورت یه سرویس در حال گوش دادن وجود داره.
Closed: مقصد بلافاصله میگه این پورت و سرویس، فعال نیست (به شکل RST برمیگرده).
Filtered: مثلا فایروال وسطه و اجازه نمیده بفهمی پورت بازه یا بسته. معمولا نتیجشش میشه timeout یا reset مبهم.
متدی وجود داره به اسم connect_ex که بعد از درخواست به پورت، عددی رو برمیگردونه، اگه اون عدد 0 باشه یعنی پورت بازه و اگه هرچیز دیگه ایی باشه یعنی پورت بستس.
import socket for port in range(1, 65536): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.settimeout(1) result = s.connect_ex(("localhost", port)) if result == 0: print(f"port [{port}] is open") s.close()
با timeout زمان انتظار رو تنظیم میکنم و اگه نزارم، connect_ex ممکنه 30 ثانیه برای هر پورت منتظر بمونه.
کتابخونه socket تو کد بالا سعی میکنه یک Three-Way Handshake کامل با هر پورت انجام بده.
شما بجای localhost میتونید IP یکی دیگه از دستگاه های LAN رو وارد کنید.
برای مرور و درک بهتر سعی کنید کدهای زیر رو خودتون بنویسید و نتیجش رو ببینید.
فایل server.py:
import socket main_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) main_socket.bind(("localhost", 50111)) main_socket.listen(1) print(f"Server on \"localhost\" and port \"50111\"") client, address = main_socket.accept() print(f"\nConnect Successfully. Client IP: {address[0]} Client Port: {address[1]}\n\n") while True: from_client = client.recv(1024) if not from_client: client.close() break print(f"from client: {from_client.decode()}") to_client = input("Enter message for send: ") if to_client.lower() == "exit": client.close() break client.sendall(to_client.encode())
فایل client.py:
import socket server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.connect(("localhost", 50111)) print("Connect with \"localhost:50111\" Successfully\n\n") while True: to_server = input("Enter message for send: ") if to_server.lower() == "exit": server.close() break server.sendall(to_server.encode()) from_server = server.recv(1024) if not from_server: server.close() break print(f"from server: {from_server.decode()}")
داخل کتابخونه socket یسری متدهایی وجود داره که من اسمشون رو میزارم متدهای کمکی، بریم با چندتا از این متدها آشنا بشیم.
socket.gethostbyname("google.com") # 142.250.151.100 # این متد یه دامنه رو میگیره و آیپی مربوط بهش رو برمیگردونه socket.gethostbyaddr("8.8.8.8") # 'dns.google', [], ['8.8.8.8']) # این متد یه آیپی رو میگیره و نام هاست یا دامنه رو برمیگردونه print(socket.gethostname()) # این متد اسم کامپیوتر فعلی رو برمیگردونه print(socket.gethostbyname_ex()) # این متد نسخه پیشرفته تر متد بالاست و یک اسم رو میگیره، اسم اصلی، لیست اسم های مستعار و لیست آیپی هارو برمیگردونه. print(socket.getservbyname("http")) # 80 # این متد اسم یک سرویس رو میگیره و پورت مربوط بهش رو برمیگردونه print(socket.getservbyport(80)) # http # این متد یک پورت رو میگیره و سرویس مربوط بهش رو برمیگردونه
به آخر مقاله رسیدیم :)
خسته نباشید و امیدوارم که براتون مفید بوده باشه.
من که انرژی زیادی ازم رفت بخوام این مقاله رو بنویسم و لطف کنید با نظرات ارزشمندتون، انرژی از دست رفته من رو برگردونید ❤