内容简介:Arthas源码第三篇之命令执行过程这一篇主要聊下输入命令,到最后响应的过程, 顺带着熟悉整个项目结构。(以下会边贴代码边解释,篇幅可能比较长)
Arthas源码第三篇之命令执行过程
这一篇主要聊下输入命令,到最后响应的过程, 顺带着熟悉整个项目结构。(以下会边贴代码边解释,篇幅可能比较长)
接着上一篇ArthasBootstrap bind方法. 在方法内部会发现下面一行代码:
代码块
shellServer.listen(new BindHandler(isBindRef));
这行代码主要是监听命令作用,具体到listen方法内部看看:
代码块
@Override public ShellServer listen(final Handler<Future<Void>> listenHandler) { final List<TermServer> toStart; synchronized (this) { if (!closed) { throw new IllegalStateException("Server listening"); } toStart = termServers; } final AtomicInteger count = new AtomicInteger(toStart.size()); if (count.get() == 0) { setClosed(false); listenHandler.handle(Future.<Void>succeededFuture()); return this; } Handler<Future<TermServer>> handler = new TermServerListenHandler(this, listenHandler, toStart); for (TermServer termServer : toStart) { termServer.termHandler(new TermServerTermHandler(this)); termServer.listen(handler); } return this; }
可以看到TermServerTermHandler被实例化, 赋值给TermServer对象,然后TermServer监听(下面telnet监听为例).
代码块
@Override public TermServer listen(Handler<Future<TermServer>> listenHandler) { // TODO: charset and inputrc from options bootstrap = new NettyTelnetTtyBootstrap().setHost(hostIp).setPort(port); try { bootstrap.start(new Consumer<TtyConnection>() { @Override public void accept(final TtyConnection conn) { termHandler.handle(new TermImpl(Helper.loadKeymap(), conn)); } }).get(connectionTimeout, TimeUnit.MILLISECONDS); listenHandler.handle(Future.<TermServer>succeededFuture()); } catch (Throwable t) { logger.error(null, "Error listening to port " + port, t); listenHandler.handle(Future.<TermServer>failedFuture(t)); } return this; }
- 方法内部启动telnet端口监听。
- Helper.loadKeymap()这个类方法主要是在项目目录inputrc文件里加载对应的快捷键以及对应的处理类name标识,返回个映射对象,对命令行界面快捷键指示处理需要。
- new TermImpl 看看这个类内部实例做了哪些事情
代码块
private static final List<Function> readlineFunctions = Helper.loadServices(Function.class.getClassLoader(), Function.class); ... public TermImpl(Keymap keymap, TtyConnection conn) { this.conn = conn; readline = new Readline(keymap); readline.setHistory(FileUtils.loadCommandHistory(new File(Constants.CMD_HISTORY_FILE))); for (Function function : readlineFunctions) { readline.addFunction(function); } echoHandler = new DefaultTermStdinHandler(this); conn.setStdinHandler(echoHandler); conn.setEventHandler(new EventHandler(this)); }
- TermImpl内部首先可以看到对Function类通过spi进行了所有Function的加载,Function就是刚才快捷键对应的处理类,下面随便看看一个类,快捷键向上看历史命令。
public class HistorySearchBackward implements Function { @Override public String name() { return "history-search-backward"; } @Override public void apply(Readline.Interaction interaction) { LineBuffer buf = interaction.buffer().copy(); int cursor = buf.getCursor(); List<int[]> history = interaction.history(); int curr = interaction.getHistoryIndex(); int searchStart = curr + 1; for (int i = searchStart; i < history.size(); ++i) { int[] line = history.get(i); if (LineBufferUtils.equals(buf, line)) { continue; } if (LineBufferUtils.matchBeforeCursor(buf, line)) { interaction.refresh(new LineBuffer().insert(line).setCursor(cursor)); interaction.setHistoryIndex(i); break; } } interaction.resume(); } }
- 上面可以看到readline的字段history,通过本地history文件加载出来。我们执行的历史命令都会存储到history文件中。 可以猜测history命令怎么查找所有历史命令,就是这样拿出来的。
- 接着实例化DefaultTermStdinHandler,EventHandler以及对应的赋值,然后结合term框架,对相应的快捷键进行处理,这里就不多说,感兴趣自行去看。下面会重点说明help的整个过程。
- 回到上面termHandler.handle(new TermImpl(Helper.loadKeymap(), conn));这一行代码。回顾一下最上面termServer listen的时候, termServer.termHandler(new TermServerTermHandler(this)); 实例化了TermServerTermHandler。所以这里执行了TermServerTermHandler.handle方法
代码块
public class TermServerTermHandler implements Handler<Term> { private ShellServerImpl shellServer; public TermServerTermHandler(ShellServerImpl shellServer) { this.shellServer = shellServer; } @Override public void handle(Term term) { shellServer.handleTerm(term); }
- 然后调到ShellServerImpl.handleTerm方法
代码块
public void handleTerm(Term term) { synchronized (this) { // That might happen with multiple ser if (closed) { term.close(); return; } } ShellImpl session = createShell(term); session.setWelcome(welcomeMessage); session.closedFuture.setHandler(new SessionClosedHandler(this, session)); session.init(); sessions.put(session.id, session); // Put after init so the close handler on the connection is set session.readline(); // Now readline }
- 这里就初始化了session对象,然后设置了对应的欢迎语,所以你attach成功后,看到了图形界面,wiki,version等。
- 这里注意的是ShellImpl构造把命令列表以及内建命令缓存到session内存。
- session.readline(); 然后就是等待用户命令输入了,如图中$。 这里利用了term框架封装好的readline方法库,同时根据对应ShellLineHandler来回调处理相应的命令。
代码块
public void readline() { term.readline(Constants.DEFAULT_PROMPT, new ShellLineHandler(this), new CommandManagerCompletionHandler(commandManager)); } ... public void readline(String prompt, Handler<String> lineHandler, Handler<Completion> completionHandler) { if (conn.getStdinHandler() != echoHandler) { throw new IllegalStateException(); } if (inReadline) { throw new IllegalStateException(); } inReadline = true; readline.readline(conn, prompt, new RequestHandler(this, lineHandler), new CompletionHandler(completionHandler, session)); } ... public class RequestHandler implements Consumer<String> { private TermImpl term; private final Handler<String> lineHandler; public RequestHandler(TermImpl term, Handler<String> lineHandler) { this.term = term; this.lineHandler = lineHandler; } @Override public void accept(String line) { term.setInReadline(false); lineHandler.handle(line); } }
- 下面我们就输入help来看下项目整个处理过程.
- help输入来到上面readline方法。最终回调到ShellLineHandler.handle方法,ShellLineHandler handle方法关键步骤处理如下:
代码块
List<CliToken> tokens = CliTokens.tokenize(line); CliToken first = TokenUtils.findFirstTextToken(tokens); if (first == null) { // For now do like this shell.readline(); return; } String name = first.value(); if (name.equals("exit") || name.equals("logout") || name.equals("quit")) { handleExit(); return; } else if (name.equals("jobs")) { handleJobs(); return; } else if (name.equals("fg")) { handleForeground(tokens); return; } else if (name.equals("bg")) { handleBackground(tokens); return; } else if (name.equals("kill")) { handleKill(tokens); return; } Job job = createJob(tokens); if (job != null) { job.run(); }
- 这里做了前置的文件检查以及解析,help命令顺利到了createJob这一步,一层层封装点进去, 这里主要是遍历前面加载到内存的命令,如果找不到,command not found。
- 然后同时创建实例化CommandProcess, 这里要注意的是找到command对应的processHandler赋值给ProcessImpl属性了,这里就埋下伏笔,为后面路由找到HelpCommand。
- 篇幅有限,其他的点这里就不解释下去了。
代码块
@Override public Job createJob(InternalCommandManager commandManager, List<CliToken> tokens, ShellImpl shell) { int jobId = idGenerator.incrementAndGet(); StringBuilder line = new StringBuilder(); for (CliToken arg : tokens) { line.append(arg.raw()); } boolean runInBackground = runInBackground(tokens); Process process = createProcess(tokens, commandManager, jobId, shell.term()); process.setJobId(jobId); JobImpl job = new JobImpl(jobId, this, process, line.toString(), runInBackground, shell); jobs.put(jobId, job); return job; } ... private Process createProcess(List<CliToken> line, InternalCommandManager commandManager, int jobId, Term term) { try { ListIterator<CliToken> tokens = line.listIterator(); while (tokens.hasNext()) { CliToken token = tokens.next(); if (token.isText()) { Command command = commandManager.getCommand(token.value()); if (command != null) { return createCommandProcess(command, tokens, jobId, term); } else { throw new IllegalArgumentException(token.value() + ": command not found"); } } } throw new IllegalArgumentException(); } catch (Exception e) { throw new RuntimeException(e); } } ... public Command getCommand(String commandName) { Command command = null; for (CommandResolver resolver : resolvers) { // 内建命令在ShellLineHandler里提前处理了,所以这里不需要再查找内建命令 if (resolver instanceof BuiltinCommandPack) { command = getCommand(resolver, commandName); if (command != null) { break; } } } return command; }
- 然后创建完job, 继续回到ShellLineHandler job那块代码,上面代码可以看出 job.run(); 对job启动。这里比较重要的是刚才创建的Process对象,这里调用run方法
代码块
public Job run(boolean foreground) { ... process.setTty(shell.term()); process.setSession(shell.session()); process.run(foreground); if (!foreground && foregroundUpdatedHandler != null) { foregroundUpdatedHandler.handle(null); } if (foreground) { shell.setForegroundJob(this); } else { shell.setForegroundJob(null); } return this; }
- 然后接着以下关键代码, 关键一步就是最好两行ArthasBootstrap.getInstance().execute(task); 执行这个task,ProcessHandler执行process。
代码块
@Override public synchronized void run(boolean fg) { ... CommandLine cl = null; try { if (commandContext.cli() != null) { if (commandContext.cli().parse(args2, false).isAskingForHelp()) { UsageMessageFormatter formatter = new StyledUsageFormatter(Color.green); formatter.setWidth(tty.width()); StringBuilder usage = new StringBuilder(); commandContext.cli().usage(usage, formatter); usage.append('\n'); tty.write(usage.toString()); terminate(); return; } cl = commandContext.cli().parse(args2); } } catch (CLIException e) { tty.write(e.getMessage() + "\n"); terminate(); return; } process = new CommandProcessImpl(args2, tty, cl); if (cacheLocation() != null) { process.echoTips("job id : " + this.jobId + "\n"); process.echoTips("cache location : " + cacheLocation() + "\n"); } Runnable task = new CommandProcessTask(process); ArthasBootstrap.getInstance().execute(task); } ... private class CommandProcessTask implements Runnable { private CommandProcess process; public CommandProcessTask(CommandProcess process) { this.process = process; } @Override public void run() { try { handler.handle(process); } catch (Throwable t) { logger.error(null, "Error during processing the command:", t); process.write("Error during processing the command: " + t.getMessage() + "\n"); terminate(1, null); } } }
- Handler的实现有很多,可以发现ProcessHandler. 这里实现了handle,不过processHandler是在AnnotatedCommandImpl类里面的。(AnnotatedCommandImpl类的话,在之前初始化命令的时候,就已经实例化了,Command.create(HelpCommand.class))
- process里面instance.process(process)这一步,结合前面埋下的伏笔路由找到对应的HelpCommand的process
代码块
private class ProcessHandler implements Handler<CommandProcess> { @Override public void handle(CommandProcess process) { process(process); } } ... private void process(CommandProcess process) { AnnotatedCommand instance; try { instance = clazz.newInstance(); } catch (Exception e) { process.end(); return; } CLIConfigurator.inject(process.commandLine(), instance); instance.process(process); UserStatUtil.arthasUsageSuccess(name(), process.args()); }
- HelpCommand里面首先查询session里面的内建命令缓存(还记得前面内建命令缓存吗?), 然后render出对应的help表格界面
代码块
@Override public void process(CommandProcess process) { List<Command> commands = allCommands(process.session()); Command targetCmd = findCommand(commands); String message; if (targetCmd == null) { message = RenderUtil.render(mainHelp(commands), process.width()); } else { message = commandHelp(targetCmd, process.width()); } process.write(message); process.end(); }
最后
- 整个源码过程中还涉及到很多东西,一篇文章不能全部道来,只能把一部分流程写出来,希望有点帮忙。
- 上面只是我阅读源码过程中,顺带记录下来的,有任何理解不对的地方可以指出来。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 命令源码文件
- 走近源码:Redis 如何执行命令
- Go Redigo 源码分析(三) 执行命令
- Cocoapods 源码浅析 - 从 pod 命令开始
- 熔断器 Hystrix 源码解析 —— 执行命令方式
- 走近源码:Redis命令执行过程(客户端)
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
We Are the Nerds
Christine Lagorio-Chafkin / Hachette Books / 2018-10-2 / USD 18.30
Reddit hails itself as "the front page of the Internet." It's the third most-visited website in the United States--and yet, millions of Americans have no idea what it is. We Are the Nerds is an eng......一起来看看 《We Are the Nerds》 这本书的介绍吧!