לדלג לתוכן

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

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

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

גישה בסיסית

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

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

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

1. יצירת סוקט

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

import socket

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

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

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

import socket

# יצירת סוקט חדש
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# חיבור לכתובת IP ופורט של השרת
s.connect(('127.0.0.1', 65432))  # מחברים לכתובת המקומית (localhost) ולפורט 65432
  • connect() מחברת את הלקוח לשרת על פי כתובת IP ופורט.

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

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

import socket

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

# שולחים מידע לשרת
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))

# קבלת מידע מהשרת (כמות המידע שבקשה היא 1024 בייטים)
data = s.recv(1024)
print('Received:', data.decode())  # מדפיס את המידע המתקבל
  • recv(1024) - מציין שאנחנו מצפים לקבל עד 1024 בייטים מהשרת.

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

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

import socket

# יצירת סוקט חדש
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# קביעת הכתובת והפורט שעליהם השרת יאזין
s.bind(('127.0.0.1', 65432))

# מאזינים לחיבורים
s.listen()

# מחכים ללקוח להתחבר
conn, addr = s.accept()
print(f"Connected by {addr}")

# קבלת מידע מהלקוח
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():
    # יצירת סוקט חדש וקשירתו לכתובת ופורט
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.bind(('127.0.0.1', 65432))

    # מאזינים לחיבורים
    s.listen()

    print("Server listening for connections...")
    conn, addr = s.accept()  # מחכים לחיבור מלקוח
    print(f"Connected to {addr}")

    # קבלת מידע מהלקוח
    data = conn.recv(1024)
    print(f"Received from client: {data.decode()}")

    # שליחת תשובה ללקוח
    conn.sendall(b'Hello from server!')

    # סוגרים את החיבור
    conn.close()

if __name__ == "__main__":
    start_server()

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

import socket

def start_client():
    # יצירת סוקט חדש ותחיבור לשרת
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect(('127.0.0.1', 65432))

    # שליחת מידע לשרת
    s.sendall(b'Hello from client!')

    # קבלת תשובה מהשרת
    data = s.recv(1024)
    print(f"Received from server: {data.decode()}")

    # סוגרים את החיבור
    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()  # מקבל חיבור מלקוח
        with conn:
            print(f"Connected by {addr}")
            while True:
                data = conn.recv(1024)  # מקבל נתונים מהלקוח
                if not data:
                    break
                if data.decode() == 'ping request':
                    conn.sendall(b'ping reply')  # שולח תשובה ללקוח
                else:
                    conn.sendall(b'unknown command')  # תשובה אם לא קיבל פקודה מוכרת

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))  # מתחבר לשרת
        client_socket.sendall(b'ping request')  # שולח את הבקשה לשרת
        data = client_socket.recv(1024)  # מקבל את התשובה מהשרת
        print(f"Received: {data.decode()}")  # מדפיס את התשובה שהתקבלה

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 מאפשרת לפתח כל תוכנה שדורשת תקשורת בין מחשבים בצורה גמישה ויעילה.

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