目录

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\…