约定
下文中,GUI程序所在地称为本地端,xserver所在地称为远端。
连接
本地端如果有sshd,并配置开启X11转发;远端开启xserver后,给ssh也配置上X11转发,这样ssh session创建完成后,sshd会创建一个本地端的代理xserver,通过ssh的加密传输来代理到远端的xserver。
期间ssh还负责给session设置$DISPLAY,给用户主目录的.Xauthority添加cookie以完成验证权限等操作。
问题是ssh加密传输在传输x的数据的时候,速度太慢了,cpu占用也高;可以通过压缩传输以及更换比较快的加密算法来改善,但是效果依然差强人意。
所以还是在本地端自己设置DISPLAY为“远端IP:0.0”并启动GUI客户端速度最快。
这个可以做个快捷启动。在本地端做一个这个脚本:
!/usr/bin/bash # use plink.exe together with a remote X server to execute this script # make sure a proxy DISPLAY and .Xauthority cookie is ready here # make the remote server accept all future client request from this end xhost +192.168.111.11 # reset the direct DISPLAY to make it work smoothly DISPLAY=192.168.111.1:0.0 /usr/bin/konsole # now this script exits, so does plink.exe
依然用ssh的X11转发来简历代理DISPLAY,只是这个代理只做一件事,就是设置远端的xserver的权限,以便后续的konsole的顺利启动。
字体
本地端xlsfonts命令会直接连接$DISPLAY的xserver,并列出来该xserver上的传统字体,这个行为就跟用xset来配置远端xserver是一样的。
因此当你的GUI程序使用的是传统的xserver字体系统(现在很少了,不过估计xterm应该是),那么就需要在远端的xserver上安装字体并生成fonts.dir和fonts.scale,让xserver启动的时候加载它们。
当你的GUI程序是用的font-config字体系统时(现在主流的qt和gtk应用应该都是,例如konsole),那么需要在本地端安装字体,并且用fc-cache扫描它们,这样GUI程序启动之后会渲染好了发送到$DISPLAY的xserver那里显示出来。
后记
后来做了一个ruby脚本,在xserver(cygwin的XWin最稳定,用XWin -multiwindow -listen tcp启动)端运行后,通过plink.exe来启动xclient端的konsole在xserver端显示。
xserver = '192.168.111.1' xclient = '192.168.111.11' rd, wr = IO.pipe pid = Process.spawn( "C:\\opt\\putty\\plink.exe", "-ssh", "-batch", "-i", "C:\\opt\\putty\\id_rsa.ppk", "yuqing@#{xclient}", "ruby", :in => rd) wr.write <<-EOBlock # make sure a proxy DISPLAY and .Xauthority cookie is ready puts 'Force #{xserver} to accept all future client request from #{xclient}...' if !system('xhost +#{xclient}') exit 1 end puts 'Use #{xserver} as direct DISPLAY to start GUI client...' pid = Process.spawn( {'DISPLAY' => '#{xserver}:0.0'}, '/usr/bin/konsole', '--nofork', [:in, :out, :err] => "/dev/null") Process.detach pid EOBlock wr.close if (!Process.wait2(pid).last.success?) system 'pause' end
要求xclient端也安装了ruby。
感觉Process.spawn()比system()和popen()甚至popen3()之类的好用的多,例如这里结合一个pipe就实现了popen,并且同时保持了子进程的stdout和stderr和父进程相同。