dubbo之NIO服务器(三)之telnet

本文深入探讨了Dubbo中telnet功能的实现细节,包括TelnetCodec的解码过程,如何处理不同命令,以及如何通过SPI加载并执行特定的TelnetHandler。此外,还介绍了如何适配和执行用户输入的telnet命令,以及如何处理命令历史记录和提示符的定制。
摘要由CSDN通过智能技术生成

写在前面

dubbo之telnet使用 一文中我们介绍了如何使用dubbo 提供的telnet server功能,本文一起来看下其具体实现,对应的源码模块如下图:

在这里插入图片描述

该功能模块对应的顶层接口是com.alibaba.dubbo.remoting.telnet.TelnetHandler,不同的telnet命令对应了不同的实现子类,如command包下对应的命令分别是clear,exit,help,log,status,如下图红框对部分命令的使用:

在这里插入图片描述

客户端方发送的消息需要经过适配,才能执行到对应的子类,这个适配的类是com.alibaba.dubbo.remoting.telnet.support.TelnetHandlerAdapter,而发送的消息需要经过编解码类com.alibaba.dubbo.remoting.telnet.codec.TelnetCodec的解码,整个消息发送的过程如下图:

在这里插入图片描述

我们按照从左向右的顺序来看。

1:TelnetCodec

为什么要有不同的编解码器:不同协议下的数据格式是不一样的,因此解析其中我们需要的业务数据方式也是不一样的,所以想要从不同协议格式中获取业务数据,就需要不同的编解码器。

1.1:解码

源码如下:

class FakeCls {
    // com.alibaba.dubbo.remoting.telnet.codec.TelnetCodec.decode(channel, buffer, readable, message)
    protected Object decode(Channel channel, ChannelBuffer buffer, int readable, byte[] message) throws IOException {
        // false
        if (isClientSide(channel)) {
            return toString(message, getCharset(channel));
        }
        // 检查消息体大小
        checkPayload(channel, readable);
        // false
        if (message == null || message.length == 0) {
            return DecodeResult.NEED_MORE_INPUT;
        }
        // 处理退格
        if (message[message.length - 1] == '\b') { // Windows backspace echo
            try {
                // 32 空格 8 退格
                boolean doublechar = message.length >= 3 && message[message.length - 3] < 0; // double byte char
                channel.send(new String(doublechar ? new byte[]{32, 32, 8, 8} : new byte[]{32, 8}, getCharset(channel).name()));
            } catch (RemotingException e) {
                throw new IOException(StringUtils.toString(e));
            }
            return DecodeResult.NEED_MORE_INPUT;
        }
        // 退出
        for (Object command : EXIT) {
            if (isEquals(message, (byte[]) command)) {
                if (logger.isInfoEnabled()) {
                    logger.info(new Exception("Close channel " + channel + " on exit command: " + Arrays.toString((byte[]) command)));
                }
                channel.close();
                return null;
            }
        }
        // 键盘的上下键 ↑ ↓,用于查看历史使用的指令
        boolean up = endsWith(message, UP);
        boolean down = endsWith(message, DOWN);
        if (up || down) {
            LinkedList<String> history = (LinkedList<String>) channel.getAttribute(HISTORY_LIST_KEY);
            // snip code
            }
            // 需要更多输入
            return DecodeResult.NEED_MORE_INPUT;
        }
        for (Object command : EXIT) {
            if (isEquals(message, (byte[]) command)) {
                if (logger.isInfoEnabled()) {
                    logger.info(new Exception("Close channel " + channel + " on exit command " + command));
                }
                channel.close();
                return null;
            }
        }
        // 判断是否是回车结尾,如果不是,则说明用户还没有输入完毕
        /*
        0 = {byte[2]@4590} 
         0 = 13
         1 = 10
        1 = {byte[1]@4591} 
         0 = 10
        */
        byte[] enter = null;
        for (Object command : ENTER) {
            if (endsWith(message, (byte[]) command)) {
                enter = (byte[]) command;
                break;
            }
        }
        // 不是回车,则返回需要更多输入
        if (enter == null) {
            return DecodeResult.NEED_MORE_INPUT;
        }
        LinkedList<String> history = (LinkedList<String>) channel.getAttribute(HISTORY_LIST_KEY);
        Integer index = (Integer) channel.getAttribute(HISTORY_INDEX_KEY);
        channel.removeAttribute(HISTORY_INDEX_KEY);
        if (history != null && !history.isEmpty() && index != null && index >= 0 && index < history.size()) {
            String value = history.get(index);
            if (value != null) {
                byte[] b1 = value.getBytes();
                byte[] b2 = new byte[b1.length + message.length];
                System.arraycopy(b1, 0, b2, 0, b1.length);
                System.arraycopy(message, 0, b2, b1.length, message.length);
                message = b2;
            }
        }
        // 获取用户输入的内容
        String result = toString(message, getCharset(channel));
        // 存档到历史记录中,用于后续通过上下键↑ ↓查看历史命令记录
        if (result.trim().length() > 0) {
            if (history == null) {
                history = new LinkedList<String>();
                channel.setAttribute(HISTORY_LIST_KEY, history);
            }
            if (history.isEmpty()) {
                history.addLast(result);
            } else if (!result.equals(history.getLast())) {
                history.remove(result);
                history.addLast(result);
                if (history.size() > 10) {
                    history.removeFirst();
                }
            }
        }
        // 返回用户输入的命令
        return result;
    }
}

2:TelnetHandler

telnet命令处理器的顶层接口,源码如下:

@SPI
public interface TelnetHandler {
    String telnet(Channel channel, String message) throws RemotingException;
}

对应的dubbo spi文件如下图:

在这里插入图片描述

3:TelnetHandlerAdapter

该类是telnet处理器的适配器类,用来解析发送的telnet命令,然后通过dubbo SPI机制获取对应的telnet实现类,而后调用其逻辑执行具体处理,源码如下:

public class TelnetHandlerAdapter extends ChannelHandlerAdapter implements TelnetHandler {
    // dubbo com.alibaba.dubbo.common.extension.ExtensionLoader
    // 2022年3月26日17:17:22
    private final ExtensionLoader<TelnetHandler> extensionLoader = ExtensionLoader.getExtensionLoader(TelnetHandler.class);

    @Override
    public String telnet(Channel channel, String message) throws RemotingException {
        // 2022年3月26日17:28:47
        String prompt = channel.getUrl().getParameterAndDecoded(Constants.PROMPT_KEY, Constants.DEFAULT_PROMPT);
        // 2022年3月28日10:16:39
        boolean noprompt = message.contains("--no-prompt");
        // 替换掉--no-prompt,获取命令信息,如ls -l --no-prompt 变为 ls -l
        message = message.replace("--no-prompt", "");
        StringBuilder buf = new StringBuilder();
        message = message.trim();
        String command;
        // 获取命令和命令参数 如ls -l,command="ls" message = "-l"
        if (message.length() > 0) {
            int i = message.indexOf(' ');
            if (i > 0) {
                command = message.substring(0, i).trim();
                message = message.substring(i + 1).trim();
            } else {
                command = message;
                message = "";
            }
        } else {
            command = "";
        }
        if (command.length() > 0) {
            // 如果存在的dubbo SPI实现类的话
            if (extensionLoader.hasExtension(command)) {
                // 2022年3月28日14:07:52
                if (commandEnabled(channel.getUrl(), command)) {
                    try {
                        // 2022年3月28日15:08:57
                        String result = extensionLoader.getExtension(command).telnet(channel, message);
                        if (result == null) {
                            return null;
                        }
                        buf.append(result);
                    } catch (Throwable t) {
                        buf.append(t.getMessage());
                    }
                // 命令不支持,返回Command: {command} disabled
                } else {
                    buf.append("Command: ");
                    buf.append(command);
                    buf.append(" disabled");
                }
            // 不存在对应的Dubbo SPI扩展类,说明不支持该命令
            } else {
                buf.append("Unsupported command: ");
                buf.append(command);
            }
        }
        // 拼接换行到结果
        if (buf.length() > 0) {
            buf.append("\r\n");
        }
        // 拼接命令提示符到结果
        // 比如返回的结果是xxx,则最终用户看到的效果是:
        /*
        xxx
        dubbo>
        */
        if (prompt != null && prompt.length() > 0 && !noprompt) {
            buf.append(prompt);
        }
        return buf.toString();
    }
}

2022年3月26日17:17:22处关于dubbo spi机制可以参考dubbo之SPI分析2022年3月26日17:28:47处是处理提示符的情况,提示符默认是dubbo>,如下图:

在这里插入图片描述

也可以通过<dubbo:provider ... prompt="dongshidaddy--"/>,这里就是将提示符修改为dongshidaddy--,如下图:

在这里插入图片描述

当我们不进行任何配置时默认的提示符如下图:

在这里插入图片描述

2022年3月28日10:16:39查看是否使用了--no-prompt参数,如果是使用了该参数则在返回结果中不会显示命令提示符,如下图:

在这里插入图片描述

2022年3月28日14:07:52处是判断当前命令是否支持,如果是命令不支持,会返回Command: log disabled。具体参考3.1:TelnetHandlerAdapter2022年3月28日15:08:57处是根据命令获取对应的Dubbo SPI扩展类,并调用telnet方法获取命令执行的结果,具体参考4:TelnetHandler命令实现

3.1:TelnetHandlerAdapter

源码如下:

// com.alibaba.dubbo.remoting.telnet.support.TelnetHandlerAdapter.commandEnabled
class FakeCls {
    private boolean commandEnabled(URL url, String command) {
        boolean commandEnable = false;
        // 获取url上的telnet值,该值用于配置支持的命令,不配置或者是空的话代表全局支持
        String supportCommands = url.getParameter(Constants.TELNET);
        // 全部支持
        if (StringUtils.isEmpty(supportCommands)) {
            commandEnable = true;
        // 部分支持,则判断当前命令是否在部分支持的命令中,如<dubbo:provider ... telnet="ls,status"/>,仅支持ls,status命令
        } else {
            // 逗号分隔,依次判断
            String[] commands = Constants.COMMA_SPLIT_PATTERN.split(supportCommands);
            for (String c : commands) {
                if (command.equals(c)) {
                    commandEnable = true;
                    break;
                }
            }
        }
        return commandEnable;
    }
}

4:TelnetHandler命令实现

对应的DUBBO xml配置如下图:

在这里插入图片描述

我们以invoke=org.apache.dubbo.rpc.protocol.dubbo.telnet.InvokeTelnetHandler为例来看下,具体参考4.1:InvokeTelnetHandler

4.1:InvokeTelnetHandler

当我们使用命令invoke 命令调用提供者的服务时,就会使用InvokeTelnetHandler,如invoke XxxService.xxxMethod(1234, "abcd", {"prop" : "value"}) 或者 invoke com.xxx.XxxService.xxxMethod(1234, "abcd", {"prop" : "value"})

源码如下:

class FakeCls {
    // com.alibaba.dubbo.rpc.protocol.dubbo.telnet.InvokeTelnetHandler.telnet
    public String telnet(Channel channel, String message) {
        // 发送的telnet,如invoke dongshi.daddy.service.scoperemote.ScopeRemoteService.sayHi("from telnet client")
        if (message == null || message.length() == 0) {
            return "Please input method name, eg: \r\ninvoke xxxMethod(1234, \"abcd\", {\"prop\" : \"value\"})\r\ninvoke XxxService.xxxMethod(1234, \"abcd\", {\"prop\" : \"value\"})\r\ninvoke com.xxx.XxxService.xxxMethod(1234, \"abcd\", {\"prop\" : \"value\"})";
        }
        StringBuilder buf = new StringBuilder();
        // 通过telnet.service获取默认的服务
        String service = (String) channel.getAttribute(ChangeTelnetHandler.SERVICE_KEY);
        if (service != null && service.length() > 0) {
            buf.append("Use default service " + service + ".\r\n");
        }
        // 获取方法参数的开始位置
        int i = message.indexOf("(");
        // 如果是不存在(,或者是没有以)结尾
        if (i < 0 || !message.endsWith(")")) {
            return "Invalid parameters, format: service.method(args)";
        }
        // 获取方法名称,如invoke dongshi.daddy.service.scoperemote.ScopeRemoteService.sayHi("from telnet client") -> dongshi.daddy.service.scoperemote.ScopeRemoteService.sayHi 
        String method = message.substring(0, i).trim();
        // 获取方法参数,即(和)中间的内容
        String args = message.substring(i + 1, message.length() - 1).trim();
        // 获取服务名和方法名
        i = method.lastIndexOf(".");
        if (i >= 0) {
            service = method.substring(0, i).trim();
            method = method.substring(i + 1).trim();
        }
        List<Object> list;
        try {
            list = JSON.parseArray("[" + args + "]", Object.class);
        } catch (Throwable t) {
            return "Invalid json argument, cause: " + t.getMessage();
        }
        // 服务类对应的invoker
        Invoker<?> invoker = null;
        // 要调用的服务方法
        Method invokeMethod = null;
        // 通过Exporter获取目标invoker
        for (Exporter<?> exporter : DubboProtocol.getDubboProtocol().getExporters()) {
            if (service == null || service.length() == 0) {
                invokeMethod = findMethod(exporter, method, list);
                if (invokeMethod != null) {
                    invoker = exporter.getInvoker();
                    break;
                }
            } else {
                if (service.equals(exporter.getInvoker().getInterface().getSimpleName())
                        || service.equals(exporter.getInvoker().getInterface().getName())
                        || service.equals(exporter.getInvoker().getUrl().getPath())) {
                    invokeMethod = findMethod(exporter, method, list);
                    invoker = exporter.getInvoker();
                    break;
                }
            }
        }
        if (invoker != null) {
            if (invokeMethod != null) {
                try {
                    Object[] array = PojoUtils.realize(list.toArray(), invokeMethod.getParameterTypes(), invokeMethod.getGenericParameterTypes());
                    RpcContext.getContext().setLocalAddress(channel.getLocalAddress()).setRemoteAddress(channel.getRemoteAddress());
                    long start = System.currentTimeMillis();
                    // 调用获取结果
                    Object result = invoker.invoke(new RpcInvocation(invokeMethod, array)).recreate();
                    // 记录程序执行耗时信息
                    long end = System.currentTimeMillis();
                    buf.append(JSON.toJSONString(result));
                    buf.append("\r\nelapsed: ");
                    buf.append(end - start);
                    buf.append(" ms.");
                } catch (Throwable t) {
                    return "Failed to invoke method " + invokeMethod.getName() + ", cause: " + StringUtils.toString(t);
                }
            } else {
                buf.append("No such method " + method + " in service " + service);
            }
        } else {
            buf.append("No such service " + service);
        }
        return buf.toString();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值