几行代码,撸了个 元宇宙?!

均衡侠
• 阅读 495

今天我就用 Python 实现一个简单的迷你元宇宙。

代码简洁易懂,不仅可以学习 Python 知识,还能用实践理解元宇宙的概念。

还等什么,现在就开始吧!

迷你元宇宙

什么是元宇宙?

不同的人有不同的理解和认识,最能达成共识的是:

元宇宙是个接入点,每个人都可以成为其中的一个元素,彼此互动。

那么我们的元宇宙有哪些功能呢?

首先必须有可以接入的功能。

然后彼此之间可以交流信息。比如 a 发消息给 b,b 可以发消息给 a,同时可以将消息广播出去,也就是成员之间,可以私信 和 群聊。

另外,在元宇宙的成员可以收到元宇宙的动态,比如新人加入,或者有人离开等,如果玩腻了,可以离开元宇宙。

最终的效果像这样:

几行代码,撸了个 元宇宙?!

效果

设计

如何构建接入点

直接思考可能比较困难,换个角度想,接入点其实就是 —— 服务器。

只要是上网,每时每刻,我们都是同服务器打交的。

那就选择最简单的 TCP 服务器,TCP 服务器的核心是维护套接字(socket)的状态,向其中发送或者获取信息。

python 的 socket 库,提供了很多有关便捷方法,可以帮助我们构建。

核心代码如下:

import socket

socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket.bind((ip, port))
socket.listen()

data = socket.recv(1024)
...

创建一个 socket,让其监听机器所拥有的一个 ip 和 端口,然后从 socket 中读取发送过来的数据。

如何构建客户端

客户端是为了方便用户链接到元宇宙的工具,这里,就是能链接到服务器的工具,服务器是 TCP 服务器,客户端自然需要用可以链接 TCP 服务器的方式。

python 也已为我们备好,几行代码就可以搞定,核心代码如下:

import socket

client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
client.connect((ip, port))

data = client.recv(1024)
...

代码与服务器很像,不过去链接一个服务器的 ip 和 端口

如何构建业务逻辑

首先需要让服务器将接入的用户管理起来。

然后当接收到用户消息时做出判断,是转发给其他用户,广播还是做出回应。

这样就需要构造一种消息格式,用来表示用户消息的类型或者目的。

我们就用 @username 的格式来区分,消息发给特殊用户还是群发。

另外,为了完成注册功能,需要再定义一种命令格式,用于设置 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)

# 存放已建立的链接
readers = [self._socket]
# 存放客户端 ip和端口
IPs = dict()

# 退出标记 用于关闭服务器
self._stop = False

# 服务器主循环
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()
            
            # 将链接设置为非阻塞的
            client_socket.setblocking(0)
            # 添加到监听队列
            readers.append(client_socket)
            # 存储ip信息
            IPs[client_socket] = client_ip

            # 调用建立链接回调函数
            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:
                # 调用收到消息回调函数
                self.onReceiveMsg(self, sock, IPs[sock], data)
            else:
                # 链接退出时,移除监听队列
                readers.remove(sock)
                sock.close()

                # 调用链接关闭回调函数
                self.onCloseConn(self, sock, IPs[sock])         
    # 处理存在错误的链接
    for sock in err:
        # 移除监听队列
        readers.remove(sock)
        sock.close()

        # 调用链接关闭回调函数
        self.onCloseConn(self, sock, IPs[sock])
  • 首先利用 socket 建立一个服务器链接,这个和最初的 socket 核心代码一样
  • 不同的是设置链接为非阻塞的,这样就可以通过 select 同时监控多个链接,也不至于阻塞服务器了。关于 select 可以看这里[2]
  • 在主循环中,筛选出有了消息的链接,判断是建立链接还是消息发送,调用不同的回调函数
  • 最后处理一下异常

事件处理

现在通过回调函数,就可以编写业务了,之间看代码。

这段是建立链接时的处理:


def onCreateConn(server, sock, ip):
    cid = f'{ip[0]}_{ip[1]}'
    clients[cid] = {'cid': cid, 'sock': sock, 'name': None}
    sock.send("你已经接入元宇宙,告诉我你的代号,输入格式为 name:lily.".encode('utf-8'))
  • 首先计算出客户端 id,即 cid,通过 ip 和 端口 组成
  • clients 是个词典,用 cid 为 key,存储了 cid、链接、和名称
  • 一旦建立起来链接,向链接发送一段问候语,并要求其设置自己的名称

然后是接收消息的回调函数,这个相对复杂一些,主要是处理的情况更多:

def onReceiveMsg(server, sock, ip, data):
    cid = f'{ip[0]}_{ip[1]}'
    data = data.decode('utf-8')
    print(f"收到数据: {data}")
    _from = clients[cid]
    if data.startswith('name:'):
        # 设置名称
        name = data[5:].strip()
        if not name:
            sock.send(f"不能设置空名称,否则其他人找不见你".encode('utf-8'))
        elif not checkname(name, cid):
            sock.send(f"这个名字{name}已经被使用,请换一个试试".encode('utf-8'))
        else:
            if not _from['name']:
                sock.send(f"{name} 很高兴见到你,现在可以畅游元宇宙了".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)
  • 代码分为两大部分,if 前面是处理收到的消息,将 bytes 转化为 字符串;if 开始处理具体的消息
  • 如果收到 name: 开头的消息,表示需要设置用户名,其中包括判重,以及给其他成员发送消息
  • 如果收到的消息里有 @,表示在发私信,先提取出需要发出的用户们,然后将消息发送给对应的用户
  • 如果没有特殊标记,就表示群发
  • 其中 sendMsg 用于发送消息,接收三个参数,第一个是消息,第二是发送者,第三个是接收者名称数组

当链接关闭时,需要处理一下关闭的回调函数:

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]
  • 当收到链接断开的消息时,合成消息,发送给其他用户
  • 然后从客户端缓存中删除

客户端

客户端需要解决两个问题,第一个是处理接收到的消息,第二个是允许用户的输入。

我们将接收消息作为一个线程,将用户输入作为主循环。

接收消息

先看接收消息的代码:

def receive(client):
    while True:
        try:
            s_info = client.recv(1024)  # 接受服务端的消息并解码
            if not s_info:
                print(f"{bcolors.WARNING}服务器链接断开{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}服务器链接断开{bcolors.ENDC}")
            break
        if close:
            break
  • 这是线程中用的代码,接收一个客户端链接作为参数
  • 在循环中不断地从链接中获取信息,如果没有消息时 recv 方法会阻塞,直到有新的消息过来
  • 收到消息后,将消息写出到控制台上
  • bcolors 提供了一些颜色标记,将消息显示为不同的颜色
  • close 是一个全局标记,如果客户端需要退出时,会设置为 True,可以让线程结束

输入处理

下面再看一下输入控制程序:

while True:
    pass
    value = input("")
    value = value.strip()
    
    if value == ':start':
        if thread:
            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}")
    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()
  • 主要是对不同的命令做出的相应,比如 :start 表示需要建立链接,:quit 表示退出等
  • 命令前加 : 是为了和一般的消息做区分,如果不带 : 就认为是在发送消息

启动

完成了整体编码之后,就可以启动了,最终的代码由三部分组成。

第一部分是服务器端核心代码,存放在 simpletcp.py 中。

第二部分是服务器端业务代码,存放在 metaServer.py 中。

第三部分是客户端代码,存放在 metaClient.py 中。

另外需要一些辅助的处理,比如发送消息的 sendMsg 方法,颜色处理方法等,具体可以下载本文源码了解。

进入代码目录,启动命令行,执行 python metaServer.py,输入指令 start:

几行代码,撸了个 元宇宙?!

server

然后再打开一个命令行,执行 python metaClient.py,输入指令 :start,就可以接入到元宇宙:

几行代码,撸了个 元宇宙?!

client

设置自己的名字:几行代码,撸了个 元宇宙?!

如果有新的成员加入时,就会得到消息提醒, 还可以玩点互动:几行代码,撸了个 元宇宙?!

怎么样好玩吧,一个元宇宙就这样形成了,赶紧让其他伙伴加入试试吧。

总结

元宇宙现在是个很热的概念,但还是基于现有的技术打造的,元宇宙给人们提供了一个生活在虚拟的神奇世界里的想象空间,其实自从有了互联网,我们就已经逐步生活在元宇宙之中了。

今天我们用基础的 TCP 技术,构建了一个自己的元宇宙聊天室,虽然功能上和想象中的元宇宙相去甚远,不过其中的主要功能已经成形了。

如果有兴趣还可以在这个基础上加入更好玩的功能,比如好友,群组,消息记录等等,在深入了解的同时,让这个元宇宙更好玩。

以上就是本次分享的所有内容,如果你觉得文章还不错,欢迎关注公众号:Python编程学习圈,每日干货分享,发送“J”还可领取大量学习资料。或是前往编程学习网,了解更多编程技术知识。

点赞
收藏
评论区
推荐文章
美凌格栋栋酱 美凌格栋栋酱
6个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
元宇宙成为物联网新风口
2021年是元宇宙被业界认可的第一年。很多行业人士告诉记者,元宇宙的逻辑与物联网息息相关,是物联网的一大出口,物联网行业要把握好元宇宙的话语权。曾伯钧告诉记者,物联网不仅是元宇宙的发展基础,也是元宇宙的发展愿景。“2022年,物联网将带来三大技术战场,包括要求环境可持续性和脱碳生产的绿色物联网,聚焦与万物相连的空间物联网,以及聚焦数据计算、镜像现实的元宇宙物
什么是元宇宙?元宇宙可以用在哪些行业?华锐互动
元宇宙是利用科技手段进行链接与创造的,与现实世界映射与交互的虚拟世界,具备新型社会体系的数字生活空间。随着元宇宙的爆火,它可以被应用在哪些行业领域呢?小编给大家捋一捋:场景一:虚拟厨房空间虚拟的锅子,冰箱,燃气灶,菜肴,调味剂就能创造虚拟的线上厨房空间,装修方面用户可以根据自己的喜好来自定,场景自己创造,完全实现数字化虚拟。通过脑机接口,MR(混合现实)接口
元宇宙虚拟世界未来趋势大好,广州华锐互动助力抢占元宇宙市场
元宇宙(https://www.xrnew3d.com/)作为数字虚拟世界和物理现实世界深度融合交互的重要平台,集成了大数据、区块链、物联网、人工智能、交互体验等一系列新技术。今年以来,元宇宙迅速成为数字经济时代的一个新热点。对于元宇宙这个概念,
元宇宙沉浸式互动展示系统开发定制平台
2021年公认是元宇宙(https://www.xrnew3d.com/yyz.html)元年,今年元宇宙的发展更是得到了社会各界的广泛关注,元宇宙首次被写入地方“十四五”规划,各大数字科技巨头、政府机构也都纷纷入局元宇宙。随着元宇宙的变现能力
元宇宙到底是啥?揭秘元宇宙的7个基本要素
源自:人人都是产品经理自从“元宇宙”一词出现以来,关于它的讨论就没有停止过,但元宇宙到底是什么?元宇宙和另一个虚拟世界(virtualworld)之间的界限又在哪里?本文作者提出了七个实现“真正的”元宇宙所需要的基本要素,为评估早期的元宇宙提
元宇宙华锐 元宇宙华锐
4个月前
元宇宙为企业带来的机遇
客户服务方面,元宇宙同样具有巨大的潜力。企业可以在元宇宙中创建虚拟客服,为用户提供更加便捷、高效的服务。虚拟客服可以通过自然语言处理技术与用户进行实时交流,解答用户的问题,提供个性化的服务。此外,企业还可以利用元宇宙技术为用户提供虚拟体验,例如虚拟试穿、虚
元宇宙华锐 元宇宙华锐
4个月前
元宇宙:企业驶向未来的新航道
元宇宙并非一个全新的概念,其起源可以追溯到上世纪的科幻作品。1992年,美国科幻大师尼尔・斯蒂芬森在小说《雪崩》中首次提出“元宇宙”的概念,描绘了一个人们通过虚拟化身在虚拟世界中生活、社交、工作的场景。此后,元宇宙的概念在科幻电影、游戏等作品中不断出现,如
元宇宙华锐 元宇宙华锐
2个月前
北京华锐视点邀您参与2025数字显示与元宇宙博览会【5月10-12日】
2025年5月1012日,备受瞩目的2025鸿威・世界数字显示与元宇宙生态博览会将在广州广交会展馆盛大举行。这不仅是一场行业的盛会,更是一次对未来科技生活的提前预演。北京华锐视点邀您共赴此次元宇宙盛宴,感受科技生活的魅力,元宇宙的迅速崛起带来的便捷。在这场
元宇宙将影响人类的生产和生活方式|广州华锐互动
在元宇宙开发过程中,企业、创作者、普通用户、投资者和整个社会都会不同程度地参与元宇宙开发建设。  作为技术开发人员的企业,以开放的态度研究元宇宙平台的相关技术,技术的发展永远不会等待后来者。目前,许多互联网公司都对元宇宙开发开始布局。在元宇宙快速发展起来的时候,参与制定标准可以在元宇宙开发中拥有更大的发言权。从长远来看,元宇宙发展所需的超大计算能力和交互技术