1

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服务器

参考文档


sswhsz
168 声望4 粉丝