目的:远程服务器或者docker运行GUI程序,可以不使用VNC等工具
MAC电脑安装XQuartz:
官网:https://www.xquartz.org/
window电脑安装:MobaXterm
官网:https://mobaxterm.mobatek.net/download-home-edition.html
通过SSH链接docker
- 启动docker容器
docker run -it -d -p local:docker 镜像 /bin/sh
- 登陆docker内
docker exec -it 容器名称 /bin/sh
apt update
apt install openssh-server
apt install vim
passwd // 给root账号赋予密码
vim /etc/ssh/sshd_config
// 修改两个位置
/***
第一:把ssh 服务默认的22端口设置为与容器服务的端口一致,设置为启动过程中:dockerip,
因为我们运行容器的时候挂载的是内部的docker端口映射到宿主机的local端口,
所以需要和容器内部端口保持一致,
第二:将PermitRootLogin prohibit-password修改为PermitRootLogin yes,开启使用密码登录,设置完成后保存退出
第三:确定下X11Forwarding yes 是否为yes
*/
service ssh restart // 重启ssh服务
- 链接docker测试
ssh -p docker端口 root@远程IP
-------------------------------现在SSH链接正常,下一步进行X11服务图形显示----------------
- docker 启动参数
FOLDER=/path/to/your/data/on/host // 挂载的本地机器的目录,和docker的共享目录
docker run -it -e DISPLAY=host.docker.internal:0 -e "QT_X11_NO_MITSHM=1" \
-v "/tmp/.X11-unix:/tmp/.X11-unix:rw" \
-v "$FOLDER:/data" 容器名称
- 执行顺序
1. XQuartz -> 偏好设置 -> 安全性 -> 勾选“允许从网络客户端连接” -> 退出程序;
2. 终端键入 xhost + ip(注意两者之间的空格)重新启动 XQuartz;为远程主机添加权限
或者xhost +为所有IP添加权限
3. ssh -XY root@0.0.0.0 -p 6100
4. netstat -an | grep -F 6100 查看
5. apt install x11-apps
6. 在 run 或 exec 容器时加入-e DISPLAY=host.docker.internal:0参数,比如我这里通过对一个现有的,已经安装过 xarclock 时钟小程序的容器 toyOS 执行docker exec -ite DISPLAY=host.docker.internal:0 toyOS /usr/bin/xarclock,就会在我的本地出现一个小时钟的GUI程序;
参数解释
如何让 Docker 中的 X Client 与宿主机的 X Server 实现交互
作为 X Client 的程序如果想与 X Server 进行交互,大致分为两种方式:
- 在命令后加
--display
参数并指明相关的位置 - 用户提前设置好环境变量
DISPLAY
,程序从该变量获得相关信息
这里我们采用第二种方式,故在启动容器时通过-e
参数为其设置 DISPLAY
变量,现在的问题在于,如何解释变量的值 host.docker.internal:0
呢?
对于该变量中,冒号前面的部分,Docker 官方文档中有如下解释:
The host has a changing IP address (or none if you have no network
access). From 18.03 onwards our recommendation is to connect to the
special DNS namehost.docker.internal
, which resolves to the internal
IP address used by the host.
也就是说,这个值本质上是获得了宿主机的内部IP,为了验证这一点,可以通过ifconfig
命令来查看宿主机实际的IP,并将 DISPLAY
的值换成 your_ip:0
,可以发现和前面一样可以运行。之所以本次实验采用了前者,是因为要获取实际IP,第一是过程很麻烦,第二是设备要处于联网的状态下,而在文档的描述中可以看到 (or none if you have no network access)
这句话,也就是说,这种参数设置在无网络的条件下也可以正常运行。
那么 DISPLAY
的值就可以被解释为 your_ip:0
了,关于这个格式,其实它的完整形式为your_ip: display_number. screen_number
,在本实验中其实可以写为 host.docker.internal:0.0
,display_number
和 screen_number
均从0开始计数,前者表示一个输入流的标号(输入流包括显示器,键盘,鼠标等),后者表示输入流中某个具体的显示屏,因为很少有人使用多屏幕,所以 screen_number
多数情况下均为0,也就可以省略掉了。
而对于 display_number
,X11 protocol 官方文档中有如下描述:
For TCP connections, displays on a given host are numbered starting
from 0, and the server for display N listens and accepts connections
on port 6000 + N.
也就是说,这个值实际上取决于宿主机上 X11 服务占用的端口,用端口号减掉6000即可,这就是上述命令中冒号后面的0的具体含义。为了验证这一点,可以使用 socat
工具运行 socat tcp-listen:6100,reuseaddr,fork tcp:localhost:6000
命令,将6100端口的消息转交给6000端口,这样按照上面的描述,DISPLAY
变量的值就可以为 host.docker.internal:100
,替换后执行完整命令,可以发现一样能运行GUI测试程序。
注:如果最终仍然无法显示,可以在docker内部执行,设置环境变量指定显示端口:
如果启动命令为:
docker run -it -d -p 6100:6100 -e DISPLAY=host.docker.internal:0 -e "QT_X11_NO_MITSHM=1" \
-v "/tmp/.X11-unix:/tmp/.X11-unix:rw" \
-v "$FOLDER:/data" kalibr /bin/sh
export DISPLAY=host.docker.internal:0