Facebook更名为meta,一下子点燃了metaverse的概念。今天我将用Python实现一个简单的迷你虚拟宇宙。代码简洁易懂。不仅可以学习Python知识,还可以通过实践来理解Metaverse的概念。还等什么,现在就开始吧!Mini-metaverse什么是Metaverse?不同的人有不同的理解和理解。最普遍的共识是:虚拟世界是一个接入点,每个人都可以成为其中的一个元素,彼此互动。那么我们的元界有哪些功能呢?首先,必须有可以访问的功能。然后可以相互交换信息。比如a给b发消息,b可以给a发消息,同时广播消息,即成员可以私信和群聊。另外,元界成员可以收到元界动态,比如有新人加入,有人离开等,玩腻了可以离开元界。最终的效果是这样的:可能很难直接去思考如何设计一个接入点。从另一个角度来看,接入点实际上是一台服务器。只要我们在线,就无时无刻不在和服务器打交道。然后选择最简单的TCP服务器。TCP服务器的核心是维护套接字(socket)的状态,向其发送或获取信息。Python的socket库提供了很多方便的方法可以帮助我们构建。核心代码如下:importsocketsocket=socket.socket(socket.AF_INET,socket.SOCK_STREAM)socket.bind((ip,port))socket.listen()data=socket.recv(1024)...创建socket并让它监听一个本机拥有的ip和端口,然后读取socket发送过来的数据。如何搭建客户端客户端是用户连接元界的工具。在这里,它是一个可以连接到服务器的工具。服务端是一个TCP服务器,客户端自然需要使用能够连接到TCP服务器的方法。Python也已经为我们准备好了,几行代码就可以搞定,核心代码如下:client.recv(1024)...代码和服务器很相似,但是连接到一个服务器的ip和端口。如何构建业务逻辑,首先需要服务端对访问用户进行管理。然后当收到一条用户消息时,判断是转发给其他用户,广播还是回复。这样就需要构造一个消息格式来表示用户消息的类型或目的。我们使用@username的格式来区分消息是发送给特定用户还是发送给某个群组。另外,为了完成注册功能,还需要定义一个设置用户名的命令格式。我们可以使用name:username的格式作为设置用户名的命令。构建有了初步设计,就可以进一步构建我们的代码了。服务器服务器需要同时响应多个连接,包括新建连接、消息和连接断开等,为了不阻塞服务器,我们使用非阻塞连接。连接上后,存储连接,然后用select工具等待有消息的连接。simpletcp[1]已经实现了这个功能,只需要稍作修改即可。把接收到的消息,建立链接,关闭链接做成一个回调方法,这样就可以对外写业务逻辑了。核心业务下面是核心代码:#创建服务器链接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)#Storeestablishedlinksreaders=[self._socket]#Storeclientipandportips=dict()#exitmark用于关闭服务器self._stop=False#Servermainloopwhilereadersandnotself._stop:#使用select从已建立的链接中选择一些新消息。read,_,err=select.select(readers,[],readers)forsockinread:ifsockiself._socket:#建立新链接#获取socket和新链接的ip和端口client_socket,client_ip=self._socket.accept()#设置链接为非阻塞client_socket.setblocking(0)#加入监听队列readers.append(client_socket)#存储ip信息IPs[client_socket]=client_ip#调用连接回调函数self.onCreateConn(self,client_socket,client_ip)else:#Receivedanewmessagetry:#Getthemessagedata=sock.recv(self.recv_bytes)exceptsocket.errorase:ife.errno==errno.ECONNRESET:#表示链接退出data=Noneelse:raiseeifdata:#调用接收消息的回调函数self.onReceiveMsg(self,sock,IPs[sock],data)else:#当链接退出exits,removethemonitorQueuereaders.remove(sock)sock.close()#调用链接关闭回调函数self.onCloseConn(self,sock,IPs[sock])#处理错误链接forsockinerr:#移除监听队列readers.remove(sock)sock.close()#调用连接关闭回调函数self.onCloseConn(self,sock,IPs[sock])首先使用socket建立服务器连接,和原来的socket一样核心代码也不同的是链接设置为非阻塞,这样可以通过select同时监听多个链接,服务器不会阻塞。关于select,可以看这里【2】在主循环中,筛选出有消息的链接,判断是建立链接还是发送消息,调用不同的回调函数,最后处理异常事件处理现在通过回调函数,可以写业务,看中间的代码。这一段是建立链接时的处理:defonCreateConn(server,sock,ip):cid=f'{ip[0]}_{ip[1]}'clients[cid]={'cid':cid,'sock':sock,'name':None}sock.send("你已经连接到元界了,告诉我你的代号,输入格式是name:lily。".encode('utf-8'))首先计算clientTerminalid,即cid,clients是一个由ip和port组成的字典,以cid为key,存储cid,link,name建立链接后,向链接发送问候语,要求其设置自己的名字然后接收消息的回调函数比较复杂,主要是处理的情况比较多:defonReceiveMsg(server,sock,ip,data):cid=f'{ip[0]}_{ip[1]}'data=data.decode('utf-8')print(f"receiveddata:{data}")_from=clients[cid]ifdata.startswith('name:'):#Setname=data[5:].strip()ifnotname:sock.send(f"名字不能设置为空,否则别人找不到你。".encode('utf-8'))elifnotcheckname(name,cid):sock.send(f"这个名字{name}已经在使用中,请尝试另一个元界".encode('utf-8'))msg=f"新成员{name}加入了元界,我们聊聊他吧。".encode('utf-8')sendMsg(msg,_from)else:短袜。send(f"改名完成".encode('utf-8'))msg=f"{_from['name']}把名字改成{name},跟TA聊天".encode('utf-8')发送消息(毫秒g,_from)_from['name']=nameelif'@'indata:#私信targets=re.findall(r'@(.+?)',data)print(targets)msg=f"{_from['name']}:{data}".encode('utf-8')sendMsg(msg,_from,targets)else:#groupmessagemsg=f"{_from['name']}:{data}".encode('utf-8')sendMsg(msg,_from)代码分为两部分,第一部分if是对接收到的消息进行处理,将字节转化为字符串;if开始处理具体消息如果收到name:开头的消息,表示需要设置用户名,包括权重判断,向其他成员发送消息。如果收到的消息中有@,则表示您发送的是私信。先提取需要发送的用户,然后将消息发送给对应的用户。如果没有特殊标记表示群发。sendMsg用于发送消息和接收三个参数。第一个是消息,第二个是发送者,第三个是接收者名称数组。当链接关闭时,需要处理关闭的回调函数:defonCloseConn(server,sock,ip):cid=f'{ip[0]}_{ip[1]}'name=clients[cid]['name']ifname:msg=f"{name}frommetaDisappearedintheuniverse".encode('utf-8')sendMsg(msg,clients[cid])delclients[cid]当收到断链消息时,合成消息,发送给其他用户,然后从客户端缓存中取回删除客户端客户端需要解决两个问题。第一个是处理接收到的消息,第二个是允许用户输入。我们将以线程的形式接收消息,以主循环的形式接收用户输入。接收消息,先看接收消息的代码:defreceive(client):whileTrue:try:s_info=client.recv(1024)#接受服务端的消息并解码ifnots_info:print(f"{bcolors.WARNING}服务器链接断开{bcolors.ENDC}")breakprint(f"{bcolors.OKCYAN}newmessage:{bcolors.ENDC}\n",bcolors.OKGREEN+s_info.decode('utf-8')+bcolors.ENDC)exceptException:print(f"{bcolors.WARNING}服务器链接断开{bcolors.ENDC}")breakifclose:break这个是线程中用到的代码,接收一个客户端链接作为参数,不断从循环中的链接获取信息。如果没有消息recv方法将阻塞,直到有新消息到达。收到消息后,将消息写入控制台。bcolors提供了一些颜色标记来以不同的颜色显示消息。close是一个全局标记,如果客户端需要退出的时候,它会被设置为True,这样可以让线程结束输入处理。我们再看输入控制程序:whileTrue:passvalue=input("")value=value.strip()ifvalue==':start':ifthread:print(f"{bcolors.OKBLUE}你已经在元界{bcolors.ENDC}")else:client=createClient(ip,5000)thread=Thread(target=receive,args=(client,))thread.start()print(f"{bcolors.OKBLUE}你进入了虚拟世界{bcolors.ENDC}")elifvalue==':quit'orvalue==':stop':ifthread:client.close()close=Trueprint(f"{bcolors.OKBLUE}正在退出...{bcolors.ENDC}")thread.join()print(f"{bcolors.OKBLUE}元界退出{bcolors.ENDC}")thread=Noneifvalue==':quit':print(f"{bcolors.OKBLUE}退出程序{bcolors.ENDC}")breakpasselifvalue==':help':help()else:ifclient:#chatmodeclient.send(value.encode('utf-8'))else:print(f'{bcolors.警告}未连接元界,请输入:start访问{bcolors.ENDC}')client.close()主要是响应不同的命令,例如:start表示需要建立链接,:quit表示添加:在exit等命令之前,以区别于一般的消息。如果不带:,则认为发送消息后开始,完成整体编码。最终代码由三部分组成。第一部分是服务器端核心,代码存放在simpletcp.py中,第二部分是服务器端业务代码,存放在metaServer.py中,第三部分是客户端代码,存放在metaClient.py中。另外,一些辅助过程sing是必须的,比如发送消息的sendMsg方法,颜色处理方法等,具体可以下载本文源码。进入代码目录,启动命令行,执行pythonmetaServer.py,输入命令start:server然后打开命令行,执行pythonmetaClient.py,输入命令:start,就可以访问metaverse:client设置自己的名字:如果有新成员加入,会收到消息提醒,还可以玩一些互动:怎么样,一个元宇宙就这样形成了,赶紧让其他小伙伴一起加入吧。总结一下,Metaverse是现在很火的一个概念,但它仍然建立在现有技术的基础上。元宇宙为人们提供了生活在虚拟魔法世界中的想象空间。自从有了互联网,我们就逐渐生活在元宇宙中。今天我们使用基础的TCP技术搭建自己的元界聊天室。虽然功能和想象中的元界相去甚远,但主要的功能已经形成。如果大家有兴趣,可以在此基础上增加更多好玩的功能,比如好友、群组、消息记录等,让这个元宇宙变得更好玩的同时,也能获得更深入的了解。我希望你今天的元宇宙会激励你。欢迎在留言区写下你的想法和看法,比心!参考代码https://github.com/JustDoPython/python-examples/tree/master/taiyangxue/metaReferences[1]simpletcp:https://github.com/fschr/simpletcp[2]select:https://docs.python.org/zh-cn/3/library/select.html
