构建属于自己的云游戏服务器

查看 44|回复 2
作者:luozhiyun   
原文链接: https://www.luozhiyun.com/archives/846
最近沉迷于暗黑 4 第四赛季,所以就在倒腾,怎样才能随时随地玩到暗黑 4 ,掌机 steam deck 我试过了,太重并且性能很差,已经被我卖了,于是折腾起了云游戏。
先来看看我的折腾成果: https://www.bilibili.com/video/BV1Z93TeuEQ4/
其实效果我没想到有这么好,在远程串流的情况,可以 1080p 60hz 几乎无卡顿的玩暗黑 4 ,延迟只有 20ms 左右,配上我的手柄简直就是一个强大的掌机。
各种平台云游戏怎样了?
有了上面的需求之后,我就去试了以下几个平台:GeForce Now 、Xbox Game Cloud 、Start 云游戏、网易云游戏。
但是遗憾的是,几乎每个平台都有自己的问题。首先上面列举到的所有平台,IOS 都只有网页版,因为苹果不让上架。
Start 云游戏:我检查了一下,只有少数的几个单机游戏,大多是网游手游,并没有暗黑 4 ;
网易云游戏:无论是快速启动还是普通启动,都非常的慢,估计要 3 分钟左右才能启动。并且我充值了一下玩了一下,网络倒是很流畅,延迟只有 15ms 左右,但是非常的卡,并且经常进入游戏界面死机,估计是机器性能不行,说实话我有点心疼我充值的 10 块钱了。
所谓的高配,其实性能很低:

Xbox Game Cloud:微软自家的平台,好处是有了 XGPU 之后可以畅玩所有云游戏,最大的问题是服务器不在国内,所以连接延迟其实很高,并且常有波动,如果要玩 Xbox Game Cloud 那么还需要充值 XGPU 才行;
Xbox Game Cloud 强大的游戏阵容:

GeForce Now:这个云游戏平台可以说只能用无敌两个字来形容,每次登录都可以免费半个小时,即使服务器在海外,但是开了加速器也可以很稳定,画面有 720P ,在手机上玩还可以,主机性能也很好,经常分配到 2080 以上的机器。但是唯一不爽得是,免费用户每次都要排队挺长时间的,并且如果要付费,其实挺贵的,基本要 100 元每个月了。
GeForce Now 价格表:

开源解决方案
所以尝试了这么的云游戏发现都不好用之后,为什么不可以自己弄个呢?其实所谓的云游戏,无非是用客户端连接到主机端而已。那么我们实际上也可以把自己家里运行的 PC 或者主机,变成了由云游戏服务商提供的云上服务,这种行为就叫做串流游戏。
所以我们要做的是怎样把我们的私有网络的 PC 或者主机做成云服务提供给外网访问,让我们可以随时随地,只要有网就可以使用。

那么我的要求主要有这么几点:
[ol]
  • 要有跨平台的客户端,保证 mac 、iphone 、android 、win 都能用;
  • 延迟要足够低,支持的可配置项要足够多;
    [/ol]
    正好自己有台闲置的 4090 的机器,那我就可以用它来作为主机端,我的安卓机作为客户端进行串流云游戏。
    服务端 & 客户端
    目前服务端主要有以下几个实现方案:
    [ol]
  • N 卡 GeForce Experience
  • Sunshine
    [/ol]
    客户端主要有:Moonlight
    N 卡 GeForce Experience
    如果你使用 N 卡,并且是 GTX960 以上可以通过 GeForce Experience 进行串流。只需要打开 GeForce Experience 在设置里找到 SHELD 这个串流配置,并添加游戏或者应用程序即可。

    但是我现在不是很推荐这个方案,因为 NV 说过他们要把这个功能去掉,只是现在没有去掉而已。
    Sunshine
    它是一个开源推流方案 https://github.com/LizardByte/Sunshine , 属于通用串流方案,支持 Nvidia 、AMD 、Intel 。尤其适合核显串流(如果你用的是 P106 这类显卡没有视频编码器,只能使用 Sunshine 串流方案)。
    下载 Sunshine 后首先需要运行服务端安装脚本 install-service.bat ,然后再运行 sunshie 。sunshine 没有 UI 界面,设置需要通过网页端。运行 sunshine 后访问 https://127.0.0.1:47990 进行设置。设置里最重要的是进行 PIN 码配对,设备之间 PIN 匹配之后就可以进行串流了。

    Moonlight
    Moonlight 以方便的将 Windows 电脑画面传输到各主流操作系统的客户端软件上,甚至可以直接传输至谷歌浏览器。画面方面,移动端最高支持 4K120 帧,且支持 HDR (需要显卡支持),而桌面端甚至可以直接自定义分辨率和帧数;交互方面支持键鼠/手柄/触摸屏/触控板/触控笔,就像用自己的电脑一样使用远程电脑。该方案无广告,完全免费。
    手机端可以在各大商店下载,也可以去 Moonlight 官网地址: https://moonlight-stream.org 下载。如果使用 iphone 作为客户端,直接在 App store 下载 Moonlight 即可。
    保持主机和客户端在同一局域网内,打开客户端软件,应该能够看到主机的计算机名。点击会弹出 4 位 PIN 码,需要在 Sunshine 配置网页 https://localhost:47990/pin 中输入 PIN 码。建立连接后,点击桌面( DESKTOP )将启动桌面串流。
    网络配置好之后,在局域网内串流延迟通常相当的底,我经常躺床上用 pad 串流我书房的 pc 玩游戏,延迟只有几毫秒。
    远程串流
    由于 Geforce Experience 和 Sunshine 默认只在本地网络监听端口,客户端和主机位于同一局域网内才能连接成功,如果要真正实现远程连接,最简单稳定的方法是公网直连。
    独一无二的 IP 地址使得主机能够在互联网中被识别,但是由于 IPv4 地址匮乏,大多数家庭网络并不具备公网 IPv4 地址。
    所以我这里采用内网穿透的方式来构建我们的云服务:

    内网穿透的核心思想就是“映射”和“转发”,把私有网络的设备的端口映射到公网设备的端口上,来进行流量转发。思想其实很简单,由于内网设备没有 ip ,那么我们通过一台有公网 ip 的机器来代替把流量做一层转发。比如上图,
    我们在外网设置的用手机访问云服务器的 7000 端口,实际上云服务器会接收到之后通过 47900 进行转发到我们私有网络的 pc 机器,然后 pc 机器处理完之后再通过 46900 端口转发给云服务器,上面所提到的端口都是可以自定义的。
    那么对于做内网穿透一般现在流行两种做法:
    [ol]
  • 直接 p2p 点对点的进行传输,流行的方案有 zerotier ;
  • 基于服务器的流量转发,流行的方案有 frp ;
    [/ol]
    为什么会有内网穿透?
    其实在互联网的世界中,如果每个用户都有真实的 IP 情况下,那么我们可以通过源 IP+源端口+目标 IP+目标端口+协议类型很容易的找到对方,是根本不需要 P2P 的,因为本来任何对象都可以作为 Server 或者 Client 来提供服务,彼此之间是可以互联。
    但是 IP 和端口,是有限的,最初设计者也是没想到发展如此迅速,整个 IPv4 的地址范围,完全不够互联网设备来分配,那为了解决地址不够用的问题,就引入了 NAT 。

    NAT ( Net Address Translate ,网络地址转换)是一种 IP 复用的一种技术,将有限的 IP 扩展成无限,由于 IPv4 地址资源有限,而 NAT 将网络划分成了公有网络和私有网络,允许多个设备使用一个公共 IP 地址访问互联网。路由器会将内部网络中的私有 IP 地址转换为公共 IP 地址,从而节省了 IPv4 地址资源。
    所以我们在用 WIFI 的时候可以看到我们手机或 PC 上的 IP 地址通常是:192.168.x.xxx ,这其实就是由路由器分配的地址,并不是真的地址。
    另外,在 IPv4 地址资源越来越紧张的今天,很多电信运营商,已经不再为用户分配公网 IP ;而是直接在运营商自己的路由器上运营 NAT ,所以会出现甚至一整个小区共用一个 IP 出口的情况。
    通过 NAT 技术的公私网络隔离,可以实现 IP 复用,解决了 IPv4 不够用的问题,但是也同时带来了新问题,那就是直接导致通信困难,由于 NAT 导致 IP 成为虚拟 IP ,外网无法针对内网某台主机进行直连通信,因为没有真实地址可用。
    所以为了将 NAT 设备内外通信打通,就有了内网穿透技术。
    zerotier
    zerotier 是一个开源的内网穿透软件 https://github.com/zerotier/ZeroTierOne ,有社区版本和商业版本,唯一的区别是社区版本有 25 台连接数量的限制,但对普通用户足够了,用它可以虚拟出一组网络,让节点之间的连接就像是在局域网内连接一样。
    zerotier 底层是通过一个加密的 p2p 网络来实现连接。由于节点之间通常存在 NAT 隔离,无法直接通信,所以 zerotier 存在一个根服务器来帮助通路建设,所谓通路建设俗称打洞( hole punching ),也就是穿透 NAT 隔离实现两个节点的连接。打洞也是区分 UDP 和 TCP 的,由于 zerotier 用的是 UDP ,所以这里以 UDP 讲解打洞原理。
    假设 clientA 想要直接与 clientB 建立 UDP 会话,用 S 表示根服务器:
    A 最初不知道如何到达 B ,因此 A 请求 S 帮助与 B 建立 UDP 会话,S 会记录下他们各自的内外网 IP 端口:

    打洞中:
    S 用包含 B 的内外网 IP 端口的消息回复 A 。同时,S 使用其与 B 的 UDP 会话发送 B 包含 A 的内外网 IP 端口的连接请求消息。一旦收到这些消息,A 和 B 就知道彼此的内外网 IP 端口;
    当 A 从 S 接收到 B 内外网 IP 端口信息后,A 始向这两个端点发送 UDP 数据包,并且 A 会自动锁定第一个给出响应的 B 的 IP 和端口;
    B 开始向 A 的内外网地址二元组发送 UDP 数据包,并且 B 会自动锁定第一个给出相应的 A 的 IP 和端口;

    打洞后:
    A 和 B 直接利用内网地址通信

    zerotier 的根服务实际上是部署在海外的,如果我们直接使用,很可能连不上,并且延迟基本在 200ms 以上,我们可以通过 zerotier-cli listpeers 查看根服务器:
    # ./zerotier-cli listpeers
    200 listpeers     
    200 listpeers 62f865ae71 50.7.252.138/9993;24574;69283 341 - PLANET
    200 listpeers 778cde7190 103.195.103.66/9993;24574;69408 213 - PLANET  
    200 listpeers cafe9efeb9 104.194.8.134/9993;4552;69462 159 - PLANET
    上面的 PLANET 节点就是是 ZeroTier 网络中的根服务器。它们负责在对等点之间中继初始流量,帮助对等点建立对等连接,并充当身份和相关公钥的缓存。
    我们随便 ping 一下它的延迟:
    # ping 50.7.252.138
    PING 50.7.252.138 (50.7.252.138) 56(84) bytes of data.
    64 bytes from 50.7.252.138: icmp_seq=1 ttl=46 time=347 ms
    64 bytes from 50.7.252.138: icmp_seq=2 ttl=46 time=347 ms
    64 bytes from 50.7.252.138: icmp_seq=3 ttl=46 time=354 ms
    这样的游戏明显是玩不了云游戏的,zerotier 也考虑到这种延迟的情况,所以可以让有需要的用户自建 MOON 服务器。
    ZeroTier 中的 MOON 节点是用户定义的根服务器,可以添加到 ZeroTier 网络中。它的行为类似于 ZeroTier 的默认根服务器(称为 PLANET 节点),但由用户控制,我们可以把 MOON 服务器部署在离自己更近的地方,比如我就部署在广州,可这样以通过提供更近或更快的根服务器来提高网络性能。
    怎样部署我这里就不贴教程了,可以自己去 search 一下,很简单。 但是现实场景中的网络要复杂的多,远远不是部署一个 MOON 节点就可以解决延迟的问题。通常我们的网络会涉及到防火墙限制、运营商级 NAT 、路由器兼容问题,还有就是 ZeroTier 走的是 UDP, 在国内的网络环境下一些运营商会对 UDP 流量实施 QoS(服务质量)策略,,丢包可能会比较严重。
    所以总之 ZeroTier 这条路并不是这么好走,看起来 p2p 直连貌似可以很美好,理论上可以不受根服务器的影响,两端直连跑满所有带宽,但实际上当不能打洞成功的时候那么就会退化成根服务器转发,那么实际的速率就取决于你自建的 MOON 节点的转发带宽了。
    并且还有一个问题是,ZeroTier 是需要客户端的,到目前为止移动端的 app 是不支持添加自建 MOON 节点信息的,也就是说只能在电脑上进行串流,这实用性还是下降了不少。
    所以总结一下优缺点:
    优点:
    [ol]
  • 组网非常方便,可以像局域网一样连接 ZeroTier 组网内的节点;
  • 连接以及数据传输都是加密,所以比较安全;
    [/ol]
    缺点:
    [ol]
  • 根服务器在海外,需要自建 MOON ,否则延迟很高;
  • 依赖服务端,并且移动端 app 功能不完善;
  • 受制于网络环境,p2p 打洞成功率低;
    [/ol]
    frp
    frp 也是一个开源软件 https://github.com/fatedier/frp ,实际上它没有这么多花哨的功能,就是帮我们做了一个流量的转发。它的客户端连接不需要 app ,所以用来串流的话直接用 moonlight 直接连接 frp 远程转发服务器即可,可以说很方便了。
    它的架构如下:

    在安装 frp 远程转发服务的时候,我这里给一下配置,因为现在网上找的教程都是老的 ini 配置,现在新版本用的是 toml 配置。
    服务端的配置:
    #frps 服务监听的本机端口
    bindPort = 9200
    bindAddr = "0.0.0.0"
    # frpc 客户端连接鉴权 token ,默认为 token 模式
    auth.token="xxxx"
    #日志打印配置
    log.to = "./log"
    log.level = "debug"
    log.maxDays = 7
    allowPorts = [
            // 远程连接需要用的端口
      { start = 47000, end = 48010 }
    ]
    sunshine 主要连接的端口是这几个:
    TCP 47984, 47989, 48010
    UDP 47998, 47999, 48000, 48002, 48010
    所以我们需要给这几个端口都加上防火墙,服务器和 pc 都要开放相应的端口,在测试的时候可以先全打开,测试完了再挨个加上,免得莫名其妙的问题。
    pc 端的配置:
    #token 需要与服务端的 token 一致
    auth.token = "xxxxx"
    # 服务端的公网 ip
    serverAddr = "1xx.xxx.xx.xx"
    # 服务端的监听端口
    serverPort = xxx
    [[proxies]]
    name = "47984"
    type = "tcp"
    localIP = "127.0.0.1"
    localPort = 47984
    remotePort = 47984
    [[proxies]]
    name = "47989"
    type = "tcp"
    localIP = "127.0.0.1"
    localPort = 47989
    remotePort = 47989
    [[proxies]]
    name = "47990"
    type = "tcp"
    localIP = "127.0.0.1"
    localPort = 47990
    remotePort = 47990
    [[proxies]]
    name = "48010"
    type = "tcp"
    localIP = "127.0.0.1"
    localPort = 48010
    remotePort = 48010
    [[proxies]]
    name = "47998"
    type = "udp"
    localIP = "127.0.0.1"
    localPort = 47998
    remotePort = 47998
    [[proxies]]
    name = "47999"
    type = "udp"
    localIP = "127.0.0.1"
    localPort = 47999
    remotePort = 47999
    [[proxies]]
    name = "48000"
    type = "udp"
    localIP = "127.0.0.1"
    localPort = 48000
    remotePort = 48000
    [[proxies]]
    name = "48002"
    type = "udp"
    localIP = "127.0.0.1"
    localPort = 48002
    remotePort = 48002
    [[proxies]]
    name = "48010"
    type = "udp"
    localIP = "127.0.0.1"
    localPort = 48010
    remotePort = 48010

    pc 端 frp 自动启动
    windows 系统开机自启比较麻烦,不像 linux 简单,所以为了保证 windows 后台运行 frpc ,创建脚本 frpc.vbs ,将以下内容粘贴进去:
    set ws=WScript.CreateObject(“WScript.Shell”)
    ws.Run “[frpc 执行文件] -c [frpc 配置]”,0
    注意可能需要修改路径(默认路径是放 C 盘目录下)
    将 frpc.vbs 放入 C:\ProgramData\Microsoft\Windows\Start Menu\Programs\StartUp 目录内,即可实现开机自启动。
    远程唤醒(Wake On LAN)pc
    家里的电脑如果经常开机的话很费电,所以按需开机是最佳办法,那么就需要远程登陆开机。远程唤醒需要主板的支持,现在的主板基本都支持。
    首先我们要进入到主板的 BIOS 设置选项里面把 WOL 功能打开,具体方法视厂商而定,可以参考的关键词包括:
  • Automatic Power On
  • Wake on LAN/WLAN
  • Power Management
  • Power On by Onboard LAN
  • Power On by PCI-E Devices

    然后在我们被唤醒的电脑里面找到网卡设置:


    然后我们可以在内网尝试一下,是否可以唤醒成功,在应用市场随便找个 WOL 软件,填上内网被唤醒机器的 IP 地址和 MAC 地址即可:

    外网唤醒,我们需要一个中间设备来中转我们的流量,因为我们需要被唤醒的机器已经被休眠了,是无法接收到请求的,所以我这里内网用我的软路由进行转发:
    首先我们要做的就是 DHCP固定住自己内网 PC 的内网 IP ,要不然无法转发唤醒,通常可以在路由器里面设置:

    然后我是通过 OpenWrt 来和我远端服务器建立好 frp 通信,监听转发端口,到时候外面的请求会先到 OpenWrt ,然后由它再转发给我的内网 PC:

    最后如果觉得麻烦,其实可以用远程物理按键解决,一劳永逸:

    隐私屏 / 作为副屏
    在用 sunlight 串流的时候由于显示的是桌面的,,因为串流软件会捕捉屏幕上的内容并编码成视频流。如果关闭屏幕,编码器将无法获取到需要的画面信息,导致串流中断。
    那么如果我们想要关闭屏幕串流,那么可以用这个工具 https://github.com/VergilGao/vddswitcher ,通过 vdd 创建一个虚拟屏幕可以实现即使主屏关闭也能串流。
    最后
    游戏串流最后不仅满足了我在外网想要随时随地玩游戏的想法,并且还拯救了我的腰椎,在家里玩游戏现在基本是用平板串流到我的电脑上面,然后买个支架夹着我的平板,然后躺着玩,但愿各位游戏佬都能找到属于自己的游戏环境。

    Reference
    https://github.com/VergilGao/vddswitcher
    https://github.com/LizardByte/Sunshine
    https://github.com/moonlight-stream/moonlight-qt
    https://keenjin.github.io/2021/04/p2p/
    https://sspai.com/post/68037
    https://bford.info/pub/net/p2pnat/
  • qhd1988   
    非局域网访问的话,最终的瓶颈就是家里宽带的上行速度,
    所以不建议折腾,除非家里宽带上行速度很不错
    thetbw   
    要是能用那种平台,可以管理多个游戏,然后通过网页玩就好了,就像自部署的网易云游戏
    您需要登录后才可以回帖 登录 | 立即注册

    返回顶部