linux环境下Java程序UI显示问题
问题描述
项目在linux环境下运行时,因为历史遗留原因,需要通过UI界面展示一个二维码供扫码使用。最常见的就是和UI相关的各种错误了,比如下面这个:
Can't connect to X11 window server using ':2.0' as the value of the DISPLAY variable.
下面我们通过一个简单的示例来探究一下linux下和UI相关各种错误及其原因。
简单的样例程序
package my;
public class Test{
public static void main(String[] args){
System.out.println("java.awt.headless=\t" + System.getProperty("java.awt.headless"));
System.out.println("DISPLAY=\t" + System.getenv("DISPLAY"));
JOptionPane.showMessageDialog(null, "Hello!");
}
}
可以使用你熟悉的任意工具将上面代码编译、并打包为一个JAR包,假设文件名为test.jar
将test.jar上传到linux服务器特定目录下,假设存放到/opt目录;
通过SSH客户端登录linux
运行程序:$ java -cp test.jar my.Test
系统输出如下:
java.awt.headless= null
DISPLAY= null
Exception in thread "main" java.awt.HeadlessException:
No X11 DISPLAY variable was set, but this program performed an operation which requires it.
at java.awt.GraphicsEnvironment.checkHeadless(GraphicsEnvironment.java:204)
at java.awt.Window.<init>(Window.java:536)
我们看到,通过SSH登录linux系统时,当前会话缺省是没有设置DISPLAY环境变量的;而Java应用程序要展示界面需要DISPLAY环境变量指向可用的显示环境(即X11Server地址)
headless属性的作用
如果我们运行前,将java.awt.headless系统属性设置为true,结果是什么呢?
运行程序:$ java -Djava.awt.headless=true -cp test.jar my.Test
系统输出如下:
java.awt.headless= true
DISPLAY= null
Exception in thread "main" java.awt.HeadlessException
at java.awt.GraphicsEnvironment.checkHeadless(GraphicsEnvironment.java:204)
at java.awt.Window.<init>(Window.java:536)
设置headless并不能解决UI显示问题;它更像是一种提示,告知当前系统无法使用图形环境;
启动第三方XServer服务
我们尝试在Windows环境下启动XServer服务:windows下部分linux远程工具通过内嵌XServer服务支持访问linux图形界面,比如:XShell、MobaXTerm、XMing、X-Win32等;
我们这里使用一个免费的更纯粹的XServer windows服务程序:VcXsrv
下载地址
启动VcXsrv以后(注意放开访问权限,配置时选择:disable access control
),windows状态栏会多出一个VcXsrv的任务栏图标,鼠标悬停在上面,会提示当前XServer的访问地址,比如xxx:0.0
其中,冒号前是主机地址,域名或ip;冒号后的第一个数字代表XServer监听端口,这个数字 + 6000 即是真正的监听端口;
在windows命令行执行C:\> netstat -ano | find "600"
,显示如下;
TCP 0.0.0.0:6000 0.0.0.0:0 LISTENING 32472
TCP 127.0.0.1:6000 127.0.0.1:53790 ESTABLISHED 32472
TCP 127.0.0.1:6000 127.0.0.1:53791 ESTABLISHED 32472
TCP 127.0.0.1:6000 127.0.0.1:53792 ESTABLISHED 32472
TCP 127.0.0.1:53790 127.0.0.1:6000 ESTABLISHED 32472
TCP 127.0.0.1:53791 127.0.0.1:6000 ESTABLISHED 32472
TCP 127.0.0.1:53792 127.0.0.1:6000 ESTABLISHED 32472
TCP [::]:6000 [::]:0 LISTENING 32472
我们看到本地监听的TCP端口 6000 ,这正是XServer进程打开的;如果我们在启动VcXsrv时,端口设定为 -1
,则表示由VcXsrv,从6000开始探测第一个可用的端口;
DISPLAY环境变量指向远程XServer地址
我们尝试在运行java程序前,指定DISPLAY环境变量如下:
# 10.1.27.33替换为windows主机地址,:0替换为Xserver监听端口减去6000的值
$ export DISPLAY=10.1.27.33:0
$ java -cp test.jar my.Test
运行程序,这时候我们发现没有错误了!只是我们的界面在哪儿呢?别慌!看下windows任务栏,你将在当前windows桌面上发现linux进程的图形界面输出。
和远程桌面技术显示远程图形界面不同(传递压缩的位图);XServer和XClient(也就是需要输出图形界面的linux进程)之间通过X协议交谈(传递绘图指令),所以传输数据量更小,对网络带宽的需求低。
DISPLAY环境变量指向本地XServer地址
首先我们启动并登录一个安装了图形界面的linux服务器。使系统为我们准备好本地图形环境。
然后,远程使用SSH,远程登录服务器。使用$ who
命令来查看,当前可用的图形环境
test :0 2022-04-14 09:47 (:0)
root pts/0 2022-04-14 09:47 (10.1.27.33)
注意,冒号前为空,表示linux本地的图形环境。我们设置当前SSH连接的DISPLAY属性指向linux本地图形环境。
$ export DISPLAY=":0"
使用$ xhost
命令可查看Xserver的授权。
access control enabled, only authorized clients can connect
SI:localuser:root
SI:localuser:test
我们开放所有用户可连接:$ xhost +
access control disabled, clients can connect from any host
接下来,重新运行java程序:
$ export DISPLAY=":0"
$ xhost +
$ cd /opt
$ java -cp test.jar my.Test
这时候,观察linux本地登录用户的桌面,发现通过SSH运行的Java程序的界面被投射到linux本地用户桌面了。
无法连接DISPLAY的错误
如果DISPLAY设定值和当前可用的图形环境不一致,那么就会发生类似下面的错误。
比如,连接后使用su username
切换用户身份时,由于缺省情况下XServer仅授权给初始登录用户,
切换用户身份后,会导致新用户的DISPLAY
环境变量失效(指向的图形环境缺少权限)。
另外,项目在使用VNC登录时也偶发下面错误
Can't connect to X11 window server using ':2.0' as the value of the DISPLAY variable.
使用SSH转发X11协议数据
在某些场景下,如果linux服务器仅开放本地的少量TCP端口(比如,开放SSH的22端口),无法直接连接外部XServer服务器的话,
可以通过SSH代理方式,将X11协议通过SSH进行转发。
这时候,我们需要打开linux服务器端SSHD服务的X协议转发功能;修改:/etc/ssh/sshd_config
文件,打开X11协议转发开关。
X11Forwarding yes
在远程的SSH客户端连接中打开X11协议转发支持。以putty为例,打开putty配置:
Connection
SSH
X11
- 选中 Enable X11 forwading
- 填写 在X DISPLAY location中填写本地XServer地址,比如:localhost:0.0
这时,我们使用putty打开SSH连接,查看当前连接的DISPLAY
的设置:
$ echo $DISPLAY
我们会发现,DISPLAY环境变量的值被设置为:10.1.27.70:10.0
,其中,10.1.27.70
就是linux主机的ip,而10.0
,
则是由SSHD服务虚拟出来的linux主机上的XServer服务地址(当然,只是转发)
这种方式下,图形数据的传递方式是:
linux主机图形程序
<---X11协议:6010端口--->
linux主机上SSHD虚拟的XServer
<---SSH协议:22端口--->
windows远程SSH客户端
<---X11协议:6000端口--->
windows Xserver服务器
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。