ساخت چت روم با پایتون

تو آموزش این هفته ایرانی پای قراره چت رومی رو با پایتون پیاده سازی کنیم که تو کامندلاین اجرا میشه و شامل دو بخش سرور (میزبان) و کلاینت (کاربر) میشه. هر کاربری با هر دستگاهی که بتونه اسکریپت پایتون رو اجرا کنه، میتونه به سرور متصل بشه و با کاربرای دیگه چت کنه.

برای پیاده سازی برنامه مون، از معماری client-server استفاده میکنیم. این به این معنیه که ما چندتا کلاینت (کاربر) و یک سرور مرکزی داریم که همه چیز رو میزبانی می کنه و دیتا رو برای این کلاینت ها فراهم می کنه.

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


اسکریپتهایی که قراره بنویسیم رو میتونین از مخزن اصلی گیت هاب ایرانی پای بردارید.


بخش اول: ساخت سرور

ایمپورت وابستگی ها

به دوتا کتابخونه socket و threading نیاز داریم. اولی برای ساخت و ارتباط با شبکه و دومی برای انجام همزمان چندتا کار روی رشته های مختلف کاربرد داره. فایلی به اسم server.py میسازیم و دو خط پایین رو توش مینویسیم:

import socket 
import threading

ساخت سوکت

در ادامه باید اطلاعات کانکشن رو وارد کنیم؛ یک instance از socket برای اتصال به شبکه میسازیم. برای این کار به آدرس ip و port نیاز داریم. تو این آموزش ما از localhost استفاده میکنیم. اگه قراره این اسکریپت روی سرور اجرا بشه، باید ip اون سرور به جای localhost وارد بشه. پورتی که انتخاب میکنید اهمیت نداره ولی نکته ای که باید رعایت کنید اینه که پورت اشغال نباشه و جای دیگه ای در حال استفاده نباشه.

host = '127.0.0.1'
port = 55555
clients = [] 
nicknames = []

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind((host, port))
server.listen()

دوتا لیست clients و nicknames کلاینت هایی که متصل شدن و اسم هایی که موقع اتصال انتخاب کردن رو نگه میدارن.

موقع تعریف کردن socket باید دو تا پارمتر رو بهش پاس بدیم که مشخص میکنن چه نوع سوکتی میخوایم استفاده کنیم. اولی یعنی AF_INET برای اینه که مشخص کنیم میخوایم از internet socket استفاده کنیم نه unix socket. پارامتر دوم برای زمانیه که میخوایم از TCP استفاده کنیم و نه UDP.

خط بعدی به سوکتی که که ساختیم میگیم از آیپی و پورتی که مشخص کردیم برای اتصال به شبکه استفاده کنه. دقت کنید که آیپی و پورت رو به صورت تاپل به سرور دادیم.

خط بعدی سرور رو تو حالت listening میذاریم که تو این حالت سرور منتظر اتصال کلاینت ها میمونه.

ارسال پیام به تمام کلاینت ها

def broadcast(message):
    for client in clients:
        client.send(message)

به کمک تابع broadcast میتونیم یک پیام رو برای تمام کلاینت های متصل به سرور ارسال کنیم. از این تابع برای فرستادن پیام های یک کلاینت به بقیه کلاینت ها استفاده میکنیم.

هندل کردن کردن پیام ها

خب رسیدیم به بخش اصلی برنامه یعنی هندل کردن کلاینت ها و پیام ها:

def handle(client):
    while True:
        try:
            message = client.recv(1024)
            broadcast(message)
        except:
            index = clients.index(client)
            clients.remove(client)
            client.close()
            nickname = nicknames[index]
            broadcast('{} left!'.format(nickname).encode('ascii'))
            nicknames.remove(nickname)
            break

همون طور که مشخصه این تابع یک کلاینت رو به عنوان پارامتر دریافت میکنه و یک حلقه بی نهایت (infinite loop) ایجاد میکنه و تا زمانی که اروری raise نشه، متوقف نمیشه. این تابع قراره برای هر کلاینت جداگونه اجرا بشه.

وظیفه این تابع دریافت اطلاعات ارسالی از سوکت کلاینت از طریق متد ()recv هستش. عدد 1024 نوشته شده، برای مقدار دیتاییه که تو هربار ارسال دیتا از طرف کلاینت، میخوایم دریافت کنیم (1024 بایت). در نهایت اون دیتا رو به همه کاربرای متصل به سرور ارسال میکنیم.

حالا اگه خطایی پیش بیاد چه اتفاقی میوفته؟ خط به خط except رو بررسی میکنیم:

  1. اندیس کلاینت رو توی لیست clients که بالاتر تعریف کردیم به index نسبت میدیم.
  2. از لیست clients حذفش میکنیم.
  3. اتصالش به سرور رو قطع میکنیم.
  4. اسم انتخابی کلاینت رو از طریق index پیدا میکنیم.
  5. یک پیام برای همه با محتوای <<فلان کاربر از چت خارج شد>> ارسال میکنیم.
  6. اسمش رو از لیست nicknames پاک میکنیم.
  7. از حلقه while خارج میشیم.

پذیرفتن کلاینت

def receive():
    while True:
        client, address = server.accept()
        print(&quotConnected with {}&quot.format(str(address)))

        client.send('NICK'.encode('ascii'))
        nickname = client.recv(1024).decode('ascii')
        nicknames.append(nickname)
        clients.append(client)

        print(&quotNickname is {}&quot.format(nickname))
        broadcast(&quot{} joined!&quot.format(nickname).encode('ascii'))
        client.send('Connected to server!'.encode('ascii'))

        thread = threading.Thread(target=handle, args=(client,))
        thread.start()

وقتی که آماده اجرا کردن اسکریپت سرورمون هستیم، تابع receive رو اجرا میکنیم. اینجا دوباره از حلقه بی نهایت (infinite loop) استفاده میکنیم که دائما کانکشن های جدید از سمت کلاینت رو قبول میکنه و به سرور متصل میکنه.

وقتی کلاینت جدید به سرور متصل میشه، بعد از قبول کردن اتصال، اولین کاری که انجام میده ارسال رشته NICK به کلاینته که در واقع از این طریق به کلاینت میگیم اسم مستعار (nickname) خودشو به سرور بفرسته.

بعد منتظر دریافت پیام از طرف کاربر میمونه و اون پیام دریافتی رو به عنوان اسم مستعار (nickname) کاربر تازه متصل شده در نظر میگیره و اطلاعات اون کلاینت و اسم انتخابیش رو به ترتیب به لیست های clients و nicknames اضافه میکنه.

بعد پیامی رو برای همه کاربرهای متصل به سرور ارسال میکنه و میگه فلان کاربر به سرور متصل شد و بعد از اون پیامی به اون کلاینت ارسال میکنه که نشون دهنده موفقیت آمیز بودن اتصالشه. در آخر هم رشته (thread) جدیدی میسازیم که با اجرای تابع handle که بالاتر تعریف کردیم، کلاینت رو هندل کنه.

دقت کنید که ما همیشه برای دریافت و ارسال اطلاعات، دیتا رو encode میکنیم، چون ارسال و دریافت به صورت رشته (string) ممکن نیست و دیتا باید به شکل بایت (byte) باشه.

اجرای سرور

تو خط آخر فقط کافیه این تابع رو اجرا و اسکریپت رو اجرا کنیم:

receive()

بخش دوم: کلاینت

خب کار ساخت سرور تموم شد اما بدون کلاینت عملا هیچ کاربردی نداره؛ پس بریم سراغ پیاده سازی اسکریپت کلاینت.

ایمپورت پیش نیازها

فایلی به اسم client.py بسازید و کتابخونه هایی که تو اسکریپت سرور ایمپورت کردید رو، اینجا هم ایمپورت کنید:

import socket
import threading

ساخت سوکت

قدم اول برای ساخت کلاینت، انتخاب یک اسم دلخواه و ساخت سوکت کانکشن و اتصال به سروره.

برای اتصال به ip و پورت سرور نیاز داریم:

server_ip= '127.0.0.1' 
server_port = 55555

nickname = input('choose a nickname: ')
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect((server_ip, server_port))

اینجا هم دقیقا مثل بالا عمل کردیم. تنها متد جدید استفاده شده در اینجا متد ()connect هستش که برای اتصال کلاینت به سرور استفاده میشه.

دریافت دیتا از سرور

به تابعی نیاز داریم که دائما در حال گوش دادن به سرور باشه و در صورتی که سرور پیامی رو فرستاد، اونو نمایش بده:

def receive():
    while True:
        try:
            message = client.recv(1024).decode('ascii')
            if message == 'NICK':
               client.send(nickname.encode('ascii')
           else:
               print(message)
        except:
            print('An error occured')
            client.close()
            break

دوباره یک حلقه بی نهایت (infinite loop) میسازیم و پیام های ارسالی از طرف سرور رو دریافت و چاپ میکنیم. فقط اگه پیام دریافتی NICK باشه، یعنی سرور از ما درخواست یک اسم مستعار (nickname) کرده، پس به جای چاپ کردن پیام، اسم انتخابی رو ارسال میکنیم. در آخر مشخص میکنیم که اگه مشکلی به وجود اومد، بعد از چاپ پیام برای اطلاع رسانی، کانکشن رو ببند و از حلقه خارج شو.

ارسال پیام

def write():
    while True:
        message = '{}: {}'.format(nickname, input())
        client.send(message.encode('ascii'))

این تابع دائما منتظر ورودی از طرف کاربر میمونه. زمانی که چیزی وارد میکنه اونو به همراه nickname به صورت encode شده به سرور ارسال میکنه.

تو مرحله آخر، تنها کاری که باید انجام بدیم، اجرای دو رشته برای اجرای این دوتا تابع به صورت همزمانه:

receive_thread = threading.Thread(target=receive)
receive_thread.start()

write_thread = threading.Thread(target=write)
write_thread.start()

کارمون تموم شد. ?

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


امیدواریم از این پست استفاده کافی رو برده باشید. اگه شما هم مثل ما از خوندن مطالب پایتونی لذت میبرید، خوشحال میشیم ما رو تو شبکه های اجتماعی دنبال و در صورت داشتن انتقاد و پیشنهاد ما رو ازش مطلع کنید. لایک هم یادتون نره :)


حمایت از ما

با دنبال کردن ما در

یا کمک مالی به مبلغ دلخواه از طریق درگاه آیدی پی میتونید از ما حمایت کنید.

وب سایت (به زودی): iranipy.ir