-
سه شنبه, ۱۸ خرداد ۱۴۰۰، ۰۶:۰۰ ب.ظ
-
۲۴۵۵
سلام دوستان :)
توی پست امروز قراره با هم یک چت روم ساده با پایتون بسازیم.
مکانیزم این چت روم به این صورته که یک سرور داریم که برنامه سرور رو اجرا میکنه و چندین کلاینت که برنامه کلاینت رو اجرا میکنن و از طریق سرور با همدیگه ارتباط برقرار میکنن. اگر یادتون باشه توی پست (آموزش پردازش موازی در پایتون) راجع به 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: