異步Websocket構建聊天室

目錄

Websocket技術背景

Websockec簡介

實現websocket通信程序

實驗環境:

服務端(阿里云ESC,VPC網絡):

客戶端1(本機):

通信模型:

實現功能邏輯:

源代碼:

服務端源代碼:

客戶端源代碼:

實驗效果:

總結

Websocket技術背景

在websocket協議誕生之前,應用層中用于web通信的協議基本由HTTP主導,毫無疑問,HTTP就是構建現代Web的基石,但是http存在比較致命的性能問題——它是一種請求-響應模型,這意味著他是一種短連接的通信協議,在get多個web資源時需要依靠多次請求響應來輪詢,這種通信導致了一種極其低效的通信問題,在實時通信時尤為明顯,因此websocket,基于socket優秀的長連接特性得以被擴展到應用層并得到廣泛使用


Websockec簡介

WebSocket 是一種基于TCP協議之上的應用層協議,2011年已被IETF的RFC文檔標準化,相比socket與socketserver,他天然支持全雙工通信、異步通信,通信效率極高,且由于是應用層標準協議,因此大部分瀏覽器能夠原生支持websocket協議,對瀏覽器兼容性極強
?

實現websocket通信程序

實驗環境:

服務端(阿里云ESC,VPC網絡):

操作系統:Ubuntu22.04

私網IP:172.29.42.239?

公網IP:47.111.23.151

編程語言:Python

數據庫:Mysql

客戶端1(本機):

操作系統:Windows11

編程語言:Python

通信模型:

C/S,由服務端處理具體的業務邏輯并轉發客戶端之間的消息

純異步通信邏輯

實現功能邏輯:

完整的注冊登錄邏輯

歡迎邏輯,分為發送給新用戶自己的單播歡迎邏輯和通告他人新用戶登錄的歡迎邏輯

顯示與隱藏IP歸屬地,缺省隱藏

shell命令調用(非交互式,交互式有點危險)

心跳報文邏輯,5秒hello,15秒dead

獲取在線用戶邏輯

獲取系統級信息邏輯,如主機名、操作系統、分區信息以及每個分區的具體情況、內存信息以及內存占用詳情、CPU信息、當前進程信息等

幫助邏輯,通過發送/help命令獲取到用戶可使用的全部命令

遞歸目錄樹邏輯

每條消息顯示當前時間邏輯

PS:以上,shell命令調用、獲取系統級信息這些邏輯的命令,不能對自己使用,只能對他人,另一個客戶端

源代碼:

服務端源代碼:

import datetime
from websockets.asyncio.server import ServerConnection,serve
import asyncio
from aioconsole import aprint,ainput
import json
from ip2region.binding.python import xdbSearcher
from datetime import datetime
import pymysql
from textwrap import dedentclient_list={}
client_heartbeat={}
location = False#數據庫查詢
async def db_select(user):db_conn = pymysql.connect(user='root',password='root',host='127.0.0.1',database='user_info')cur = db_conn.cursor()sql = ' select passwd from user_pwd where username = %s 'cur.execute(sql,user)pwd = cur.fetchall()db_conn.close()return pwd#數據庫更新
async def db_update(user,pwd):db_conn = pymysql.connect(user='root', password='root', host='127.0.0.1', database='user_info')cur = db_conn.cursor()sql = 'update user_pwd set passwd = %s where username = %s ;'cur.execute(sql, (pwd, user))db_conn.commit()db_conn.close()#數據庫插入
async def db_insert (user,pwd):db_conn = pymysql.connect(user='root', password='root', host='127.0.0.1', database='user_info')cur = db_conn.cursor()sql = 'insert into user_pwd(username,passwd) values(%s,%s)'cur.execute(sql, (user,pwd))db_conn.commit()db_conn.close()#注冊邏輯
async def  register(websocket_conn,region,city,name_pwd):try:parts = name_pwd.split(' ', 1)username = parts[0]passwd = parts[1]print (parts)pwd = await db_select(username)if pwd:await websocket_conn.send('親愛的用戶,您的賬號已注冊,無需再次重復注冊,直接登錄即可')await broadcast_welcome(websocket_conn, region, city, choose='登錄')if not pwd:await db_insert(username, passwd)client_list[username] = websocket_conntry:for client_name, ws_conn in client_list.items():print ('進來了3')if username == client_name:print ('進來了4')await ws_conn.send(f'歡迎你,【{username}】,來自{region}{city}的朋友,歡迎你加入我們的戰略會議室!\n輸入help獲取幫助')else:print('進來了5')await ws_conn.send(f'歡迎來自{region}{city}的新同伴【{username}】加入了我們的戰略會議室!\n輸入help獲取幫助')except Exception as e:print (f'注冊邏輯中的歡迎消息邏輯捕獲到異常報錯:{e}')except Exception as e:print (f'注冊邏輯捕獲到異常報錯:{e}')#登錄邏輯
async def login(websocket_conn,region,city,name_pwd):try:parts = name_pwd.split(' ', 1)name = parts[0]passwd = parts[1]pwd = await db_select(name)try:if not pwd:await websocket_conn.send('親愛的用戶,您的賬號尚未注冊,請注冊后再加入我們的會議。')await broadcast_welcome(websocket_conn,region,city,choose='注冊')if pwd:pwd = pwd[0][0]if passwd != pwd:await websocket_conn.send('不好意思,親愛的用戶,您的密碼輸入錯誤,請重新嘗試。')await broadcast_welcome(websocket_conn, region, city,choose='登錄')if str(passwd) == str(pwd):await websocket_conn.send(f'登錄成功!')client_list[name] = websocket_connfor client_name, ws_conn in client_list.items():if name == client_name:await ws_conn.send(f'歡迎你,【{name}】,來自{region}{city}的朋友,歡迎你加入我們的戰略會議室!\n輸入help獲取幫助')if name != client_name:await ws_conn.send(f'歡迎來自{region}{city}的新同伴【{name}】加入了我們的戰略會議室!\n請輸入help獲取幫助')except Exception as e:print (f'登錄邏輯中的條件判定捕獲到異常報錯:{e}')except Exception as e:print (f'登錄邏輯捕獲到異常報錯:{e}')#歡迎邏輯,應用了注冊和登錄邏輯
async def broadcast_welcome(websocket_conn,region,city,choose):global client_listtry:name_pwd = await websocket_conn.recv()if '注冊' in choose:await register(websocket_conn, region, city, name_pwd)if '登錄' in choose:await login(websocket_conn,region, city,name_pwd)except Exception as e:print (f'歡迎邏輯中捕獲到異常報錯:{e}')#服務器通常轉發邏輯
async def forwarding_other(sender_name,sender_msg,city):global client_listglobal locationtry:if sender_msg != 'hold,connect,status' \and not sender_msg.startswith('/cmd') \and not sender_msg.startswith('/cmd_return') \and sender_msg != '/client_online' \and not sender_msg.startswith('/get') \and  sender_msg != '/help' \and not sender_msg.startswith('/get_info') \and not sender_msg.startswith('/get_cpu_info') \and not sender_msg.startswith('/change_pwd'):time_now = datetime.now().strftime('%H:%M:%S')show_location_forward_msg = f'\r《{city}|({time_now})|{sender_name}>>{sender_msg}'normal_forward_msg = f'\r({time_now})|{sender_name}>>{sender_msg}'for client_name,ws_conn in client_list.items():if sender_msg == '/get_location':location = Trueif sender_msg == '/disable_location':location = Falseif client_name != sender_name and location == False:await ws_conn.send(normal_forward_msg)if client_name != sender_name and location == True:await ws_conn.send(show_location_forward_msg)except Exception as e:print (f'轉發邏輯捕獲到異常報錯:{e}')#Shell命令調用
async def sys_cmd_unicast(sender_name,sender_msg):global client_listtry:if sender_msg.startswith('/cmd') or sender_msg.startswith('/cmd_return'):parts = sender_msg.split(' ', 2)remote_user = parts[1]if sender_msg.startswith('/return_cmd'):remote_conn = client_list[remote_user]cmd_return = parts[2]remote_msg = cmd_returnawait remote_conn.send(remote_msg)if sender_msg.startswith('/cmd'):remote_conn = client_list[remote_user]cmd = parts[2]remote_msg = f'/cmd {sender_name} {cmd}'await remote_conn.send(remote_msg)except Exception as e:print (f'處理cmd命令的邏輯捕獲到異常報錯:{e}')#歸屬地查詢
async def get_ip_location(ip):# 指定 .xdb 文件路徑#D:/Python_Script/.venv/Lib/site-packages/ip2region/data/ip2region.xdb 我windows上存放位置#/usr/local/lib/python3.11/dist-packages/ip2region/data/ip2region.xdb 我ubuntu上存放位置try:dbfilepath = "/usr/local/lib/python3.11/dist-packages/ip2region/data/ip2region.xdb"# 初始化搜索對象searcher = xdbSearcher.XdbSearcher(dbfile=dbfilepath)# 查詢 IP 所在地區result1 = searcher.search(ip)location_parts = [part for part in result1.split('|') if part != '0' and part !='內網IP']if not location_parts:return '內網IP'return location_partsexcept Exception as e:print (f'歸屬地查詢庫的調用邏輯捕獲到異常報錯{e}')#心跳報文死亡消息的廣播邏輯
async def keepalive_broadcast_send(msg):global client_listtry:for client_name,ws_conn in client_list.items():await ws_conn.send(msg)except Exception as e:print (f'死亡廣播邏輯中捕獲到異常報錯:{e}')#監聽心跳報文
async def listen_keepalive(sender_msg,sender_name):global client_listglobal client_heartbeattry:if sender_msg == 'hold,connect,status':client_heartbeat[sender_name] = datetime.now()need_remove_client = []for name, last_time in client_heartbeat.items():if (datetime.now() - last_time).total_seconds() > 15:need_remove_client.append(name)for name in need_remove_client:ws_conn = client_list[name]del  client_list[name]del client_heartbeat[name]await ws_conn.close()time_now = datetime.now().strftime('%H:%M:%S')dead_msg = f'噢!親愛的【{name}】,我們曾引以為傲的一員,在北京時間{time_now}的時刻永遠地停止了心跳,離開了這個世界,朋友們,留給我們的時間不多了,敵人日益強大,我們的同伴卻在不斷減少。'await keepalive_broadcast_send(dead_msg)except Exception as e:print (f'心跳報文邏輯中捕獲的異常報錯')#顯示IP歸屬地邏輯
async def show_ip_location(sender_msg,region,city,sender_name):global client_listtry:if sender_msg == '/get_location':location = f'{region}|{city}'msg = f'/post_location {location}'ws_conn = client_list[sender_name]await ws_conn.send(msg)except Exception as e:print (f'顯示IP歸屬地邏輯中捕獲到異常報錯:{e}')
#獲取在線用戶邏輯
async def get_online_client(sender_msg,sender_name):global client_listtry:name_list = ''if sender_msg == '/client_online' :times = 0for name in client_list:if times == 0 :name_list += nametimes +=1elif times > 0:name_list += '-' + nameawait aprint(name_list)msg = f'/online_client_list {name_list}'ws_conn = client_list[sender_name]await ws_conn.send(msg)except Exception as e:print (f'獲取在線用戶邏輯中捕獲到異常報錯:{e}')#獲取信息邏輯
async def get_info(sender_msg,sender_name):global client_listtry:if sender_msg.startswith('/get_sys_info') :parts = sender_msg.split(' ', 1)remote_user = parts[1]ws_conn = client_list[remote_user]cmd = dedent("""system = platform.system()version = platform.version()arch=platform.architecture()[0]arch=arch.replace('bit','')hostname=platform.node()print('')print('系統信息:')print(f'主機名:{hostname}')print(f'操作系統:{arch}位{system}{version}')""")msg = f'/post_info {sender_name} {cmd}'await ws_conn.send(msg)if sender_msg.startswith('/get_part_info'):parts = sender_msg.split(' ', 1)remote_user = parts[1]ws_conn = client_list[remote_user]cmd = dedent("""all=psutil.disk_partitions()print('')print('分區信息:')for i in all:dict_info = psutil.disk_usage(i[0])total = int(dict_info[0]/1024/1024/1024)used = int(dict_info[1]/1024/1024/1024)used_percent = dict_info[3]print(f'盤符:{i[0]} 文件系統:{i[2]} 分區總空間:{total}GB 分區已用空間{used}GB 分區已用空間占比{used_percent}%')""")msg = f'/post_info {sender_name} {cmd}'await ws_conn.send(msg)if sender_msg.startswith('/get_memory_info'):parts = sender_msg.split(' ', 1)remote_user = parts[1]ws_conn = client_list[remote_user]cmd = dedent("""print('')print('內存信息')all = psutil.virtual_memory()sum_memory=int(all[0]/1024/1024/1024)print(f'總內存:{sum_memory}GB')available_memory=int(all[1]/1024/1024/1024)print (f'可用內存:{available_memory}GB')used=int(all[3]/1024/1024/1024)print(f'已用內存{used}GB')used_percent=all[2]print(f'已用內存占比:{used_percent}%')""")msg = f'/post_info {sender_name} {cmd}'await ws_conn.send(msg)if sender_msg.startswith('/get_cpu_info'):parts = sender_msg.split(' ', 1)remote_user = parts[1]ws_conn = client_list[remote_user]cmd =dedent("""cpu_per_count = psutil.cpu_count()cpu_freq = psutil.cpu_freq()[2]/1000print('')print('CPU信息:')print(f'CPU核心數: {psutil.cpu_count(logical=False)}')print(f"CPU線程數: {cpu_per_count}")print(f"頻率: {cpu_freq}GHz")""")msg = f'/post_info {sender_name} {cmd}'await ws_conn.send(msg)if sender_msg.startswith('/get_process_info'):parts = sender_msg.split(' ',1)remote_user=parts[1]ws_conn = client_list[remote_user]cmd = dedent("""print('')print('當前進程信息:')for i in psutil.process_iter():print (i)""")msg = f'/post_info {sender_name} {cmd}'await ws_conn.send(msg)except Exception as e:print (f'獲取信息邏輯中捕獲到異常報錯{e}')
#幫助邏輯
async def help_cmd(sender_name,sender_msg):global client_listtry:if sender_msg == '/help':ws_conn = client_list[sender_name]dilimiter = "-" * 50help_content = f"""\r用戶命令\r{dilimiter}\r獲取CPU信息: /get_cpu_info [用戶名]\r獲取系統信息: /get_sys_info [用戶名]\r獲取內存信息: /get_memory_info  [用戶名]\r獲取分區信息: /get_part_info [用戶名]\r獲取進程: /get_process_info [用戶名]\r非交互式調用Shell命令并獲取執行結果: /cmd [用戶名] [shell命令]\r獲取在線用戶列表:/client_online\r退出會議: [q] or [Q]\r顯示IP歸屬地信息: /get_location\r隱藏IP歸屬地信息: /disable_location\r獲取遞歸目錄樹: /get_path_tree [用戶名]\rps:輸入命令的時候不用填中括號[]\r幫助: /help\r{dilimiter}"""await ws_conn.send(help_content)except Exception as e:print (f'幫助邏輯中捕獲到異常報錯:{e}')#遞歸目錄樹的邏輯
async def get_path_tree(sender_name,sender_msg):global client_listtry:if sender_msg.startswith('/get_path_tree'):#/get_path_tree remote_name remote_pathprint ('tree,我來了')parts = sender_msg.split(' ',2)remote_name = parts[1]ws_conn = client_list[remote_name]remote_path = parts[2]msg = f'/get_path_tree {sender_name} {remote_path}'await ws_conn.send(msg)except Exception as e:print (f'遞歸目錄樹的邏輯捕獲到異常報錯{e}')async def change_pwd(sender_name,sender_msg):global client_listtry:if sender_msg.startswith('/change_pwd'):#/change_pwd new_pwdparts = sender_msg.split(' ',2)pwd = parts[1]ws_conn = client_list[sender_name]await db_update(sender_name,pwd)await ws_conn.send('密碼修改成功!')except Exception as e:print (f'改密邏輯捕獲到異常報錯:{e}')async def server_handle(websocket_conn:ServerConnection):address_port = websocket_conn.remote_addressip_address = address_port[0]location_parts = await get_ip_location(ip_address)if location_parts == '內網IP':region = '本地'city = '內網IP'else:region = location_parts[1]city = location_parts[2]try:choose = await websocket_conn.recv()await broadcast_welcome(websocket_conn,region,city,choose)except Exception as e:print (f'登錄|注冊的入口邏輯捕獲到異常報錯{e}')try:async for msg_dict_json in websocket_conn:msg_dict = json.loads(msg_dict_json)await aprint(msg_dict)sender_name = msg_dict['Name']sender_msg = msg_dict['Message']async with asyncio.TaskGroup() as TG:TG.create_task(sys_cmd_unicast(sender_name,sender_msg))TG.create_task(forwarding_other(sender_name,sender_msg,city))TG.create_task(listen_keepalive(sender_msg,sender_name))TG.create_task(show_ip_location(sender_msg,region,city,sender_name))TG.create_task(get_online_client(sender_msg,sender_name))TG.create_task(get_info(sender_msg,sender_name))TG.create_task(help_cmd(sender_name,sender_msg))TG.create_task(get_path_tree(sender_name,sender_msg))TG.create_task(change_pwd(sender_name,sender_msg))except Exception as e:print (f'雙向通信階段捕獲到異常報錯{e}')
async def main():try:async with serve(server_handle,'172.29.42.239',12345):print("WebSocket 服務器已啟動,監聽中...")await asyncio.Future()except Exception as e:print (f'啟動服務邏輯捕獲到異常報錯:{e}')if __name__ == '__main__':asyncio.run(main())

客戶端源代碼:

import os.path
import websockets
import asyncio
from aioconsole import aprint,ainput
import json
import subprocess
import sys
from io import StringIO
import psutil
import platform
import cpuinfo
from datetime import datetimeregion = ''
city = ''
location = Falseasync def send(ws_conn,name):global regionglobal cityglobal locationmsg = ''while 1:name_msg = {}time_now = datetime.now().strftime('%H:%M:%S')if location:msg = await ainput(f'《{region}{city}|({time_now})|{name}>>')if not location:msg = await ainput(f'({time_now})|{name}>>')if msg.upper() == 'Q':await ws_conn.close()await sys.exit()if msg == '/disable_location':location = Falseelse:name_msg['Name'] = namename_msg['Message'] = msgname_msg = json.dumps(name_msg)await ws_conn.send(name_msg)#獲取遞歸目錄樹
async def get_path_tree(path,depth=1):tree = ''dirname = os.path.basename(path)tree = tree +depth * '|-----' + dirname + '\n'files = os.listdir(path)for file in files:filepath = os.path.join(path,file)if os.path.isdir(filepath):tree = await get_path_tree(filepath,depth+1)if os.path.isfile(filepath):tree = tree + depth * '|-----' + '|-----' + file + '\n'return treeasync def recv(ws_conn,name):global locationglobal cityglobal regionwhile 1:msg = await ws_conn.recv()time_now = datetime.now().strftime('%H:%M:%S')if msg.startswith('/cmd'):parts = msg.split(' ',2)sender_name =parts[1]cmd_raw = parts[2]cmd_raw_split_num = cmd_raw.count(' ')if cmd_raw_split_num != 0:cmd = cmd_raw.split(' ',cmd_raw_split_num)else:cmd = cmd_rawresult = subprocess.run(cmd,text=True,shell=True,capture_output=True)cmd_result = result.stdoutmsg = f'/return_cmd {sender_name} {cmd_result}'name_unicate_msg = {}name_unicate_msg['Name'] = namename_unicate_msg['Message'] = msgname_unicate_msg_json = json.dumps(name_unicate_msg)await ws_conn.send(name_unicate_msg_json)if  msg.startswith('/post_location'):parts = msg.split(' ',1)location_parts = parts[1]location_parts = location_parts.split('|',1)region = location_parts[0]city = location_parts[1]location = Trueif msg.startswith('/post_info'):parts = msg.split(' ',2)sender_name = parts[1]cmd = parts[2]output = StringIO()sys.stdout = outputexec(cmd)result_cmd = output.getvalue()sys.stdout = sys.__stdout__msg = f'/result_cmd {sender_name} {result_cmd}'print (msg)name_unicate_msg = {}name_unicate_msg['Name'] = namename_unicate_msg['Message'] = msgname_unicate_msg_json = json.dumps(name_unicate_msg)await ws_conn.send(name_unicate_msg_json)if msg.startswith('/get_path_tree'):parts = msg.split(' ',2)sender_name = parts[1]local_path = parts[2]tree = await get_path_tree(local_path)print (tree)msg = f'/result_cmd {sender_name} {tree}'name_unicate_msg = {}name_unicate_msg['Name'] = namename_unicate_msg['Message'] = msgname_unicate_msg_json = json.dumps(name_unicate_msg)await ws_conn.send(name_unicate_msg_json)if msg.startswith('/online_client_list'):parts = msg.split(' ',1)name_list = parts[1]n=1split_num = name_list.count('-')if split_num >0:name_list = name_list.split('-',split_num)n = 0for online_user in name_list:if n == 0:await aprint(f'\n在線用戶列表:')n += 1if n > 0 :await aprint(f' 用戶{n}:{online_user}')elif msg.startswith('/'):if location:await aprint(f'《{region}{city}|{name}>>', end='', flush=True)else:await aprint(f'{name}>>',end='',flush=True)else:if location:await aprint(f'\r{msg}\n《{region}{city}|({time_now})|{name}>>', end='', flush=True)else:await aprint(f'\r{msg}\n({time_now})|{name}>>', end='', flush=True)#客戶端注冊邏輯
async def register(ws_conn):await aprint("-" * 50)await aprint('注冊階段')await aprint("-" * 50)name = await ainput('請輸入用戶名:\n')pwd = await ainput('請輸入登錄代碼:\n')await aprint("-" * 50)name_pwd = f'{name} {pwd}'await ws_conn.send(name_pwd)  # 發送賬號密碼return name#服務端登錄邏輯
async def login(ws_conn):await aprint("-" * 50)await aprint('登錄階段')await aprint("-" * 50)name = await ainput('請輸入用戶名:\n')pwd = await ainput('請輸入登錄代碼:\n')await aprint("-" * 50)name_pwd = f'{name} {pwd}'await ws_conn.send(name_pwd)  # 發送賬號密碼return nameasync def create_name(ws_conn,choose):while 1:if '注冊' in choose:name = await register(ws_conn)print (name)return nameif '登錄' in choose:name = await login(ws_conn)msg = await ws_conn.recv()await aprint (msg)if '輸入錯誤' in msg:continueif '尚未注冊' in msg:await create_name(ws_conn,choose='注冊')if '登錄成功' in msg:return nameasync def keepalive(ws_conn,name):keepalive_packets = {}content = 'hold,connect,status'keepalive_packets['Name'] = namekeepalive_packets['Message'] = contentwhile 1:keepalive_packets_json = json.dumps(keepalive_packets)await ws_conn.send(keepalive_packets_json)await asyncio.sleep(5)async def client_handle():url = 'ws://47.111.23.151:12345'async with websockets.connect(url) as ws_conn:await aprint (r"""(`-').->  (`-')  _               (`-')   (`-')  _ (`-')          <-. (`-')   (`-')  _  (`-')  _ (`-')        _      <-. (`-')_            ( OO)_    ( OO).-/  _         <-.(OO )   ( OO).-/ ( OO).->          \(OO )_  ( OO).-/  ( OO).-/ ( OO).->    (_)        \( OO) )    .->    
(_)--\_)  (,------.  \-,-----. ,------,) (,------. /    '._       ,--./  ,-.)(,------. (,------. /    '._    ,-(`-') ,--./ ,--/  ,---(`-') 
/    _ /   |  .---'   |  .--./ |   /`. '  |  .---' |'--...__)     |   `.'   | |  .---'  |  .---' |'--...__)  | ( OO) |   \ |  | '  .-(OO ) 
\_..`--.  (|  '--.   /_) (`-') |  |_.' | (|  '--.  `--.  .--'     |  |'.'|  |(|  '--.  (|  '--.  `--.  .--'  |  |  ) |  . '|  |)|  | .-, \ 
.-._)   \  |  .--'   ||  |OO ) |  .   .'  |  .--'     |  |        |  |   |  | |  .--'   |  .--'     |  |    (|  |_/  |  |\    | |  | '.(_/ 
\       /  |  `---. (_'  '--'\ |  |\  \   |  `---.    |  |        |  |   |  | |  `---.  |  `---.    |  |     |  |'-> |  | \   | |  '-'  |  `-----'   `------'    `-----' `--' '--'  `------'    `--'        `--'   `--' `------'  `------'    `--'     `--'    `--'  `--'  `-----'   
""")await aprint("    ┌──────────────────────────────────────────────────────────────────────┐")await aprint("    │                    ? Secret Meeting v1.0 ?                           │")await aprint("    │                           Forever                                    │")await aprint("    │? WebSocket Successful Connect to Server@47.111.23.151                │")await aprint("    └──────────────────────────────────────────────────────────────────────┘")while 1:option = input('【注冊】(register)還是【登錄】(login),請做出你的選擇\n1.注冊\n2.登錄\n')if option == '1':choose = '注冊'breakif option == '2':choose = '登錄'breakelse:continueawait ws_conn.send(choose)name = await create_name(ws_conn,choose)msg_welcome = await ws_conn.recv()await aprint(msg_welcome)async with asyncio.TaskGroup() as TG:TG.create_task(send(ws_conn,name))TG.create_task(recv(ws_conn,name))TG.create_task(keepalive(ws_conn,name))if __name__ == '__main__':asyncio.run(client_handle())

實驗效果:

服務端剛運行,開始監聽:

客戶端初次進入聊天室:

客戶端注冊后:

客戶端獲取幫助:

客戶端注冊登錄后,服務端的心跳開始運作:

總結:

這些功能各位可以自己去嘗試,我就不一一演示了,源碼自取即可。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/89592.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/89592.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/89592.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

OpenCV CUDA模塊設備層-----反向二值化閾值處理函數thresh_binary_inv_func()

操作系統&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 編程語言&#xff1a;C11 算法描述 OpenCV CUDA 模塊&#xff08;cudev&#xff09; 中的一個仿函數&#xff08;functor&#xff09;生成器&#xff0c;用于創建一個反向二值化閾值…

【實現一個時間MCP完整技術解析】

&#x1f552; MCP Time Server 完整技術解析&#xff1a;從核心實現到文件架構的深度剖析 目前已上傳npm庫&#xff0c;chan-mcp-time-server&#xff0c;有興趣的可以下載試試 創建時間: 2025年7月2日 &#x1f3af; 項目概述與架構設計 核心問題定義 AI助手在處理時間相關…

類成員方法命名風格解析:動賓、純動詞與純名詞的選擇之道

在軟件開發的浩瀚代碼海洋中&#xff0c;類成員方法的命名猶如指引開發者的燈塔&#xff0c;其重要性不言而喻。合理的命名不僅能讓代碼 “自我言說”&#xff0c;降低理解成本&#xff0c;還能提升開發效率&#xff0c;促進團隊協作。常見的類成員方法命名風格可歸納為動賓結構…

自己電腦搭建本地服務器并實現公網訪問,內網也能提供互聯網連接使用

如何在本地自己計算機上自建服務器并開啟公網地址提供互聯網服務的詳細教學&#xff0c;一步步操作流程&#xff0c;從本地部署到配置公網IP&#xff0c;最后并附無公網IP內網穿透公網訪問的nat123方案。 要在自用的電腦上搭建本地服務器并實現公網地址的訪問&#xff0c;需要…

如何使用AI改進論文寫作 ---- 引言篇(2)

寫在前面 本篇作為1.0版本的補充優化&#xff0c;記錄本人的研究過程。 在分析了多本論文寫作的相關的書籍之后&#xff0c;我明白了一點&#xff0c;關于論文寫作&#xff0c;永遠是一個熟能生巧的過程&#xff0c;對于人來說&#xff0c;必須多寫才能夠變得熟練&#xff0c;對…

【Java21】在spring boot中使用ScopedValue

文章目錄 0.環境說明1.基礎知識1.1 ScopedValue的特點 2.應用場景2.1 spring web項目中&#xff0c;使用ScopedValue傳遞上下文&#xff08;全局不可變量&#xff09;2.2 spring grpc項目中&#xff0c;使用ScopedValue傳遞上下文&#xff08;全局不可變量&#xff09; 3.Scope…

第10篇 圖像語義分割和目標檢測介紹

語義分割(Semantic Segmentation)是圖像處理和機器視覺一個重要分支&#xff0c;其目標是精確理解圖像場景與內容。語義分割是在像素級別上的分類&#xff0c;屬于同一類的像素都要被歸為一類&#xff0c;因此語義分割是從像素級別來理解圖像的。如下如所示的照片&#xff0c;屬…

微算法科技(NASDAQ MLGO)基于量子圖像處理的邊緣檢測算法:開拓圖像分析新視野

在當今數字化時代&#xff0c;圖像數據海量增長&#xff0c;邊緣檢測作為圖像處理的關鍵環節&#xff0c;在機器視覺、醫學成像、安防監控等眾多領域有著至關重要的作用。傳統邊緣檢測算法在處理復雜圖像時&#xff0c;面臨計算效率低、精度不足等問題。量子計算的興起&#xf…

SM4密碼算法的C語言實現(帶測試)

一、SM4算法原理 SM4是中國國家密碼管理局于2012年發布的國家商用密碼算法標準&#xff0c;也稱為GB/T 32907-2016。它是一種分組對稱加密算法&#xff0c;采用32輪非線性迭代結構&#xff0c;分組長度和密鑰長度均為128位。SM4算法的設計充分考慮了安全性、高效性和實現簡便性…

【React Native原生項目不能運行npx react-native run-android項目】

運行命令報錯,幫我修復X:\jetbrains-workspace\theme-wallpaper>npx react-native run-android error Android project not found. Are you sure this is a React Native project? If your Android files are located in a non-standard location (e.g. not inside ‘andro…

SPLADE 在稀疏向量搜索中的原理與應用詳解

今天看到Sentence Transformers v5.0 集成了許多稀疏嵌入模型。為了搞清楚什么稀疏嵌入模型以及應用&#xff0c;查到了SPLADE&#xff0c;比較巧合的是在paper reading分享的時候看到有同學分享了一片ACL 2025的工作也是基于SPLADE去做的。下面結合一些資料分享關于SPLADE 在稀…

wpf的Binding之UpdateSourceTrigger

前言 在wpf界面開發中&#xff0c;Binding的源和目標之間可以通過Mode來決定數據的傳遞方向&#xff0c;同時數據傳遞時的觸發條件也是可以有多種情況&#xff0c;多種情況由UpdateSourceTrigger屬性來控制&#xff0c;該屬性有Default、Explicit、LostFocus、PropertyChanged…

突破性進展:超短等離子體脈沖實現單電子量子干涉,為飛行量子比特奠定基礎

關鍵詞&#xff1a;量子計算、電子干涉測量、等離子體脈沖、馬赫-曾德爾干涉儀、非絕熱量子操控 研究背景 在量子計算領域&#xff0c;飛行量子比特&#xff08;flying qubits&#xff09;因其動態傳播特性和通過庫侖相互作用直接糾纏的能力&#xff0c;成為替代光子量子比特的…

Java調用百度地圖天氣查詢服務獲取當前和未來天氣-以貴州省榕江縣為例

目錄 前言 一、百度天氣查詢服務 1、天氣查詢服務 2、查詢API簡介 二、UniHttp集成天氣查詢服務 1、定義訪問接口 2、業務集成調用 三、天氣檢索成果 1、IDE檢索結果輸出 2、互聯網天氣對比 四、總結 前言 天氣與人們的生活息息相關&#xff0c;無論是日常出行、農業…

Windows Excel文檔辦公工作數據整理小工具

在現代辦公環境中&#xff0c;Excel 是處理數據不可或缺的工具&#xff0c;而 “Excel 工作圈小工具” 則如同為 Excel 量軟件下載地址安裝包 身打造的超級增效器&#xff0c;它是一個集合了大量 Excel 功能的綠色工具軟件&#xff0c;能夠顯著提升你的工作效率。 這款軟件雖然…

Node.js v22.5+ 官方 SQLite 模塊全解析:從入門到實戰

在 Node.js v22.5.0 及更高版本中&#xff0c;node:sqlite 模塊作為內置模塊被引入&#xff0c;為開發者提供了與 SQLite 數據庫交互的官方支持。以下是關于 node:sqlite 模塊的詳細介紹&#xff1a; 一、模塊啟用與導入 啟用方式&#xff1a;node:sqlite 模塊目前處于活躍開…

API接口安全-2:簽名、時間戳與Token如何聯手抵御攻擊

在API接口通信中&#xff0c;數據傳輸的安全性至關重要。無論是前端與后端的交互&#xff0c;還是企業間的接口對接&#xff0c;一旦缺乏有效的安全校驗&#xff0c;攻擊者可能通過抓包篡改參數&#xff08;如修改訂單金額&#xff09;、重放攻擊&#xff08;重復提交支付請求&…

Pull Request記錄與Git commit簽名

Pull Request記錄 好久沒有pull request了&#xff0c;淺淺記錄一下流程 &#xff1a;Fork 原項目&#xff08;如果你沒有寫權限&#xff09;&#xff1a;打開原項目主頁&#xff08;例如&#xff1a;github.com/your-professor/research-topic&#xff09;&#xff0c;點擊右…

如何在C++交易系統中集成高性能回測與模擬撮合

DolphinDB 的高性能行情回放與模擬撮合引擎插件&#xff0c;為量化交易者提供了低延遲、高吞吐量的策略驗證解決方案。對于已構建 C 回測框架的機構而言&#xff0c;直接在現有系統中集成撮合引擎&#xff0c;既能復用既有基礎設施&#xff0c;又能獲得 DolphinDB 的極速計算優…

【Laravel】 Laravel 智能驗證規則生成器

Laravel 智能驗證規則生成器:企業級增強方案 <?phpnamespace App\Services\Validation;use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Validator; use Illuminate\Support\Str; use Illuminate\Validation\…