מדריך: בואו נכתוב צ'ט ב Python עם Web Sockets

03/06/2022

פוסט זה כולל טיפ קצר לעבודה יעילה עם Python. אם אתם רוצים ללמוד פייתון יותר לעומק אני ממליץ על קורס Python כאן באתר.
הקורס כולל עשרות שיעורי וידאו והמון תרגול מעשי וילמד אתכם Python בצורה מקצועית מההתחלה ועד הנושאים המתקדמים.

מנגנון Web Sockets הגיע מעולם האינטרנט ומאפשר לדפדפן ולשרת ווב לתקשר בצורה דו-כיוונית. זה מנגנון מרגש כיוון שהוא מהווה את הבסיס להרבה מערכות אינטרנט שאנחנו מכירים היום, במיוחד כל המערכות בהן דברים מתעדכנים על המסך כי מישהו עשה משהו במקום אחר (לדוגמה - מישהו עושה Like בפייסבוק ופתאום אני רואה את זה על המסך שלי, בלי לטעון מחדש את העמוד).

שימוש פופולרי ב Web Sockets הוא לבניית צ'ט בין משתמשים, ואפילו כתבתי בעבר מדריך למימוש תקשורת דו כיוונית בין שרת פייתון ללקוח ריאקט. היום ארצה להראות איך לחבר את אותו שרת פייתון ללקוח פייתון, כדי לבנות צ'ט משורת הפקודה בפייתון בלבד.

1. פיתוח קוד השרת

הספריה python-socketio מספקת מימוש מאוד נוח לעבודה עם Web Sockets בפייתון. ב Web Sockets לקוח יכול לשלוח הודעה לשרת ואז מופעל קוד בצד השרת, או ששרת שולח הודעה ללקוח ואז מופעל קוד בצד הלקוח. לכן בצד השרת אני בסך הכל צריך לכתוב פונקציה שתתאים לכל הודעה שהשרת יכול לקבל, ולעטוף את זה בקצת קוד מנהלה שיפעיל את השרת.

במקרה של צ'אט מספיקה לי פונקציה אחת - אני אקרא לה message - שתופעל כשנכנסת הודעה, תדפיס אותה למסך לצרכי debug ואז תשלח אותה לכל הלקוחות המחוברים.

הקוד המלא של השרת יהיה:

from aiohttp import web
import socketio

sio = socketio.AsyncServer(cors_allowed_origins='*', aync_mode='aiohttp')
app = web.Application()

sio.attach(app)

@sio.on('message')
async def message(sid, message):
    print("Socket ID: " , sid)
    print(message)
    await sio.emit('message', message, broadcast=True);

if __name__ == '__main__':
    web.run_app(app, port=4000)

ובשביל להפעיל אותו נצטרך להתקין את הספריות socketio ו aiohttp:

$ pip install python-socketio aiohttp

2. פיתוח צד לקוח

הקוד בצד הלקוח רץ בלולאה, קורא שורה מהמשתמש ושולח אותה לשרת. בדרך הוא יכול לקבל הודעות מהשרת (שלקוחות אחרים שלחו). בגלל העבודה עם שורת הפקודה יותר נוח לי לעבוד בממשק Threads מאשר בממשק asyncio, ולכן אני מתקין את החבילה מבוססת ה Threads עם הפקודה:

$ pip install "python-socketio[client]"

בחבילה זו פונקציה שמטפלת באירוע לא צריכה להיות async, והיא אוטומטית "תתפרץ" לתוכנית כשאירוע יקרה. האירוע היחיד שיכול לקרות הוא קבלת הודעה ולכן הקוד המלא ללקוח נראה כך:

import asyncio
import socketio

sio = socketio.Client()

@sio.event
def message(data):
    print('I received a message!')
    print(data)

sio.connect('http://localhost:4000')
print('my sid is', sio.sid)
while True:
    line = input(":> ")
    if len(line) > 0:
        sio.emit('message', {'text': line})

3. הפעלה ושיחה

אני שמרתי את השרת בקובץ בשם server.py ואת הלקוח בקובץ בשם client.py, ולכן מפעיל בחלון אחד שרת ובעוד שני חלונות שני לקוחות שונים:

# first window
$ python server.py

# second window
$ python client.py

# third window
$ python client.py

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