
作者:李曉飛
來源:Python
Facebook 改名為 meta,一下子點(diǎn)燃了 元宇宙 這個(gè)概念。
今天我就用 Python 實(shí)現(xiàn)一個(gè)簡單的迷你元宇宙。
代碼簡潔易懂,不僅可以學(xué)習(xí) Python 知識,還能用實(shí)踐理解元宇宙的概念。
還等什么,現(xiàn)在就開始吧!
什么是元宇宙?
不同的人有不同的理解和認(rèn)識,最能達(dá)成共識的是:
元宇宙是個(gè)接入點(diǎn),每個(gè)人都可以成為其中的一個(gè)元素,彼此互動(dòng)。
那么我們的元宇宙有哪些功能呢?
首先必須有可以接入的功能。
然后彼此之間可以交流信息。比如 a 發(fā)消息給 b,b 可以發(fā)消息給 a,同時(shí)可以將消息廣播出去,也就是成員之間,可以私信 和 群聊。
另外,在元宇宙的成員可以收到元宇宙的動(dòng)態(tài),比如新人加入,或者有人離開等,如果玩膩了,可以離開元宇宙。
最終的效果像這樣:
效果
直接思考可能比較困難,換個(gè)角度想,接入點(diǎn)其實(shí)就是 —— 服務(wù)器。
只要是上網(wǎng),每時(shí)每刻,我們都是同服務(wù)器打交的。
那就選擇最簡單的 TCP 服務(wù)器,TCP 服務(wù)器的核心是維護(hù)套接字(socket)的狀態(tài),向其中發(fā)送或者獲取信息。
python 的 socket 庫,提供了很多有關(guān)便捷方法,可以幫助我們構(gòu)建。
核心代碼如下:
import socket socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) socket.bind((ip, port)) socket.listen() data = socket.recv(1024) ...
創(chuàng)建一個(gè) socket,讓其監(jiān)聽機(jī)器所擁有的一個(gè) ip 和 端口,然后從 socket 中讀取發(fā)送過來的數(shù)據(jù)。
客戶端是為了方便用戶鏈接到元宇宙的工具,這里,就是能鏈接到服務(wù)器的工具,服務(wù)器是 TCP 服務(wù)器,客戶端自然需要用可以鏈接 TCP 服務(wù)器的方式。
python 也已為我們備好,幾行代碼就可以搞定,核心代碼如下:
import socket client = socket.socket(socket.AF_INET,socket.SOCK_STREAM) client.connect((ip, port)) data = client.recv(1024) ...
代碼與服務(wù)器很像,不過去鏈接一個(gè)服務(wù)器的 ip 和 端口。
首先需要讓服務(wù)器將接入的用戶管理起來。
然后當(dāng)接收到用戶消息時(shí)做出判斷,是轉(zhuǎn)發(fā)給其他用戶,廣播還是做出回應(yīng)。
這樣就需要構(gòu)造一種消息格式,用來表示用戶消息的類型或者目的。
我們就用 @username 的格式來區(qū)分,消息發(fā)給特殊用戶還是群發(fā)。
另外,為了完成注冊功能,需要再定義一種命令格式,用于設(shè)置 username,我們可以用 name:username 的格式作為設(shè)置用戶名的命令。
有了初步設(shè)計(jì),就可以進(jìn)一步構(gòu)建我們的代碼了。
服務(wù)器需要同時(shí)響應(yīng)多個(gè)鏈接,其中包括新鏈接創(chuàng)建,消息 和 鏈接斷開 等。
為了不讓服務(wù)器阻塞,我們采用非阻塞的鏈接,當(dāng)鏈接接入時(shí),將鏈接存儲(chǔ)起來,然后用 select 工具,等待有了消息的鏈接。
這個(gè)功能,已經(jīng)有人實(shí)現(xiàn)好了 simpletcp[1],只要稍作改動(dòng)就好。
將其中的收到消息,建立鏈接,關(guān)閉鏈接做成回調(diào)方法,以便再外部編寫業(yè)務(wù)邏輯。
這里說明一下核心代碼:
# 創(chuàng)建一個(gè)服務(wù)器鏈接 self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self._socket.setblocking(0) self._socket.bind((self.ip, self.port)) self._socket.listen(self._max_connections) # 存放已建立的鏈接 readers = [self._socket] # 存放客戶端 ip和端口 IPs = dict() # 退出標(biāo)記 用于關(guān)閉服務(wù)器 self._stop = False # 服務(wù)器主循環(huán) while readers and not self._stop: # 利用 select 從 建立的鏈接中選取一些有新消息的 read, _, err = select.select(readers, [], readers) for sock in read: if sock is self._socket: # 建立了新鏈接 # 獲取新鏈接的 socket 以及 ip和端口 client_socket, client_ip = self._socket.accept() # 將鏈接設(shè)置為非阻塞的 client_socket.setblocking(0) # 添加到監(jiān)聽隊(duì)列 readers.append(client_socket) # 存儲(chǔ)ip信息 IPs[client_socket] = client_ip # 調(diào)用建立鏈接回調(diào)函數(shù) self.onCreateConn(self, client_socket, client_ip) else: # 收到了新消息 try: # 獲取消息 data = sock.recv(self.recv_bytes)
except socket.error as e: if e.errno == errno.ECONNRESET: # 表明鏈接已退出 data = None else:
raise e if data: # 調(diào)用收到消息回調(diào)函數(shù) self.onReceiveMsg(self, sock, IPs[sock], data) else: # 鏈接退出時(shí),移除監(jiān)聽隊(duì)列 readers.remove(sock)
sock.close() # 調(diào)用鏈接關(guān)閉回調(diào)函數(shù) self.onCloseConn(self, sock, IPs[sock]) # 處理存在錯(cuò)誤的鏈接 for sock in err: # 移除監(jiān)聽隊(duì)列 readers.remove(sock)
sock.close() # 調(diào)用鏈接關(guān)閉回調(diào)函數(shù) self.onCloseConn(self, sock, IPs[sock])
現(xiàn)在通過回調(diào)函數(shù),就可以編寫業(yè)務(wù)了,之間看代碼。
這段是建立鏈接時(shí)的處理:
def onCreateConn(server, sock, ip): cid = f'{ip[0]}_{ip[1]}' clients[cid] = {'cid': cid, 'sock': sock, 'name': None}
sock.send("你已經(jīng)接入元宇宙,告訴我你的代號,輸入格式為 name:lily.".encode('utf-8'))
然后是接收消息的回調(diào)函數(shù),這個(gè)相對復(fù)雜一些,主要是處理的情況更多:
def onReceiveMsg(server, sock, ip, data): cid = f'{ip[0]}_{ip[1]}' data = data.decode('utf-8')
print(f"收到數(shù)據(jù): {data}")
_from = clients[cid] if data.startswith('name:'): # 設(shè)置名稱 name = data[5:].strip() if not name:
sock.send(f"不能設(shè)置空名稱,否則其他人找不見你".encode('utf-8')) elif not checkname(name, cid):
sock.send(f"這個(gè)名字{name}已經(jīng)被使用,請換一個(gè)試試".encode('utf-8')) else: if not _from['name']:
sock.send(f"{name} 很高興見到你,現(xiàn)在可以暢游元宇宙了".encode('utf-8'))
msg = f"新成員{name} 加入了元宇宙,和TA聊聊吧".encode('utf-8')
sendMsg(msg, _from) else:
sock.send(f"更換名稱完成".encode('utf-8'))
msg = f"{_from['name']} 更換名稱為 {name},和TA聊聊吧".encode('utf-8')
sendMsg(msg, _from)
_from['name'] = name elif '@' in data: # 私信 targets = re.findall(r'@(.+?) ', data)
print(targets)
msg = f"{_from['name']}: {data}".encode('utf-8')
sendMsg(msg, _from, targets) else: # 群信 msg = f"{_from['name']}:{data}".encode('utf-8')
sendMsg(msg, _from)
當(dāng)鏈接關(guān)閉時(shí),需要處理一下關(guān)閉的回調(diào)函數(shù):
def onCloseConn(server, sock, ip): cid = f'{ip[0]}_{ip[1]}' name = clients[cid]['name'] if name:
msg = f"{name} 從元宇宙中消失了".encode('utf-8')
sendMsg(msg, clients[cid]) del clients[cid]
客戶端需要解決兩個(gè)問題,第一個(gè)是處理接收到的消息,第二個(gè)是允許用戶的輸入。
我們將接收消息作為一個(gè)線程,將用戶輸入作為主循環(huán)。
先看接收消息的代碼:
def receive(client): while True: try:
s_info = client.recv(1024) # 接受服務(wù)端的消息并解碼 if not s_info:
print(f"{bcolors.WARNING}服務(wù)器鏈接斷開{bcolors.ENDC}") break print(f"{bcolors.OKCYAN}新的消息:{bcolors.ENDC}n", bcolors.OKGREEN + s_info.decode('utf-8')+ bcolors.ENDC) except Exception:
print(f"{bcolors.WARNING}服務(wù)器鏈接斷開{bcolors.ENDC}") break if close: break
下面再看一下輸入控制程序:
while True: pass value = input("")
value = value.strip() if value == ':start': if thread:
print(f"{bcolors.OKBLUE}您已經(jīng)在元宇宙中了{bcolors.ENDC}") else:
client = createClient(ip, 5000)
thread = Thread(target=receive, args=(client,))
thread.start()
print(f"{bcolors.OKBLUE}您進(jìn)入元宇宙了{bcolors.ENDC}") elif value == ':quit' or value == ':stop': if thread:
client.close()
close = True print(f"{bcolors.OKBLUE}正在退出中…{bcolors.ENDC}")
thread.join()
print(f"{bcolors.OKBLUE}元宇宙已退出{bcolors.ENDC}")
thread = None if value == ':quit':
print(f"{bcolors.OKBLUE}退出程序{bcolors.ENDC}") break pass elif value == ':help':
help() else: if client: # 聊天模式 client.send(value.encode('utf-8')) else:
print(f'{bcolors.WARNING}還沒接入元宇宙,請先輸入 :start 接入{bcolors.ENDC}')
client.close()
完成了整體編碼之后,就可以啟動(dòng)了,最終的代碼由三部分組成。
第一部分是服務(wù)器端核心代碼,存放在 simpletcp.py 中。
第二部分是服務(wù)器端業(yè)務(wù)代碼,存放在 metaServer.py 中。
第三部分是客戶端代碼,存放在 metaClient.py 中。
另外需要一些輔助的處理,比如發(fā)送消息的 sendMsg 方法,顏色處理方法等,具體可以下載本文源碼了解。
進(jìn)入代碼目錄,啟動(dòng)命令行,執(zhí)行 python metaServer.py,輸入指令 start:
server
然后再打開一個(gè)命令行,執(zhí)行 python metaClient.py,輸入指令 :start,就可以接入到元宇宙:
client
設(shè)置自己的名字:
如果有新的成員加入時(shí),就會(huì)得到消息提醒, 還可以玩點(diǎn)互動(dòng):
怎么樣好玩吧,一個(gè)元宇宙就這樣形成了,趕緊讓其他伙伴加入試試吧。
元宇宙現(xiàn)在是個(gè)很熱的概念,但還是基于現(xiàn)有的技術(shù)打造的,元宇宙給人們提供了一個(gè)生活在虛擬的神奇世界里的想象空間,其實(shí)自從有了互聯(lián)網(wǎng),我們就已經(jīng)逐步生活在元宇宙之中了。
今天我們用基礎(chǔ)的 TCP 技術(shù),構(gòu)建了一個(gè)自己的元宇宙聊天室,雖然功能上和想象中的元宇宙相去甚遠(yuǎn),不過其中的主要功能已經(jīng)成形了。
如果有興趣還可以在這個(gè)基礎(chǔ)上加入更好玩的功能,比如好友,群組,消息記錄等等,在深入了解的同時(shí),讓這個(gè)元宇宙更好玩。
數(shù)據(jù)分析咨詢請掃描二維碼
若不方便掃碼,搜微信號:CDAshujufenxi
SQL Server 中 CONVERT 函數(shù)的日期轉(zhuǎn)換:從基礎(chǔ)用法到實(shí)戰(zhàn)優(yōu)化 在 SQL Server 的數(shù)據(jù)處理中,日期格式轉(zhuǎn)換是高頻需求 —— 無論 ...
2025-09-18MySQL 大表拆分與關(guān)聯(lián)查詢效率:打破 “拆分必慢” 的認(rèn)知誤區(qū) 在 MySQL 數(shù)據(jù)庫管理中,“大表” 始終是性能優(yōu)化繞不開的話題。 ...
2025-09-18CDA 數(shù)據(jù)分析師:表結(jié)構(gòu)數(shù)據(jù) “獲取 - 加工 - 使用” 全流程的賦能者 表結(jié)構(gòu)數(shù)據(jù)(如數(shù)據(jù)庫表、Excel 表、CSV 文件)是企業(yè)數(shù)字 ...
2025-09-18DSGE 模型中的 Et:理性預(yù)期算子的內(nèi)涵、作用與應(yīng)用解析 動(dòng)態(tài)隨機(jī)一般均衡(Dynamic Stochastic General Equilibrium, DSGE)模 ...
2025-09-17Python 提取 TIF 中地名的完整指南 一、先明確:TIF 中的地名有哪兩種存在形式? 在開始提取前,需先判斷 TIF 文件的類型 —— ...
2025-09-17CDA 數(shù)據(jù)分析師:解鎖表結(jié)構(gòu)數(shù)據(jù)特征價(jià)值的專業(yè)核心 表結(jié)構(gòu)數(shù)據(jù)(以 “行 - 列” 規(guī)范存儲(chǔ)的結(jié)構(gòu)化數(shù)據(jù),如數(shù)據(jù)庫表、Excel 表、 ...
2025-09-17Excel 導(dǎo)入數(shù)據(jù)含缺失值?詳解 dropna 函數(shù)的功能與實(shí)戰(zhàn)應(yīng)用 在用 Python(如 pandas 庫)處理 Excel 數(shù)據(jù)時(shí),“缺失值” 是高頻 ...
2025-09-16深入解析卡方檢驗(yàn)與 t 檢驗(yàn):差異、適用場景與實(shí)踐應(yīng)用 在數(shù)據(jù)分析與統(tǒng)計(jì)學(xué)領(lǐng)域,假設(shè)檢驗(yàn)是驗(yàn)證研究假設(shè)、判斷數(shù)據(jù)差異是否 “ ...
2025-09-16CDA 數(shù)據(jù)分析師:掌控表格結(jié)構(gòu)數(shù)據(jù)全功能周期的專業(yè)操盤手 表格結(jié)構(gòu)數(shù)據(jù)(以 “行 - 列” 存儲(chǔ)的結(jié)構(gòu)化數(shù)據(jù),如 Excel 表、數(shù)據(jù) ...
2025-09-16MySQL 執(zhí)行計(jì)劃中 rows 數(shù)量的準(zhǔn)確性解析:原理、影響因素與優(yōu)化 在 MySQL SQL 調(diào)優(yōu)中,EXPLAIN執(zhí)行計(jì)劃是核心工具,而其中的row ...
2025-09-15解析 Python 中 Response 對象的 text 與 content:區(qū)別、場景與實(shí)踐指南 在 Python 進(jìn)行 HTTP 網(wǎng)絡(luò)請求開發(fā)時(shí)(如使用requests ...
2025-09-15CDA 數(shù)據(jù)分析師:激活表格結(jié)構(gòu)數(shù)據(jù)價(jià)值的核心操盤手 表格結(jié)構(gòu)數(shù)據(jù)(如 Excel 表格、數(shù)據(jù)庫表)是企業(yè)最基礎(chǔ)、最核心的數(shù)據(jù)形態(tài) ...
2025-09-15Python HTTP 請求工具對比:urllib.request 與 requests 的核心差異與選擇指南 在 Python 處理 HTTP 請求(如接口調(diào)用、數(shù)據(jù)爬取 ...
2025-09-12解決 pd.read_csv 讀取長浮點(diǎn)數(shù)據(jù)的科學(xué)計(jì)數(shù)法問題 為幫助 Python 數(shù)據(jù)從業(yè)者解決pd.read_csv讀取長浮點(diǎn)數(shù)據(jù)時(shí)的科學(xué)計(jì)數(shù)法問題 ...
2025-09-12CDA 數(shù)據(jù)分析師:業(yè)務(wù)數(shù)據(jù)分析步驟的落地者與價(jià)值優(yōu)化者 業(yè)務(wù)數(shù)據(jù)分析是企業(yè)解決日常運(yùn)營問題、提升執(zhí)行效率的核心手段,其價(jià)值 ...
2025-09-12用 SQL 驗(yàn)證業(yè)務(wù)邏輯:從規(guī)則拆解到數(shù)據(jù)把關(guān)的實(shí)戰(zhàn)指南 在業(yè)務(wù)系統(tǒng)落地過程中,“業(yè)務(wù)邏輯” 是連接 “需求設(shè)計(jì)” 與 “用戶體驗(yàn) ...
2025-09-11塔吉特百貨孕婦營銷案例:數(shù)據(jù)驅(qū)動(dòng)下的精準(zhǔn)零售革命與啟示 在零售行業(yè) “流量紅利見頂” 的當(dāng)下,精準(zhǔn)營銷成為企業(yè)突圍的核心方 ...
2025-09-11CDA 數(shù)據(jù)分析師與戰(zhàn)略 / 業(yè)務(wù)數(shù)據(jù)分析:概念辨析與協(xié)同價(jià)值 在數(shù)據(jù)驅(qū)動(dòng)決策的體系中,“戰(zhàn)略數(shù)據(jù)分析”“業(yè)務(wù)數(shù)據(jù)分析” 是企業(yè) ...
2025-09-11Excel 數(shù)據(jù)聚類分析:從操作實(shí)踐到業(yè)務(wù)價(jià)值挖掘 在數(shù)據(jù)分析場景中,聚類分析作為 “無監(jiān)督分組” 的核心工具,能從雜亂數(shù)據(jù)中挖 ...
2025-09-10統(tǒng)計(jì)模型的核心目的:從數(shù)據(jù)解讀到?jīng)Q策支撐的價(jià)值導(dǎo)向 統(tǒng)計(jì)模型作為數(shù)據(jù)分析的核心工具,并非簡單的 “公式堆砌”,而是圍繞特定 ...
2025-09-10