مقدمه
در این مقاله، چند مثال ساده برای نشان دادن چگونگی استفاده از پایتون برای ایجاد ارتباطات شبکهای پایه ارائه میشود. برنامهنویسی شبکه به فرآیند نوشتن برنامههایی گفته میشود که میتوانند از طریق یک شبکه (مانند اینترنت یا شبکه محلی) با یکدیگر ارتباط برقرار کنند. پایتون به دلیل سادگی، خوانایی بالا و داشتن کتابخانههای قدرتمند داخلی، یکی از زبانهای محبوب برای این منظور است. کتابخانه استاندارد socket در پایتون، ابزارهای اساسی برای کار با سوکتها و پروتکلهای شبکه مانند TCP/IP و UDP را فراهم میکند.
مفهوم کلیدی: سوکتها
در برنامهنویسی شبکه، سوکت (Socket) یک نقطه پایانی برای ارتباط دوطرفه بین دو برنامه در شبکه است. هر سوکت با یک آدرس IP و یک شماره پورت مشخص میشود. پایتون از طریق ماژول socket امکان ایجاد و مدیریت سوکتها را فراهم میکند. دو نوع اصلی سوکت که بیشتر مورد استفاده قرار میگیرند عبارتند از:
- سوکتهای
TCP(StreamSockets): از پروتکلTCP(TransmissionControlProtocol) استفاده میکنند. این پروتکل اتصالگرا (Connection–Oriented) است، به این معنی که قبل از ارسال داده، یک اتصال پایدار بین کلاینت و سرور برقرار میشود.TCPتضمین میکند که دادهها به ترتیب و بدون خطا به مقصد برسند. - سوکتهای
UDP(DatagramSockets): از پروتکلUDP(UserDatagramProtocol) استفاده میکنند. این پروتکل بدون اتصال (Connectionless) است. دادهها به صورت بستههای جداگانه (دیتاگرام) ارسال میشوند و هیچ تضمینی برای رسیدن، ترتیب یا عدم خطای آنها وجود ندارد.UDPسریعتر ازTCPاست زیرا سربار کمتری دارد.
مثال ۱: کلاینت TCP ساده
این برنامه به یک سرور مشخص (با آدرس IP و پورت معین) متصل میشود، پیامی ارسال میکند و پاسخی از سرور دریافت میکند.
import socket
# اطلاعات سرور
SERVER_HOST = '127.0.0.1' # آدرس IP سرور (اینجا لوکال هاست)
SERVER_PORT = 9999 # پورت سرور
# ایجاد سوکت TCP/IP
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
# اتصال به سرور
print(f"درحال اتصال به {SERVER_HOST}:{SERVER_PORT}...")
client_socket.connect((SERVER_HOST, SERVER_PORT))
print("اتصال برقرار شد.")
# ارسال پیام به سرور
message = "سلام از طرف کلاینت!"
client_socket.sendall(message.encode('utf-8')) # ارسال داده بصورت بایت
print(f"پیام ارسال شد: {message}")
# دریافت پاسخ از سرور (حداکثر 1024 بایت)
response = client_socket.recv(1024)
print(f"پاسخ دریافت شد: {response.decode('utf-8')}")
finally:
# بستن سوکت
print("بستن اتصال.")
client_socket.close()
توضیح کد:
import socket: ماژول مورد نیاز برای کار با سوکتها را وارد میکنیم.SERVER_HOSTوSERVER_PORT: آدرسIPو شماره پورت سروری که میخواهیم به آن متصل شویم را مشخص میکنیم.127.0.0.1به معنای کامپیوتر محلی (localhost) است.socket.socket(socket.AF_INET, socket.SOCK_STREAM): یک شیء سوکت جدید ایجاد میکند.socket.AF_INET: نشان میدهد که از آدرسدهیIPv4استفاده میکنیم.socket.SOCK_STREAM: مشخص میکند که این یک سوکتTCPاست.
client_socket.connect((SERVER_HOST, SERVER_PORT)): تلاش برای برقراری اتصال با سرور مشخص شده.client_socket.sendall(message.encode('utf-8')): پیام رشتهای را به بایت تبدیل کرده (encode('utf-8')) و از طریق سوکت به سرور ارسال میکند.sendallتضمین میکند که تمام دادهها ارسال شوند.client_socket.recv(1024): منتظر دریافت داده از سرور میماند (حداکثر 1024 بایت). دادههای دریافتی به صورت بایت هستند..decode('utf-8'): بایتهای دریافتی را دوباره به رشته قابل خواندن تبدیل میکند.client_socket.close(): اتصال و سوکت را میبندد. این کار در بلوکfinallyانجام میشود تا اطمینان حاصل شود که حتی در صورت بروز خطا، سوکت بسته میشود.
مثال ۲: سرور TCP ساده
این برنامه منتظر اتصال کلاینتها میماند، پیام آنها را دریافت میکند و پاسخی برایشان ارسال میکند.
import socket
# اطلاعات سرور
HOST = '127.0.0.1' # آدرس IP برای گوش دادن (0.0.0.0 برای همه اینترفیسها)
PORT = 9999 # پورت برای گوش دادن
# ایجاد سوکت TCP/IP
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# جلوگیری از خطای "Address already in use" هنگام راهاندازی مجدد سریع سرور
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# بایند کردن سوکت به آدرس و پورت
server_socket.bind((HOST, PORT))
# شروع به گوش دادن برای اتصالات ورودی (حداکثر 5 اتصال در صف انتظار)
server_socket.listen(5)
print(f"سرور در آدرس {HOST}:{PORT} در حال گوش دادن است...")
while True:
try:
# پذیرش اتصال جدید
client_socket, client_address = server_socket.accept()
print(f"اتصال جدید از {client_address}")
try:
# دریافت داده از کلاینت (حداکثر 1024 بایت)
data = client_socket.recv(1024)
if data:
message = data.decode('utf-8')
print(f"پیام دریافت شده از {client_address}: {message}")
# ارسال پاسخ به کلاینت
response = "پیام دریافت شد!"
client_socket.sendall(response.encode('utf-8'))
print(f"پاسخ به {client_address} ارسال شد.")
else:
# اگر دادهای دریافت نشود، یعنی کلاینت اتصال را بسته است
print(f"کلاینت {client_address} اتصال را قطع کرد.")
except ConnectionResetError:
print(f"اتصال با {client_address} به طور ناگهانی قطع شد.")
finally:
# بستن سوکت کلاینت
client_socket.close()
print(f"اتصال با {client_address} بسته شد.")
except KeyboardInterrupt:
print("\nدریافت سیگنال خاتمه (Ctrl+C). در حال خاموش کردن سرور...")
break
except Exception as e:
print(f"خطایی رخ داد: {e}")
break
# بستن سوکت سرور
server_socket.close()
print("سوکت سرور بسته شد.")
توضیح کد:
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1): این خط به سیستم عامل اجازه میدهد تا پورت بلافاصله پس از بسته شدن سرور دوباره استفاده شود، که برای تست و توسعه مفید است.server_socket.bind((HOST, PORT)): سوکت را به آدرسIPو پورت مشخص شده “متصل” (bind) میکند. سرور فقط به درخواستهایی که به این آدرس و پورت میآیند گوش میدهد.server_socket.listen(5): به سوکت میگوید که شروع به پذیرش اتصالات ورودی کند. عدد 5 حداکثر تعداد اتصالاتی است که میتوانند قبل از رد شدن در صف انتظار بمانند.while True:: سرور در یک حلقه بینهایت اجرا میشود تا بتواند به طور مداوم به کلاینتهای جدید سرویس دهد.server_socket.accept(): این تابع منتظر میماند تا یک کلاینت متصل شود. وقتی کلاینتی متصل میشود، یک سوکت جدید (client_socket) مخصوص آن کلاینت و آدرس کلاینت (client_address) را برمیگرداند. ارتباط با کلاینت از طریقclient_socketانجام میشود، در حالی کهserver_socketهمچنان برای پذیرش اتصالات جدید گوش میدهد.data = client_socket.recv(1024): دادههای ارسال شده توسط کلاینت متصل را میخواند.client_socket.sendall(...): پاسخی را به همان کلاینت ارسال میکند.client_socket.close(): پس از اتمام کار با کلاینت، سوکت مخصوص آن کلاینت بسته میشود.- بلوک
try...except KeyboardInterruptبه ما اجازه میدهد تا با فشار دادنCtrl+Cسرور را به صورت تمیز متوقف کنیم. server_socket.close(): در نهایت، هنگامی که حلقه اصلی متوقف میشود، سوکت اصلی سرور نیز بسته میشود.
مثال ۳: کلاینت UDP ساده
این برنامه یک پیام را با استفاده از UDP به یک سرور مشخص ارسال میکند، بدون اینکه نیاز به برقراری اتصال اولیه باشد.
import socket
# اطلاعات سرور
SERVER_HOST = '127.0.0.1'
SERVER_PORT = 9998
# پیام برای ارسال
message = "سلام UDP از طرف کلاینت!"
# ایجاد سوکت UDP/IP
client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
# ارسال پیام به سرور
# در UDP نیازی به connect نیست، آدرس مقصد همراه داده ارسال میشود
client_socket.sendto(message.encode('utf-8'), (SERVER_HOST, SERVER_PORT))
print(f"پیام UDP به {SERVER_HOST}:{SERVER_PORT} ارسال شد: {message}")
# در UDP ساده، معمولا منتظر پاسخ نمیمانیم (مگر اینکه پروتکل خاصی تعریف شده باشد)
# میتوانیم recvfrom را برای دریافت پاسخ احتمالی فراخوانی کنیم:
# print("منتظر پاسخ...")
# data, server_address = client_socket.recvfrom(1024)
# print(f"پاسخ از {server_address}: {data.decode('utf-8')}")
finally:
# بستن سوکت
print("بستن سوکت کلاینت UDP.")
client_socket.close()
توضیح کد:
socket.socket(socket.AF_INET, socket.SOCK_DGRAM): یک سوکتUDPایجاد میکند (SOCK_DGRAMبه جایSOCK_STREAM).client_socket.sendto(message.encode('utf-8'), (SERVER_HOST, SERVER_PORT)): پیام را به همراه آدرس مقصد (هاست و پورت سرور) ارسال میکند. برخلافTCP،نیازی به فراخوانیconnectنیست.- در
UDP،دریافت پاسخ تضمین شده نیست و ممکن است اصلاً پاسخی ارسال نشود. اگر انتظار پاسخ دارید، باید ازrecvfromاستفاده کنید. client_socket.close(): سوکت بسته میشود.
مثال ۴: سرور UDP ساده
این برنامه منتظر دریافت پیامهای UDP در یک پورت مشخص میماند و آدرس فرستنده را چاپ میکند.
import socket
# اطلاعات سرور
HOST = '127.0.0.1'
PORT = 9998
# ایجاد سوکت UDP/IP
server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# بایند کردن سوکت به آدرس و پورت
server_socket.bind((HOST, PORT))
print(f"سرور UDP در آدرس {HOST}:{PORT} در حال گوش دادن است...")
while True:
try:
# دریافت داده و آدرس فرستنده (حداکثر 1024 بایت)
data, client_address = server_socket.recvfrom(1024)
message = data.decode('utf-8')
print(f"پیام از {client_address}: {message}")
# (اختیاری) ارسال پاسخ به کلاینت
# response = "پیام UDP شما دریافت شد."
# server_socket.sendto(response.encode('utf-8'), client_address)
except KeyboardInterrupt:
print("\nدریافت سیگنال خاتمه (Ctrl+C). در حال خاموش کردن سرور...")
break
except Exception as e:
print(f"خطایی رخ داد: {e}")
# بستن سوکت سرور
server_socket.close()
print("سوکت سرور UDP بسته شد.")
توضیح کد:
socket.socket(socket.AF_INET, socket.SOCK_DGRAM): سوکتUDPایجاد میشود.server_socket.bind((HOST, PORT)): سوکت به آدرس و پورت محلی بایند میشود تا بتواند پیامهای ورودی به آن پورت را دریافت کند.server_socket.recvfrom(1024): منتظر دریافت یک بستهUDPمیماند. این تابع هم دادههای دریافتی (data) و هم آدرس فرستنده (client_address) را برمیگرداند. این برخلافacceptدرTCPاست که یک سوکت جدید برمیگرداند.- در صورت نیاز، سرور میتواند با استفاده از
sendtoو آدرس کلاینت (client_address) که ازrecvfromبه دست آمده، پاسخی را به کلاینت ارسال کند. - مانند سرور
TCP،ازtry...exceptبرای مدیریت خروج باCtrl+Cاستفاده شده است. server_socket.close(): سوکت سرور در پایان بسته میشود.
فراتر از اصول اولیه
مثالهای بالا اصول اولیه کار با سوکتهای TCP و UDP در پایتون را نشان میدهند. برای کاربردهای واقعیتر و پیچیدهتر، ممکن است نیاز به استفاده از مفاهیم و کتابخانههای دیگری داشته باشید:
- کار با پروتکلهای سطح بالاتر: برای کار با پروتکلهایی مانند
HTTP،FTP،SMTPو غیره، پایتون کتابخانههای سطح بالاتری مانندhttp.client,urllib,ftplib,smtplibو کتابخانههای شخص ثالث محبوبی مانندrequestsرا ارائه میدهد که جزئیات کار با سوکتها را پنهان میکنند. - برنامهنویسی ناهمزمان (
AsynchronousProgramming): برای سرورهایی که نیاز به مدیریت همزمان تعداد زیادی کلاینت دارند، استفاده از کتابخانههایی مانندasyncioیا فریمورکهایی مانندTwistedیاTornadoمیتواند کارایی را به طور قابل توجهی بهبود بخشد. - امنیت: برای ارتباطات امن، باید از
SSL/TLSاستفاده کرد که میتوان آن را با ماژولsslپایتون به سوکتها اضافه کرد.
نتیجهگیری
پایتون با ماژول socket خود، یک راه ساده و قدرتمند برای ورود به دنیای برنامهنویسی شبکه فراهم میکند. با درک مفاهیم پایه سوکتها، TCP و UDP، میتوانید شروع به ساخت برنامههای شبکهای خود کنید، از کلاینتها و سرورهای ساده گرفته تا پایهای برای سیستمهای پیچیدهتر. این مثالها نقطه شروع خوبی برای کاوش بیشتر در قابلیتهای شبکه پایتون هستند.
