وبلاگ فربد | Farbod Blog

توی این وبلاگ راجع به پروژه هام صحبت می کنم

ساخت چت روم ساده در پایتون

  • ۲۴۵۵

سلام دوستان :)

توی پست امروز قراره با هم یک چت روم ساده با پایتون بسازیم.

 

مکانیزم این چت روم به این صورته که یک سرور داریم که برنامه سرور رو اجرا میکنه و چندین کلاینت که برنامه کلاینت رو اجرا میکنن و از طریق سرور با همدیگه ارتباط برقرار میکنن. اگر یادتون باشه توی پست (آموزش پردازش موازی در پایتون) راجع به multiproccessing حرف زدیم و همچنین راجع به multithreading توضیح دادیم، توی این پست قراره از multithreading هم استفاده کنیم.

 

خب بریم سراغ نوشتن اسکریپت.

برای انجام دادن این کار نیاز به کتابخونه های socket و threading داریم که هر دوتاشون بطور پیشفرض روی پایتون نصب هستن. همچنین برای رنگی کردن متن به کتابخونه colorama نیاز داریم.

برای نصب colorama توی ویندوز برین توی cmd و کد زیر رو بزنین:

pip install colorama

برای نصب colorama توی لینوکس هم برین توی ترمینال و کد زیر رو بزنین:

sudo pip3 install colorama

 

اول از همه بریم سراغ اسکریپت کلاینت.

client.py:

#client.py
import socket
import random
from threading import Thread
from datetime import datetime
from colorama import Fore, init, Back

# init colors
init()

# set the available colors
colors = [Fore.BLUE, Fore.CYAN, Fore.GREEN, Fore.LIGHTBLACK_EX, 
    Fore.LIGHTBLUE_EX, Fore.LIGHTCYAN_EX, Fore.LIGHTGREEN_EX, 
    Fore.LIGHTMAGENTA_EX, Fore.LIGHTRED_EX, Fore.LIGHTWHITE_EX, 
    Fore.LIGHTYELLOW_EX, Fore.MAGENTA, Fore.RED, Fore.WHITE, Fore.YELLOW
]

# choose a random color for the client
client_color = random.choice(colors)

# server's IP address
# if the server is not on this machine, 
# put the private (network) IP address (e.g 192.168.1.2)
SERVER_HOST = "127.0.0.1"
SERVER_PORT = 5002 # server's port
separator_token = "<SEP>" # we will use this to separate the client name & message

# initialize TCP socket
s = socket.socket()
print(f"[*] Connecting to {SERVER_HOST}:{SERVER_PORT}...")
# connect to the server
s.connect((SERVER_HOST, SERVER_PORT))
print("[+] Connected.")
# prompt the client for a name
name = input("Enter your name: ")

def listen_for_messages():
    while True:
        message = s.recv(1024).decode()
        print("\n" + message)

# make a thread that listens for messages to this client & print them
t = Thread(target=listen_for_messages)
# make the thread daemon so it ends whenever the main thread ends
t.daemon = True
# start the thread
t.start()

while True:
    # input message we want to send to the server
    to_send =  input()
    # a way to exit the program
    if to_send.lower() == 'q':
        break
    # add the datetime, name & the color of the sender
    date_now = datetime.now().strftime('%Y-%m-%d %H:%M:%S') 
    to_send = f"{client_color}[{date_now}] {name}{separator_token}{to_send}{Fore.RESET}"
    # finally, send the message
    s.send(to_send.encode())

# close the socket
s.close()

توضیح: اول از همه ماژول های مورد نیازمون رو import کردیم. بعد با استفاده از متد init در کتابخانه colorama رنگ ها رو init کردیم. بعدش یک لیست درست کردیم و یک سری رنگ رو داخلش تعریف کردیم.بعد با استفاه از از تابع choice داخل ماژول random یکی از اون رنگ هایی که داخل لیستمون تعریف کرده بودیم به صورت شانسی انتخاب کردیم.

بعدش IP و Port سرور که قراره بهش متصل بشیم رو تعریف کردیم. من اینجا چون سرور قراره کامپیوتر خودم باشه آی پی localhost رو گذاشتم اگر میخواین از کامپیوتر دیگه ای به عنوان سرور استفاده کنین آی پی اون کامپیوتر رو بزارین. بعد یک توکن برای جدا کردن نام کلاینت و پیامش درست کردیم. توی خط بعدی یک سوکت از نوع TCP ساختیم و بعد با استفاده از تابع connect به سرور متصل شدیم. بعد نام کلاینت رو از کاربر گرفتیم و داخل متغییر name ریختیم که بعدا ازش استفاده کنیم. حالا یک تابع به نام listen_for_messages ساختیم که داخل یک حلقه بینهایت میاد با استفاده از متد recv پیام ها رو دریافت میکنه و چاپ میکنه.

حالا با استفاده از تابع Thread یک thread از تابع listen_for_messages ساختیم و توی خط پایین مقدار deamon هم برابر True قرار دادیم. حالا این یعنی چی؟

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

توی خط بعدی با استفاده از متد thread ،start مون رو اجرا کردیم.

بعد یک حلقه بینهایت ایجاد کردیم که اول از همه پیام رو از کاربر دریافت میکنه و داخل متغییر to_send میریزه. بعد توی یک if چک میکنیم که اگر چیزی که کاربر وارد کرده بود برابر q بود با استفاده از دستور break از حلقه خارج میشیم و در نهایت سوکت رو می بندیم ولی اگر چیزی که کاربر وارد کرده بود برابر q نبود میاد با استفاده از ماژول datetime زمان الان رو با یک سری فرمت دهی های خاص میریزه داخل متغییر date_now. بعد هم متغییر to_send که همون نام کاربر بود رو برابر چیزی که دارین میبینین میده. بعد هم در نهایت پیام رو به سمت سرور ارسال میکنه.

 

خب حالا میتونیم بریم سراغ سورس سرور.

 

server.py:

#server.py
import socket
from threading import Thread

# server's IP address
SERVER_HOST = "0.0.0.0"
SERVER_PORT = 5002 # port we want to use
separator_token = "<SEP>" # we will use this to separate the client name & message

# initialize list/set of all connected client's sockets
client_sockets = set()
# create a TCP socket
s = socket.socket()
# make the port as reusable port
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# bind the socket to the address we specified
s.bind((SERVER_HOST, SERVER_PORT))
# listen for upcoming connections
s.listen(5)
print(f"[*] Listening as {SERVER_HOST}:{SERVER_PORT}")

def listen_for_client(cs):
    """
    This function keep listening for a message from `cs` socket
    Whenever a message is received, broadcast it to all other connected clients
    """
    while True:
        try:
            # keep listening for a message from `cs` socket
            msg = cs.recv(1024).decode()
        except Exception as e:
            # client no longer connected
            # remove it from the set
            print(f"[!] Error: {e}")
            client_sockets.remove(cs)
        else:
            # if we received a message, replace the <SEP> 
            # token with ": " for nice printing
            msg = msg.replace(separator_token, ": ")
        # iterate over all connected sockets
        for client_socket in client_sockets:
            # and send the message
            client_socket.send(msg.encode())


while True:
    # we keep listening for new connections all the time
    client_socket, client_address = s.accept()
    print(f"[+] {client_address} connected.")
    # add the new connected client to connected sockets
    client_sockets.add(client_socket)
    # start a new thread that listens for each client's messages
    t = Thread(target=listen_for_client, args=(client_socket,))
    # make the thread daemon so it ends whenever the main thread ends
    t.daemon = True
    # start the thread
    t.start()

# close client sockets
for cs in client_sockets:
    cs.close()
# close server socket
s.close()

توضیح: اول ماژول های مورد نیازمون رو import کردیم. بعد توی دو خط بعدی دو متغییر SERVER_HOST و SERVER_PORT رو تعریف کردیم (آی پی 0.0.0.0 به معنای تمام interface های یک هاست هست). حالا مثل سورس قبلی یک توکن برای جدا کردن نام و پیام کاربر درست کردیم.

حالا یک set درست کردیم که بعدا کلاینت هایی که بهمون متصل شدن رو داخلش ذخیره کنیم. بعد یک سوکت از نوع TCP ساختیم.بعد هم با استفاده از متد IP، bind و Port مون رو بایند کردیم یعنی رزرو کردیم. خب توی خط بعدی با استفاده از متد listen منتظر کانکشن هایی که از سمت کلاینت ها میاد موندیم. توی تابع listen_for_clients گفتیم که پیام رو از کلاینت بگیره و برای تمام کلاینت ها ارسال کنه. 

بعد توی یک حلقه بی نهایت متغییر های client_socket و client_address رو درست کردیم که هر کدوم از اینا شئ سوکتمون هستن. بعد هم کلاینتی که وصل شده بود رو به اون set که قبلا تعریف کرده بودیم اضافه میکنیم.

بعد یک thread از تابع listen_for_client می سازیم و به عنوان ورودی شئ سوکتمون رو به تابع مون میدیم. بعد thread رو تبدیل به یک deamon thread میکنیم که توی بالا توضیحش دادم. حالا اگر به هر دلیلی از حلقه خارج شد توی یک for میاد ارتباطش رو با تمام کلاینت هایی که متصل بودن قطع میکنه و در نهایت سوکت اصلی رو هم می بنده.

 

خب بریم سراغ تست کردن برنامه.

همینطور که توی تصویر زیر می بینید من رفتم توی terminal و فایل server رو اجرا کردم. همچنین سه تا کلاینت هم متصل کردم حالا خروجی رو ببینید:

 

امیدوارم براتون مفید بوده باشه D:


  • با سلام و خسته نباشید من با pip کتابخانه ی colorama را نصب کردم ولی وقتی پایتون را اجرا می کنم می نویسه
    Traceback (most recent call last):
    File "C:\Users\hp\Desktop\chat room\client.py", line 4, in
    import colorama
    ModuleNotFoundError: No module named 'colorama'
    و یک سوال دیگه
    وقی من فایل سرور را اجرا می کم
    می نویسه
    [*] Listening as 0.0.0.0:5002
    و هیچ اتفاقی نمی افتد
    چکار کنم
    پاسخ:
    سلام دوست عزیز.
    برای مشکل کتابخونه باید مطمعن بشید که کتابخونه رو با استفاده از همون pip که روی ورژن پایتونی که برای اجرای فایل استفاده کردید، نصب کردید. یعنی اگر شما دو تا ورژن پایتون نصب دارید مطمعن بشید که pip و پایتونی که ازش استفاده کردید یک ورژن هستن. برای اینکار میتونین وارد cmd بشین و به جای دستوری که گفته بودم این دستور رو اجرا کنید:
    python -m pip install colorama
    بعد از مشاهده ورژن پایتون در حین نصب، وارد IDE که استفاده میکنید بشید و مطمعن بشید که ورژن پایتون تنظیم شده داخل IDE(یا ورژن پایتونی که فایل رو داخل cmd باهاش اجرا میکنید)، با ورژن پایتونی که cmd رو روی اون نصب کردید میخونه. اگر فقط یک ورژن پایتون دارید احتمالا کتابخونه درست نصب نمیشه و یا pip مشکلی داره. میتونید تمام مراحلی رو که انجام میدید برای من داخل ایمیل تماس با من(farbodmblog@gmail.com) ارسال کنید تا بتونم بیشتر راهنمایی کنم.

    در مورد اجرا فایل سرور، فایل هیچ مشکلی نداره و پیامی که میده نشون میده که فایل سرور به درستی اجرا شده. شما برای اینکه از برنامه استفاده کنید باید فایل کلاینت رو برای هر کلاینتی که میخواید ران کنید و به سرور متصل بشید. بعدش هر پیامی که از طرف کلاینت وارد کنید برای تمام کاربران قابل مشاهده هست. توجه کنید که شما نمیتونید از طرف سرور پیامی ارسال کنید و سرور فقط یک واسط بین کاربران هست.
    موفق باشید.
  • سلام عرض ادب چطوری از یوزرهای فعال لیست بگیریم؟
    پاسخ:
    سلام دوست عزیز.
    لیست یوزر های فعال در متغییر client_sockets که در اول برنامه تعریف شده است قرار دارد و شما میتوانید هر جای برنامه که مورد نیازتون هست از این متغییر استفاده کنید.
    موفق باشید.
  • عالیه واقعا
    پاسخ:
    سلام دوست عزیز
    ممنون از همراهی شما
    شاد و موفق باشید
  • سلام
    چجوری میتونم دوتا کامپیوتر رو به هم وصل کنم ؟؟
    پاسخ:
    سلام دوست عزیز
    برای اینکار باید از کتابخونه socket در پایتون استفاده کنین.
    برای متصل کردن دو کامپیوتر به هم باید یک کامپیوتر رو به عنوان host و یکی رو به عنوان client در نظر بگیرین.
    الان وارد سورس کلاینت بشین و با استفاده از دستور زیر یک شئ سوکت درست کنین:
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    این یک شئ سوکت درست میکنه که از IPV4 و پروتکل TCP استفاده می کنه.(برای استفاده از IPV6 از AF_INET6 استفاده کنین و برای استفاده از پروتکل UDP از SOCK_DGRAM استفاده کنین)
    حالا با استفاده از متد connect باید به server متصل بشین.
    s.connect(IP, PORT)
    بجای IP باید IP محلی server و بجای PORT باید پورتی که میخواین روش اتصال ایجاد کنین بذارین.
    بعد از وصل شدن میتونین با استفاده از متد های send و recv اطلاعات خودتون رو برای server ایجاد کنین.
    حالا برای سورس سرور شما باید اول ip و port مورد نظر خودتون رو bind کنین.
    s.bind(IP, PORT)
    بجای IP باید IP سرور رو وارد کنین و بحای PORT باید پورتی که میخواین روش ارتباط بر قرار کنین رو وارد کنین(دقت کنین که PORT که داخل سورس کلاینت وارد می کنین باید با PORT که داخل سورس server وارد می کنین یکی باشه).
    حالا باید با استفاده از متد listen منتظر یک اتصال بمونین.
    s.listen(1)
    حالا بعد از یک درخواست برای اتصال از طرف client باید درخواست اتصال اون رو accept کنیم.
    پس با استفاده از متد accept این کار رو انجام میدیم.
    ()c, addr = s.accept
    حالا همه چی آمادست :)) شما میتونین از یک text ساده گرفته تا فایل های سنگین رو انتقال بدین که آموزش همش داخل وبلاگ قرار داره.
    فقط توجه کنین که برای انتقال داده از طرف سرور باید از (data)c.send و دریافت داده از طرف سرور از c.recv(1024) استفاده کنین. و برای کلاینت هم باید از s.send(data) و s.recv(1024) استفاده کنین.
    موفق باشید
  • خیلی خوب بود
    از اینکه اینقدر کامل و خط به خط توضیح میدهید ممنون
  • درود
    سپاس از آموزشهای فوق العادتون🙏
    پاسخ:
    سلام دوست عزیز
    ممنون از همراهی شما
    موفق باشید
ارسال نظر آزاد است، اما اگر قبلا در بیان ثبت نام کرده اید می توانید ابتدا وارد شوید.
شما میتوانید از این تگهای html استفاده کنید:
<b> یا <strong>، <em> یا <i>، <u>، <strike> یا <s>، <sup>، <sub>، <blockquote>، <code>، <pre>، <hr>، <br>، <p>، <a href="" title="">، <span style="">، <div align="">
تجدید کد امنیتی