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

برنامه‌نویسی سوکت در پایتون (Python Socket Programming)

برنامه‌نویسی سوکت (Socket programming) تکنیکی است که از طریق آن میان دو گره (Node) متصل به یک شبکه ارتباط برقرار می‌کنیم؛ به این صورت که گره سرور به درخواست‌های ورودی از سوی گره‌های کلاینت گوش می‌دهد.

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

این ماژول امکان دسترسی به رابط سوکت BSD را فراهم می‌کند. این رابط در تمامی سیستم‌عامل‌ها مانند لینوکس، ویندوز و مک‌او‌اس در دسترس است.

سوکت چیست؟

سوکت‌ها نقطه‌پایان‌های (Endpoints) یک کانال ارتباطی دوطرفه هستند. سوکت‌ها می‌توانند درون یک پروسه، بین پروسه‌ها روی یک ماشین یکسان، یا بین پروسه‌ها در قاره‌های مختلف ارتباط برقرار کنند.

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

connection IP_address

سوکت‌ها می‌توانند روی انواع مختلفی از کانال‌ها پیاده‌سازی شوند: سوکت‌های دامنه یونیکس (Unix domain sockets)، پروتکل TCP، پروتکل UDP و غیره. کتابخانه socket کلاس‌های مشخصی را برای مدیریت پروتکل‌های انتقال رایج و همچنین یک رابط عمومی برای مدیریت سایر پروتکل‌ها ارائه می‌دهد.

اصطلاح برنامه‌نویسی سوکت به معنای تنظیم برنامه‌نویسی‌شده سوکت‌ها برای امکان ارسال و دریافت داده است.

دو نوع پروتکل ارتباطی وجود دارد −

  • پروتکل مبتنی بر اتصال (connection-oriented protocol)

  • پروتکل بدون اتصال (connection-less protocol)

پروتکل TCP یا Transmission Control Protocol یک پروتکل مبتنی بر اتصال است. داده‌ها توسط سرور در قالب بسته (packet) ارسال می‌شوند و توسط گیرنده با همان ترتیب ارسال سرهم‌بندی می‌گردند. از آنجا که سوکت‌ها در هر دو طرف ارتباط باید پیش از شروع تنظیم شوند، این روش قابل اعتمادتر است.

پروتکل UDP یا User Datagram Protocol بدون اتصال است. این روش قابل اعتماد نیست زیرا سوکت‌ها برای انتقال داده نیازی به برقراری اتصال و فرایند پایان دادن به آن ندارند.

ماژول socket در پایتون

ماژول socket برای ایجاد و مدیریت برنامه‌نویسی سوکت برای گره‌های متصل در یک شبکه استفاده می‌شود. ماژول socket یک کلاس socket ارائه می‌دهد. شما باید با استفاده از سازنده socket.socket() یک سوکت ایجاد کنید.

یک شیء از کلاس socket نشان‌دهنده جفت نام میزبان (host name) و شماره پورت است.

ساختار دستور (Syntax)

ساختار سازنده socket.socket() به صورت زیر است –

socket.socket (socket_family, socket_type, protocol=0)

پارامترها

  • family − به طور پیش‌فرض AF_INET است. مقادیر دیگر - AF_INET6 (هشت گروه از چهار رقم شانزده‌شانزدهی)، AF_UNIX، AF_CAN (Controller Area Network) یا AF_RDS (Reliable Datagram Sockets) هستند.

  • socket_type − باید SOCK_STREAM (مقدار پیش‌فرض)، SOCK_DGRAM، SOCK_RAW یا احتمالاً یکی دیگر از ثابت‌های SOCK_ باشد.

  • protocol − شماره آن معمولاً صفر است و می‌توان از آن صرف‌نظر کرد.

نوع بازگشتی

این متد یک شیء سوکت برمی‌گرداند.

هنگامی که شیء سوکت را در اختیار داشتید، می‌توانید از متدهای مورد نیاز برای ساخت برنامه کلاینت یا سرور خود استفاده کنید.

متدهای سوکت سرور

سوکتی که روی سرور نمونه‌سازی (Instantiated) می‌شود، سوکت سرور نامیده می‌شود. متدهای زیر برای شیء سوکت در سرور در دسترس هستند −

  • متد bind() − این متد سوکت را به آدرس IP و شماره پورت مشخص‌شده متصل (Bind) می‌کند.

  • متد listen() − این متد سرور را شروع می‌کند و وارد یک حلقه گوش‌دادن می‌شود تا درخواست‌های اتصال را از کلاینت جستجو کند.

  • متد accept() − زمانی که درخواست اتصال توسط سرور دریافت می‌شود، این متد آن را می‌پذیرد و سوکت کلاینت را همراه با آدرس آن شناسایی می‌کند.

برای ایجاد یک سوکت روی سرور، از قطعه‌کد زیر استفاده کنید −

import socket
server = socket.socket()
server.bind(('localhost',12345))
server.listen()
client, addr = server.accept()
print ("connection request from: " + str(addr))

به طور پیش‌فرض، سرور به آدرس IP ماشین محلی یعنی 'localhost' متصل می‌شود و به یک شماره پورت خالی و دلخواه گوش می‌دهد.

متدهای سوکت کلاینت

سوکت مشابهی در سمت کلاینت تنظیم می‌شود. این سوکت عمدتاً درخواست اتصال را به سوکت سروری که روی آدرس IP و شماره پورت خود در حال گوش‌دادن است ارسال می‌کند.

متد connect()

این متد یک شیء تاپل دو عضوی را به عنوان آرگومان می‌پذیرد. این دو عضو، آدرس IP و شماره پورت سرور هستند.

obj=socket.socket()
obj.connect((host,port))

پس از پذیرفته شدن اتصال توسط سرور، هر دو شیء سوکت می‌توانند داده ارسال و/یا دریافت کنند.

متد send()

سرور با استفاده از آدرسی که دریافت کرده است، داده‌ها را به کلاینت ارسال می‌کند.

client.send(bytes)

سوکت کلاینت داده‌ها را به سوکتی که با آن ارتباط برقرار کرده است ارسال می‌کند.

متد sendall()

مشابه send() است. با این حال، بر خلاف send()، این متد به ارسال داده‌ها از بایت‌ها ادامه می‌دهد تا زمانی که یا تمام داده‌ها ارسال شوند یا خطایی رخ دهد. در صورت موفقیت، مقدار None بازگردانده می‌شود.

متد sendto()

این متد فقط در صورت استفاده از پروتکل UDP به کار می‌رود.

متد recv()

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

client.recv(bytes)

متد recvfrom()

این متد در صورت استفاده از پروتکل UDP به کار می‌رود.

پایتون - سرور سوکت (Socket Server)

برای نوشتن سرورهای اینترنتی، از تابع socket موجود در ماژول socket برای ایجاد یک شیء سوکت استفاده می‌کنیم. سپس از شیء سوکت برای فراخوانی توابع دیگر جهت راه‌اندازی یک سرور سوکت استفاده می‌شود.

اکنون تابع bind(hostname, port) را فراخوانی کنید تا یک پورت برای سرویس خود روی میزبان داده‌شده مشخص نمایید.

سپس، متد accept شیء بازگردانده‌شده را فراخوانی کنید. این متد منتظر می‌ماند تا کلاینتی به پورتی که مشخص کرده‌اید متصل شود و سپس شیء اتصالی را بازمی‌گرداند که نشان‌دهنده ارتباط با آن کلاینت است.

مثالی از سوکت سرور

import socket
host = "127.0.0.1"
port = 5001
server = socket.socket()
server.bind((host,port))
server.listen()
conn, addr = server.accept()
print ("Connection from: " + str(addr))
while True:
   data = conn.recv(1024).decode()
   if not data:
      break
   data = str(data).upper()
   print (" from client: " + str(data))
   data = input("type message: ")
   conn.send(data.encode())
conn.close()

پایتون - کلاینت سوکت (Socket Client)

اجازه دهید یک برنامه کلاینت بسیار ساده بنویسیم که یک اتصال به پورت مشخص‌شده 5001 و localhost داده‌شده باز می‌کند. ساخت یک کلاینت سوکت با استفاده از تابع ماژول socket پایتون بسیار ساده است.

تابع socket.connect(hosname, port) یک اتصال TCP به hostname روی پورت مشخص‌شده باز می‌کند. وقتی یک سوکت باز داشتید، می‌توانید مانند هر شیء ورودی/خروجی (IO) دیگری از آن بخوانید. پس از اتمام کار، فراموش نکنید که آن را ببندید، همان‌طور که یک فایل را می‌بندید.

مثالی از سوکت کلاینت

کد زیر یک کلاینت بسیار ساده است که به یک host و port داده‌شده متصل می‌شود، هر داده در دسترسی را از سوکت می‌خواند و سپس با وارد کردن 'q' خارج می‌شود.

import socket
host = '127.0.0.1'
port = 5001
obj = socket.socket()
obj.connect((host,port))
message = input("type message: ")
while message != 'q':
   obj.send(message.encode())
   data = obj.recv(1024).decode()
   print ('Received from server: ' + data)
   message = input("type message: ")
obj.close()
  • ابتدا کد سرور را اجرا کنید. سرور شروع به گوش‌دادن می‌کند.

  • سپس کد کلاینت را شروع کنید. این کد درخواست را ارسال می‌کند.

  • درخواست پذیرفته می‌شود. آدرس کلاینت شناسایی می‌شود.

  • متنی را تایپ کرده و Enter را فشار دهید.

  • داده دریافت‌شده چاپ می‌شود. داده‌ها را به کلاینت ارسال کنید.

  • داده‌ها از سرور دریافت می‌شوند.

  • حلقه زمانی که 'q' وارد شود پایان می‌یابد.

تعامل سرور و کلاینت در زیر نشان داده شده است −

server_client_interaction

ما ارتباط کلاینت-سرور را با ماژول socket روی ماشین محلی پیاده‌سازی کرده‌ایم. برای قرار دادن کدهای سرور و کلاینت روی دو ماشین مختلف در یک شبکه، باید آدرس IP ماشین سرور را پیدا کنیم.

در ویندوز، می‌توانید با اجرای دستور ipconfig آدرس IP را پیدا کنید. دستور ifconfig دستور معادل آن در اوبونتو است.

ipv4_address

رشته host را در هر دو کد سرور و کلاینت با مقدار IPv4 Address تغییر دهید و آن‌ها را مانند قبل اجرا کنید.

انتقال فایل در پایتون با ماژول Socket

برنامه زیر نشان می‌دهد که چگونه می‌توان از ارتباط سوکت برای انتقال یک فایل از سرور به کلاینت استفاده کرد.

کد سرور

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

import socket
host = "127.0.0.1"
port = 5001
server = socket.socket()
server.bind((host, port))
server.listen()
conn, addr = server.accept()
data = conn.recv(1024).decode()
filename='test.txt'
f = open(filename,'rb')
while True:
   l = f.read(1024)
   if not l:
      break
   conn.send(l)
   print('Sent ',repr(l))
f.close()
print('File transferred')
conn.close()

کد کلاینت

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

import socket

s = socket.socket()
host = "127.0.0.1"
port = 5001

s.connect((host, port))
s.send("Hello server!".encode())

with open('recv.txt', 'wb') as f:
   while True:
      print('receiving data...')
      data = s.recv(1024)
      if not data:
         break
      f.write(data)
      
f.close()
print('Successfully received')
s.close()
print('connection closed')

ماژول socketserver در پایتون

ماژول socketserver در کتابخانه استاندارد پایتون فریم‌ورکی برای ساده‌سازی وظیفه نوشتن سرورهای شبکه است. کلاس‌های زیر در این ماژول وجود دارند که نشان‌دهنده سرورهای همگام (Synchronous) هستند −

socketserver_module

این کلاس‌ها با کلاس‌های RequestHandler مربوطه برای پیاده‌سازی سرویس کار می‌کنند. BaseServer سوپرکلاس تمامی اشیاء Server در این ماژول است.

کلاس TCPServer از پروتکل اینترنتی TCP برای ارائه جریان‌های مداوم داده بین کلاینت و سرور استفاده می‌کند. سازنده به طور خودکار تلاش می‌کند تا server_bind() و server_activate() را فراخوانی کند. پارامترهای دیگر به کلاس پایه BaseServer پاس داده می‌شوند.

شما همچنین باید یک زیرکلاس از کلاس StreamRequestHandler ایجاد کنید. این کلاس ویژگی‌های self.rfile و self.wfile را برای خواندن یا نوشتن جهت دریافت داده‌های درخواست یا بازگرداندن داده‌ها به کلاینت ارائه می‌دهد.

  • UDPServer و DatagramRequestHandler − این کلاس‌ها برای استفاده با پروتکل UDP در نظر گرفته شده‌اند.

  • DatagramRequestHandler و UnixDatagramServer − این کلاس‌ها از سوکت‌های دامنه یونیکس استفاده می‌کنند؛ آن‌ها در پلتفرم‌های غیر یونیکسی در دسترس نیستند.

کد سرور

شما باید یک کلاس RequestHandler بنویسید. این کلاس به ازای هر اتصال به سرور یک بار نمونه‌سازی می‌شود و باید متد handle() را اورراید (Override) کند تا ارتباط با کلاینت را پیاده‌سازی نماید.

import socketserver
class MyTCPHandler(socketserver.BaseRequestHandler):
   def handle(self):
      self.data = self.request.recv(1024).strip()
      host,port=self.client_address
      print("{}:{} wrote:".format(host,port))
      print(self.data.decode())
      msg=input("enter text .. ")
      self.request.sendall(msg.encode())

روی شماره پورت اختصاص‌یافته به سرور، یک شیء از کلاس TCPServer متد forever() را فراخوانی می‌کند تا سرور را در حالت گوش‌دادن قرار دهد و درخواست‌های ورودی از کلاینت‌ها را بپذیرد.

if __name__ == "__main__":
   HOST, PORT = "localhost", 9999
   with socketserver.TCPServer((HOST, PORT), MyTCPHandler) as server:
      server.serve_forever()

کد کلاینت

هنگام کار با socketserver، کد کلاینت کم‌وبیش مشابه با برنامه کلاینت سوکت است.

import socket
import sys

HOST, PORT = "localhost", 9999

while True:
   with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
      # Connect to server and send data
      sock.connect((HOST, PORT))
      data = input("enter text .. .")
      sock.sendall(bytes(data + "\n", "utf-8"))
      
      # Receive data from the server and shut down
      received = str(sock.recv(1024), "utf-8")
      print("Sent: {}".format(data))
      print("Received: {}".format(received))

کد سرور را در یک ترمینال Command Prompt اجرا کنید. چند ترمینال دیگر برای نمونه‌های کلاینت باز کنید. می‌توانید یک ارتباط همزمان بین سرور و بیش از یک کلاینت را شبیه‌سازی کنید.

Server Client-1 Client-2

D:\socketsrvr>python myserver.py

127.0.0.1:54518 wrote:

from client-1

enter text ..

hello

127.0.0.1:54522 wrote:

how are you

enter text ..

fine

127.0.0.1:54523 wrote:

from client-2

enter text ..

hi client-2

127.0.0.1:54526 wrote:

good bye

enter text ..

bye bye

127.0.0.1:54530 wrote:

thanks

enter text ..

bye client-2

D:\socketsrvr>python myclient.py

enter text .. .

from client-1

Sent:

from client-1

Received: hello

enter text .. .

how are you

Sent:

how are you

Received: fine

enter text .. .

good bye

Sent: good bye

Received: bye bye

enter text .. .

D:\socketsrvr>python myclient.py

enter text .. .

from client-2

Sent:

from client-2

Received: hi client-2

enter text .. .

thanks

Sent: thanks

Received:

bye client-2

enter text .. .

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