לדלג לתוכן

2.1 - סוקטים - הרצאה

סוקטים (פיתוח עם פייתון)

  • ארבעת השכבות שלמדנו: השכבה הפיזית, שכבת הקו, שכבת הרשת ושכבת התעבורה נותנות לנו את הבסיס ליצור חיבורים רשתיים בין מחשבים בצורה אמינה ויעילה. השכבות האלה מספקות את התשתית שמאפשרת לתקשר בין מחשבים, וגם בתוך המחשב עצמו.
  • כדי לתקשר עם השכבות האלו ולהעביר מידע, אנחנו משתמשים בinterface שנקרא סוקטים. סוקט הוא interface שמספק למתכנתים כלים פשוטים לשלוח ולקבל פקטות בין תוכנות על גבי TCP או UDP, גם בין מחשבים וגם בתוך המחשב המקומי.
  • גם בשפות תכנות אחרות יש ספריות לסוקטים, אבל פייתון מספקת interface מאוד נוח שמאפשר יצירה וניהול של סוקטים בקלות.

גישה בסיסית

לרוב, כל מערכת תקשורת רשתית בנויה משני חלקים עיקריים:

  1. שרת - התוכנה שמקבלת חיבורים ממחשבים אחרים ומספקת שירותים.
  2. לקוח - התוכנה שבקשה את השירות מהשרת.

הפונקציות הבסיסיות בסוקטים

1. יצירת סוקט

פונקציה socket() משמשת כדי ליצור סוקט חדש. זהו אובייקט שמייצג את החיבור בין התוכנה שלנו לרשת.

import socket

# Create a new TCP socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  • socket.AF_INET - מציין שהסוקט ישתמש ב-IPv4 (כתובת IP).
  • socket.SOCK_STREAM - מציין שסוקט זה יהיה TCP (לתקשורת אמינה עם חיבור).

2. חיבור לשרת (לקוח)

פונקציה connect() משמשת כדי לחבר את הלקוח לשרת.

import socket

# Create a new socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# Connect to the server's IP address and port
s.connect(('127.0.0.1', 65432))  # Connect to the local address (localhost) and port 65432
  • connect() מחברת את הלקוח לשרת על פי כתובת IP ופורט.

3. שליחת מידע לשרת

פונקציה sendall() משמשת כדי לשלוח מידע דרך הסוקט לשרת.

import socket

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('127.0.0.1', 65432))

# Send data to the server
s.sendall(b'Hello, Server!')
  • sendall() שולחת את המידע שנמצא במחרוזת (bytes). צריך לוודא שהמידע מומר ל-bytes אם הוא לא כבר כזה.

4. קבלת מידע מהשרת

פונקציה recv() משמשת לקבלת מידע מהשרת. אנחנו מציינים את גודל המידע שאנחנו מצפים לקבל.

import socket

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('127.0.0.1', 65432))

# Receive data from the server (the requested amount of data is 1024 bytes)
data = s.recv(1024)
print('Received:', data.decode())  # Print the received data
  • recv(1024) - מציין שאנחנו מצפים לקבל עד 1024 בייטים מהשרת.

5. קביעת כתובת והאזנה לחיבורים (שרת)

בשרת, אנחנו צריכים לקבוע על איזה כתובת ופורט הסוקט יאזין לחיבורים. פונקציות bind(), listen(), ו-accept() עוזרות בזה.

import socket

# Create a new socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# Set the address and port the server will listen on
s.bind(('127.0.0.1', 65432))

# Listen for connections
s.listen()

# Wait for a client to connect
conn, addr = s.accept()
print(f"Connected by {addr}")

# Receive data from the client
data = conn.recv(1024)
print('Received:', data.decode())
  • bind() - קובעת את כתובת ה-IP והפורט שעליהם הסוקט יאזין.
  • listen() - מכניסה את הסוקט למצב של האזנה לחיבורים.
  • accept() - מחכה לחיבור מהלקוח ומחזירה אובייקט חדש המייצג את החיבור עם הלקוח.

6. שליחת מידע ללקוח (שרת)

לאחר שהשרת קיבל חיבור מהלקוח, הוא יכול לשלוח לו מידע בעזרת sendall().

conn.sendall(b'Hello, Client!')

דוגמה לשרת פשוט ב-TCP:

import socket

def start_server():
    # Create a new socket and bind it to an address and port
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.bind(('127.0.0.1', 65432))

    # Listen for connections
    s.listen()

    print("Server listening for connections...")
    conn, addr = s.accept()  # Wait for a connection from a client
    print(f"Connected to {addr}")

    # Receive data from the client
    data = conn.recv(1024)
    print(f"Received from client: {data.decode()}")

    # Send a response to the client
    conn.sendall(b'Hello from server!')

    # Close the connection
    conn.close()

if __name__ == "__main__":
    start_server()

דוגמה ללקוח פשוט ב-TCP:

import socket

def start_client():
    # Create a new socket and connect to the server
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect(('127.0.0.1', 65432))

    # Send data to the server
    s.sendall(b'Hello from client!')

    # Receive the response from the server
    data = s.recv(1024)
    print(f"Received from server: {data.decode()}")

    # Close the connection
    s.close()

if __name__ == "__main__":
    start_client()

סיכום הפונקציות:

  • socket() - יצירת סוקט חדש.
  • connect() - חיבור לשרת.
  • sendall() - שליחת מידע.
  • recv() - קבלת מידע.
  • bind() - קביעת כתובת ופורט לשרת.
  • listen() - האזנה לחיבורים.
  • accept() - קבלת חיבור מלקוח.

הקוד הזה מראה איך לבנות שרת ולקוח ב-TCP שמתקשרים ביניהם, מתחילים חיבור, שולחים ומקבלים מידע.

עוד דוגמה לקוד עם socket-ים

הנה דוגמה לקוד פייתון שמיישם שרת ולקוח באמצעות TCP. השרת מקבל מלקוח את המחרוזת "ping request" ומחזיר לו "ping reply".

קוד השרת (TCP):

import socket

def start_server(host='127.0.0.1', port=65432):
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server_socket:
        server_socket.bind((host, port))
        server_socket.listen()
        print(f"Server listening on {host}:{port}")

        conn, addr = server_socket.accept()  # Accept a connection from a client
        with conn:
            print(f"Connected by {addr}")
            while True:
                data = conn.recv(1024)  # Receive data from the client
                if not data:
                    break
                if data.decode() == 'ping request':
                    conn.sendall(b'ping reply')  # Send a response to the client
                else:
                    conn.sendall(b'unknown command')  # Response if an unrecognized command was received

if __name__ == "__main__":
    start_server()

קוד הלקוח (TCP):

import socket

def send_ping_request(host='127.0.0.1', port=65432):
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client_socket:
        client_socket.connect((host, port))  # Connect to the server
        client_socket.sendall(b'ping request')  # Send the request to the server
        data = client_socket.recv(1024)  # Receive the response from the server
        print(f"Received: {data.decode()}")  # Print the received response

if __name__ == "__main__":
    send_ping_request()

הרצת השרת והלקוח

  • אם נריץ את הקוד של השרת, הוא יתחיל להאזין לחיבורים בכתובת 127.0.0.1 ובפורט 65432.
  • לאחר מכן נריץ את הקוד של הלקוח. הלקוח ישלח את המחרוזת "ping request" לשרת, והשרת יחזיר את התשובה "ping reply".
  • ניתן גם לבדוק את התקשורת באמצעות הפקודה nc 127.0.0.1 65432, שמתחברת לשרת ומאפשרת לשלוח פקודות ידניות.

הפונקציות העיקריות בסוקטים

פונקציות בשרת TCP:

  1. socket() - יוצרת סוקט חדש.
  2. bind() - קובעת את הכתובת והפורט שעליהם הסוקט יאזין.
  3. listen() - מכניסה את הסוקט למצב האזנה, מחכה לחיבורים.
  4. accept() - מחכה לחיבור מלקוח ומחזירה סוקט חדש שמייצג את החיבור עם הלקוח.
  5. recv() - מקבלת מידע מהלקוח. הפונקציה מקבלת את גודל המידע הצפוי לחזור (בייטים).
  6. sendall() - שולחת מידע ללקוח.

פונקציות בלקוח TCP:

  1. socket() - יוצרת סוקט חדש.
  2. connect() - מתחברת לשרת לפי כתובת IP ופורט.
  3. sendall() - שולחת מידע לשרת.
  4. recv() - מקבלת מידע מהשרת.

סוקטים UDP

בנוסף ל-TCP, יש גם סוקטים מסוג UDP (User Datagram Protocol). UDP פחות אמין מ-TCP, אך הוא מתאים לאפליקציות שבהן הזמן חשוב יותר מהדיוק (כמו סטרימינג או משחקים מרובי משתתפים). הסוקטים של UDP לא דורשים חיבור קודם ולכן נחשבים לקלים יותר מבחינת ביצועים.

כדי להשתמש בסוקטי UDP בפייתון, אנחנו משתמשים ב-socket.SOCK_DGRAM במקום ב-socket.SOCK_STREAM. ההבדל העיקרי הוא שלא צריך להקים חיבור, אלא פשוט שולחים ומקבלים פקטות ישירות.

מדריך נוסף

אם אתם רוצים להעמיק ולהבין עוד על סוקטים, הנה מדריך נהדר לשימוש בסוקטים בפייתון: RealPython Sockets Guide.

הכוח של סוקטים

סוקטים נותנים לנו את האפשרות לפתח כל סוג של אפליקציה רשתית, כולל:

  • תוכנות להעברת קבצים.
  • תוכנות לתקשורת בזמן אמת (כמו צ'אט).
  • שירותים מקוונים (כמו אתרי אינטרנט, שירותי API).
  • משחקים ברשת.

היכולת לשלוח ולקבל פקטות על גבי TCP או UDP מאפשרת לפתח כל תוכנה שדורשת תקשורת בין מחשבים בצורה גמישה ויעילה.

בסופו של דבר, כל התוכנות הרשתיות שאנחנו מכירים, כמו אתרי אינטרנט, אפליקציות צ'אט, וכל שירות מקוון, פותחו בעזרת סוקטים!