چند مثال ساده برای برنامه‌نویسی شبکه با پایتون و توضیح آن‌ها

مقدمه

در این مقاله، چند مثال ساده برای نشان دادن چگونگی استفاده از پایتون برای ایجاد ارتباطات شبکه‌ای پایه ارائه می‌شود. برنامه‌نویسی شبکه به فرآیند نوشتن برنامه‌هایی گفته می‌شود که می‌توانند از طریق یک شبکه (مانند اینترنت یا شبکه محلی) با یکدیگر ارتباط برقرار کنند. پایتون به دلیل سادگی، خوانایی بالا و داشتن کتابخانه‌های قدرتمند داخلی، یکی از زبان‌های محبوب برای این منظور است. کتابخانه استاندارد socket در پایتون، ابزارهای اساسی برای کار با سوکت‌ها و پروتکل‌های شبکه مانند TCP/IP و UDP را فراهم می‌کند.

مفهوم کلیدی: سوکت‌ها

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

  1. سوکت‌های TCP (Stream Sockets): از پروتکل TCP (Transmission Control Protocol) استفاده می‌کنند. این پروتکل اتصال‌گرا (ConnectionOriented) است، به این معنی که قبل از ارسال داده، یک اتصال پایدار بین کلاینت و سرور برقرار می‌شود. TCP تضمین می‌کند که داده‌ها به ترتیب و بدون خطا به مقصد برسند.
  2. سوکت‌های UDP (Datagram Sockets): از پروتکل UDP (User Datagram Protocol) استفاده می‌کنند. این پروتکل بدون اتصال (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()

توضیح کد:

  1. import socket: ماژول مورد نیاز برای کار با سوکت‌ها را وارد می‌کنیم.
  2. SERVER_HOST و SERVER_PORT: آدرس IP و شماره پورت سروری که می‌خواهیم به آن متصل شویم را مشخص می‌کنیم. 127.0.0.1 به معنای کامپیوتر محلی (localhost) است.
  3. socket.socket(socket.AF_INET, socket.SOCK_STREAM): یک شیء سوکت جدید ایجاد می‌کند.
    • socket.AF_INET: نشان می‌دهد که از آدرس‌دهی IPv4 استفاده می‌کنیم.
    • socket.SOCK_STREAM: مشخص می‌کند که این یک سوکت TCP است.
  4. client_socket.connect((SERVER_HOST, SERVER_PORT)): تلاش برای برقراری اتصال با سرور مشخص شده.
  5. client_socket.sendall(message.encode('utf-8')): پیام رشته‌ای را به بایت تبدیل کرده (encode('utf-8')) و از طریق سوکت به سرور ارسال می‌کند. sendall تضمین می‌کند که تمام داده‌ها ارسال شوند.
  6. client_socket.recv(1024): منتظر دریافت داده از سرور می‌ماند (حداکثر 1024 بایت). داده‌های دریافتی به صورت بایت هستند.
  7. .decode('utf-8'): بایت‌های دریافتی را دوباره به رشته قابل خواندن تبدیل می‌کند.
  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("سوکت سرور بسته شد.")

توضیح کد:

  1. server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1): این خط به سیستم عامل اجازه می‌دهد تا پورت بلافاصله پس از بسته شدن سرور دوباره استفاده شود، که برای تست و توسعه مفید است.
  2. server_socket.bind((HOST, PORT)): سوکت را به آدرس IP و پورت مشخص شده “متصل” (bind) می‌کند. سرور فقط به درخواست‌هایی که به این آدرس و پورت می‌آیند گوش می‌دهد.
  3. server_socket.listen(5): به سوکت می‌گوید که شروع به پذیرش اتصالات ورودی کند. عدد 5 حداکثر تعداد اتصالاتی است که می‌توانند قبل از رد شدن در صف انتظار بمانند.
  4. while True:: سرور در یک حلقه بی‌نهایت اجرا می‌شود تا بتواند به طور مداوم به کلاینت‌های جدید سرویس دهد.
  5. server_socket.accept(): این تابع منتظر می‌ماند تا یک کلاینت متصل شود. وقتی کلاینتی متصل می‌شود، یک سوکت جدید (client_socket) مخصوص آن کلاینت و آدرس کلاینت (client_address) را برمی‌گرداند. ارتباط با کلاینت از طریق client_socket انجام می‌شود، در حالی که server_socket همچنان برای پذیرش اتصالات جدید گوش می‌دهد.
  6. data = client_socket.recv(1024): داده‌های ارسال شده توسط کلاینت متصل را می‌خواند.
  7. client_socket.sendall(...): پاسخی را به همان کلاینت ارسال می‌کند.
  8. client_socket.close(): پس از اتمام کار با کلاینت، سوکت مخصوص آن کلاینت بسته می‌شود.
  9. بلوک try...except KeyboardInterrupt به ما اجازه می‌دهد تا با فشار دادن Ctrl+C سرور را به صورت تمیز متوقف کنیم.
  10. 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()

توضیح کد:

  1. socket.socket(socket.AF_INET, socket.SOCK_DGRAM): یک سوکت UDP ایجاد می‌کند (SOCK_DGRAM به جای SOCK_STREAM).
  2. client_socket.sendto(message.encode('utf-8'), (SERVER_HOST, SERVER_PORT)): پیام را به همراه آدرس مقصد (هاست و پورت سرور) ارسال می‌کند. برخلاف TCP، نیازی به فراخوانی connect نیست.
  3. در UDP، دریافت پاسخ تضمین شده نیست و ممکن است اصلاً پاسخی ارسال نشود. اگر انتظار پاسخ دارید، باید از recvfrom استفاده کنید.
  4. 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 بسته شد.")

توضیح کد:

  1. socket.socket(socket.AF_INET, socket.SOCK_DGRAM): سوکت UDP ایجاد می‌شود.
  2. server_socket.bind((HOST, PORT)): سوکت به آدرس و پورت محلی بایند می‌شود تا بتواند پیام‌های ورودی به آن پورت را دریافت کند.
  3. server_socket.recvfrom(1024): منتظر دریافت یک بسته UDP می‌ماند. این تابع هم داده‌های دریافتی (data) و هم آدرس فرستنده (client_address) را برمی‌گرداند. این برخلاف accept در TCP است که یک سوکت جدید برمی‌گرداند.
  4. در صورت نیاز، سرور می‌تواند با استفاده از sendto و آدرس کلاینت (client_address) که از recvfrom به دست آمده، پاسخی را به کلاینت ارسال کند.
  5. مانند سرور TCP، از try...except برای مدیریت خروج با Ctrl+C استفاده شده است.
  6. server_socket.close(): سوکت سرور در پایان بسته می‌شود.

فراتر از اصول اولیه

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

  • کار با پروتکل‌های سطح بالاتر: برای کار با پروتکل‌هایی مانند HTTP، FTP، SMTP و غیره، پایتون کتابخانه‌های سطح بالاتری مانند http.client, urllib, ftplib, smtplib و کتابخانه‌های شخص ثالث محبوبی مانند requests را ارائه می‌دهد که جزئیات کار با سوکت‌ها را پنهان می‌کنند.
  • برنامه‌نویسی ناهمزمان (Asynchronous Programming): برای سرورهایی که نیاز به مدیریت همزمان تعداد زیادی کلاینت دارند، استفاده از کتابخانه‌هایی مانند asyncio یا فریم‌ورک‌هایی مانند Twisted یا Tornado می‌تواند کارایی را به طور قابل توجهی بهبود بخشد.
  • امنیت: برای ارتباطات امن، باید از SSL/TLS استفاده کرد که می‌توان آن را با ماژول ssl پایتون به سوکت‌ها اضافه کرد.

نتیجه‌گیری

پایتون با ماژول socket خود، یک راه ساده و قدرتمند برای ورود به دنیای برنامه‌نویسی شبکه فراهم می‌کند. با درک مفاهیم پایه سوکت‌ها، TCP و UDP، می‌توانید شروع به ساخت برنامه‌های شبکه‌ای خود کنید، از کلاینت‌ها و سرورهای ساده گرفته تا پایه‌ای برای سیستم‌های پیچیده‌تر. این مثال‌ها نقطه شروع خوبی برای کاوش بیشتر در قابلیت‌های شبکه پایتون هستند.

دیدگاهتان را بنویسید

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