文章目录
业务场景 处理解决 实现ping功能并实时返回输出 实现长ping和中断请求 docker容器找不到ping命令处理
业务场景
我们某市的客户,一直使用CS版本的信控平台,直接安装客户Windows server服务器上,主要对信号机设备进行在线管理、方案配时、管控等 其中有一项功能,在网络波动情况,对信号机管控失败,判断信号机是否在线。大致方法是直接调用Windows的dos窗口,发送 ping ip的命令,显示网络情况 我们新的信控平台使用Spring Cloud微服务架构,使用Spring Boot构建Java服务,使用google的jib插件打成docker镜像包 我们使用docker虚拟化部署,使用docker-compose统一管理所有服务,部署在Linux服务器里 客户很喜欢之前的功能,需要我们在新平台里实现这个功能,调用dos窗口,ping网络 而我们新平台是B/S架构,浏览器是很难调用Windows组件去弹出窗口实现ping功能,而且我们也没有限制一定使用的是Windows电脑访问,有网有浏览器就行 ping功能无论Windows还是Linux,都是有的,至于界面展现,只能自己实现了
处理解决
实现ping功能并实时返回输出
代码实现,有两个核心功能点 一是根据不同的操作系统,执行对应的系统命令,进行结果接收与解析
对于第一个问题,Java有现成的类库,使用Runtime.getRuntime().exec(ping命令)
即可 对于Windows服务器,需要使用GB2312
解析命令执行结果 对于Linux 服务器,需要使用UTF_8
解析命令执行结果 对于ServletOutputStream.println
输出中文字符串报错Not an ISO 8859-1 character
问题,可以使用PrintWriter.println
输出代替 也可以在ServletOutputStream.println
输出时输出字符数组(string.getBytes()
) 二是流式输出到请求端,模拟再现一秒一次的逐步展示的效果
对于第二个问题,核心是命令执行的结果输出流,要实时的返回给请求端,请求端能接收到 主要是获取流,然后按行读取,按行flush()
即可返回给请求端 对于请求端实时渲染,需要在代码的response
里指定ContentType
为text/event-stream
,这样flush刷新的返回流,才能实时被前端浏览器接收到(ChatGPT流式输出也是使用的这种content-type
) 一开始是考虑使用multipart
,完全不行,流flush后,浏览器无法获取,只能在流输出完成后,浏览器才能获取到 具体代码如下:
@GetMapping ( "/ping/start" )
public void ping ( String ip, Integer count, HttpServletResponse response) {
logger. info ( "ping 信号机【{}】 开始 ......" , ip) ;
response. setStatus ( HttpServletResponse . SC_OK ) ;
response. setContentType ( "text/event-stream" ) ;
response. setCharacterEncoding ( "UTF-8" ) ;
String line = null ;
Process pro = null ;
BufferedReader buf = null ;
try {
if ( null == count) {
count = 4 ;
}
String osName = System . getProperty ( "os.name" ) ;
if ( osName. toLowerCase ( ) . contains ( "windows" ) ) {
pro = Runtime . getRuntime ( ) . exec ( "ping -n " + count + " " + ip) ;
buf = new BufferedReader ( new InputStreamReader ( pro. getInputStream ( ) , "GB2312" ) ) ;
} else if ( osName. toLowerCase ( ) . contains ( "linux" ) ) {
pro = Runtime . getRuntime ( ) . exec ( "ping -c " + count + " " + ip) ;
buf = new BufferedReader ( new InputStreamReader ( pro. getInputStream ( ) , StandardCharsets . UTF_8 ) ) ;
}
PrintWriter out = response. getWriter ( ) ;
while ( null != buf && ( line = buf. readLine ( ) ) != null ) {
out. println ( line) ;
out. flush ( ) ;
}
logger. info ( "执行ping请求结束!" ) ;
out. close ( ) ;
} catch ( Exception e) {
logger. error ( "执行ping命令出现异常" ) ;
e. printStackTrace ( ) ;
} finally {
if ( null != pro) {
pro. destroy ( ) ;
}
}
}
实现长ping和中断请求
主要是在请求时传输一个唯一命令id,缓存到内存里 当命令执行完成,或者接收到打断请求时,调用destroy()
打断循环,结束请求 当然,可以尝试使用kill -2
去模拟CTRL + C
的打断,可以使用Runtime.getRuntime().exec(中断命令)
打断试下,我的代码已经满足自己的需求了,就没再尝试,有兴趣的小伙伴可以试一下 具体代码如下:
package com. newatc. api. rest ;
import com. newatc. api. signalcontrol. dto. PingRequestVO ;
import org. slf4j. Logger ;
import org. slf4j. LoggerFactory ;
import org. springframework. web. bind. annotation. PostMapping ;
import org. springframework. web. bind. annotation. RequestBody ;
import org. springframework. web. bind. annotation. RequestMapping ;
import org. springframework. web. bind. annotation. RestController ;
import javax. servlet. http. HttpServletResponse ;
import java. io. BufferedReader ;
import java. io. InputStreamReader ;
import java. io. PrintWriter ;
import java. nio. charset. StandardCharsets ;
import java. util. HashMap ;
import java. util. Map ;
@RestController
@RequestMapping ( value = "/api/syscmd" )
public class SysCmdController {
private static final Logger logger = LoggerFactory . getLogger ( SysCmdController . class ) ;
public static final Map < String , Boolean > COMMAND_REQUEST_MAP = new HashMap < > ( ) ;
@PostMapping ( "/ping/start" )
public void ping ( @RequestBody PingRequestVO pingRequest, HttpServletResponse response) {
String ip = pingRequest. getIp ( ) ;
String cmdId = pingRequest. getCmdId ( ) ;
Integer count = pingRequest. getCount ( ) ;
logger. info ( "ping 信号机【{}】 开始 ......" , ip) ;
response. setStatus ( HttpServletResponse . SC_OK ) ;
response. setContentType ( "text/event-stream" ) ;
response. setCharacterEncoding ( "UTF-8" ) ;
String line = null ;
Process pro = null ;
BufferedReader buf = null ;
try {
if ( null == count) {
count = 4 ;
}
if ( count > 50 ) {
count = 50 ;
}
String osName = System . getProperty ( "os.name" ) ;
if ( osName. toLowerCase ( ) . contains ( "windows" ) ) {
pro = Runtime . getRuntime ( ) . exec ( "ping -n " + count + " " + ip) ;
buf = new BufferedReader ( new InputStreamReader ( pro. getInputStream ( ) , "GB2312" ) ) ;
} else if ( osName. toLowerCase ( ) . contains ( "linux" ) ) {
pro = Runtime . getRuntime ( ) . exec ( "ping -c " + count + " " + ip) ;
buf = new BufferedReader ( new InputStreamReader ( pro. getInputStream ( ) , StandardCharsets . UTF_8 ) ) ;
}
COMMAND_REQUEST_MAP . put ( cmdId, true ) ;
PrintWriter out = response. getWriter ( ) ;
while ( null != buf && ( line = buf. readLine ( ) ) != null ) {
out. println ( line) ;
out. flush ( ) ;
if ( ! COMMAND_REQUEST_MAP . get ( cmdId) ) {
pro. destroy ( ) ;
}
}
logger. info ( "执行ping请求结束!" ) ;
out. close ( ) ;
} catch ( Exception e) {
logger. error ( "执行ping命令出现异常" ) ;
e. printStackTrace ( ) ;
} finally {
if ( null != pro) {
pro. destroy ( ) ;
}
COMMAND_REQUEST_MAP . remove ( cmdId) ;
}
}
@PostMapping ( "/ping/stop" )
public void ping ( @RequestBody PingRequestVO requestVO) {
COMMAND_REQUEST_MAP . put ( requestVO. getCmdId ( ) , false ) ;
}
}
docker容器找不到ping命令处理
我们打包导出的docker镜像,无法使用ping命令,报错,找不到这个命令bash: ping:command not found
我们使用的是极简镜像eclipse-temurin:11-jre-focal
,这个版本里的ubuntu
没有安装不需要的命令 具体可以参考我的这篇博文:《自制Java镜像发布到dockerhub公网使用》 也可以直接使用我发布到公网的包含ping命令的jre11镜像文件1363241277/jre11:11-jre-focal
主要思路,就是打包使用的原始Java镜像里,要已经安装ping
等需要的命令