Dubbo之TelnetCodec源码解析

功能概述

  • TelnetCodec用于实现在终端执行telnet指定的编解码功能。

功能分析

核心类TelnetCodec分析

主要成员变量分析

private static final byte[] UP = new byte[] {27, 91, 65}; //向上指令private static final byte[] DOWN = new byte[] {27, 91, 66}; //向下指令private static final List<?> ENTER = Arrays.asList( //换行指令 (参照ASCII码对照表)new byte[] {'\r', '\n'} /* Windows Enter */,new byte[] {'\n'} /* Linux Enter */);private static final List<?> EXIT = Arrays.asList( //退出对应的字节数组,是个二维数组new byte[] {3} /* Windows Ctrl+C */,new byte[] {-1, -12, -1, -3, 6} /* Linux Ctrl+C */,new byte[] {-1, -19, -1, -3, 6} /* Linux Pause */);

主要成员方法分析

获取字符编码

private static Charset getCharset(Channel channel) {if (channel != null) {Object attribute = channel.getAttribute(CHARSET_KEY); //获取配置的字符集名称if (attribute instanceof String) { //判断是String类型还是Charset类型try {return Charset.forName((String) attribute); //尝试获取指定字符串的字符编码} catch (Throwable t) {logger.warn(t.getMessage(), t);}} else if (attribute instanceof Charset) {return (Charset) attribute;}URL url = channel.getUrl(); //远程urlif (url != null) {String parameter = url.getParameter(CHARSET_KEY);if (StringUtils.isNotEmpty(parameter)) {try {return Charset.forName(parameter);} catch (Throwable t) {logger.warn(t.getMessage(), t);}}}}try {return Charset.forName(DEFAULT_CHARSET); //获取默认字符编码“UTF-8”} catch (Throwable t) {logger.warn(t.getMessage(), t);}return Charset.defaultCharset();
}
  • 获取字符集的逻辑
    • 从通道Channel的设置的属性值获取。
    • 若没有,从通道的Url中获取。
    • 若还没有,则取默认的字符集(默认字符集为UTF-8)。

响应内容编码

public void encode(Channel channel, ChannelBuffer buffer, Object message) throws IOException { //对响应的内容进行编码if (message instanceof String) { //字符串类型处理if (isClientSide(channel)) {message = message + "\r\n"; //客户端输入的内容拼接上换行符}byte[] msgData = ((String) message).getBytes(getCharset(channel).name()); //若是字符串,直接根据字符集获取字节数组buffer.writeBytes(msgData);} else { //对象类型处理,交由父类来处理super.encode(channel, buffer, message);}
}

请求内容解码

protected Object decode(Channel channel, ChannelBuffer buffer, int readable, byte[] message) throws IOException { //对许多特殊字符,如换行符、退位符进行处理if (isClientSide(channel)) { //若是客户端,直接将字节数组转换为字符串return toString(message, getCharset(channel)); //获取字符集,并将字符数组转换为字符串}checkPayload(channel, readable);if (message == null || message.length == 0) { //消息内容为空时,不再进行后续处理return DecodeResult.NEED_MORE_INPUT;}if (message[message.length - 1] == '\b') { // Windows backspace echo ('\b'的值为8)try {boolean doublechar = message.length >= 3 && message[message.length - 3] < 0; // double byte char (判断逻辑:消息的长度大于3,并且倒数第三个元素数值小于0)channel.send(new String(doublechar ? new byte[] {32, 32, 8, 8} : new byte[] {32, 8}, getCharset(channel).name())); //32对应的字符为空格} catch (RemotingException e) {throw new IOException(StringUtils.toString(e));}return DecodeResult.NEED_MORE_INPUT; //需要输入更多的字符}for (Object command : EXIT) {if (isEquals(message, (byte[]) command)) { //判断是否包含"退出指令",若包含则关闭channelif (logger.isInfoEnabled()) {logger.info(new Exception("Close channel " + channel + " on exit command: " + Arrays.toString((byte[]) command)));}channel.close(); //执行退出指令时,会将通道channel关闭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);if (CollectionUtils.isEmpty(history)) {return DecodeResult.NEED_MORE_INPUT;}Integer index = (Integer) channel.getAttribute(HISTORY_INDEX_KEY); //取出历史记录索引Integer old = index;if (index == null) {index = history.size() - 1; //若没设置索引,则取列表中的最后一条} else {if (up) { //执行向上操作index = index - 1;if (index < 0) {index = history.size() - 1; //如果索引小于0,则轮询到最后一条}} else { //执行向下操作index = index + 1;if (index > history.size() - 1) {//如果所以大于最后一条,则轮询到第一条index = 0;}}}if (old == null || !old.equals(index)) { //表示:old不为空或old与index不相等channel.setAttribute(HISTORY_INDEX_KEY, index);String value = history.get(index);if (old != null && old >= 0 && old < history.size()) {String ov = history.get(old);StringBuilder buf = new StringBuilder();for (int i = 0; i < ov.length(); i++) {buf.append("\b");}for (int i = 0; i < ov.length(); i++) {buf.append(" ");}for (int i = 0; i < ov.length(); i++) {buf.append("\b");}value = buf.toString() + value;}try {channel.send(value);} 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 " + command));}channel.close();return null;}}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); //使用过后,将HISTORY_INDEX_KEY历史记录所以移除if (CollectionUtils.isNotEmpty(history) && 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;
}
  • 上下键本意上是对历史指令的支持,但是不同平台的支持不一样,比如Mac就会附加的UP的字节数组为[27,91,65,13,10]把换行符加上了,就导致不是以UP结尾,就失效了。

辅助类AbstractCodec分析

主要成员方法分析

检查负载大小

protected static void checkPayload(Channel channel, long size) throws IOException { //检查负载大小(请求体的数据大小)int payload = Constants.DEFAULT_PAYLOAD; //默认负载大小为8 * 1024 * 1024,即8M;if (channel != null && channel.getUrl() != null) {payload = channel.getUrl().getParameter(Constants.PAYLOAD_KEY, Constants.DEFAULT_PAYLOAD);}if (payload > 0 && size > payload) { //超过负载,则抛出异常ExceedPayloadLimitException e = new ExceedPayloadLimitException("Data length too large: " + size + ", max payload: " + payload + ", channel: " + channel);logger.error(e);throw e;}
}

判断是否是客户端

protected boolean isClientSide(Channel channel) { //判断是否是客户端String side = (String) channel.getAttribute(SIDE_KEY);if (CLIENT_SIDE.equals(side)) { //判断通道中属性SIDE_KEY的缓存值return true;} else if (SERVER_SIDE.equals(side)) {return false;} else {InetSocketAddress address = channel.getRemoteAddress();URL url = channel.getUrl(); //远程url,即服务端的urlboolean isClient = url.getPort() == address.getPort()&& NetUtils.filterLocalHost(url.getIp()).equals(NetUtils.filterLocalHost(address.getAddress().getHostAddress())); //比较逻辑:当通道中的远程地址与url地址比较,如果远程是服务端,那么当前就是客户端,反之类推(也可以按localAddress来判断)channel.setAttribute(SIDE_KEY, isClient ? CLIENT_SIDE: SERVER_SIDE);return isClient;}
}

辅助类TelnetHandlerAdapter分析

主要成员方法分析

指令调用

public String telnet(Channel channel, String message) throws RemotingException { //Telnet指令处理String prompt = channel.getUrl().getParameterAndDecoded(Constants.PROMPT_KEY, Constants.DEFAULT_PROMPT);boolean noprompt = message.contains("--no-prompt");message = message.replace("--no-prompt", ""); //telnet提示键(如果做了配置,将不显示dubbo>)StringBuilder buf = new StringBuilder();message = message.trim();String command;if (message.length() > 0) { //message不为空字符串时,解析指令和执行的内容(输入回车键会收到空字符串)int i = message.indexOf(' '); //接收指令时,收到的message不包含提示符,如dubbo> ls,收到的message为lsif (i > 0) { //拆分命令和参数command = message.substring(0, i).trim();message = message.substring(i + 1).trim();} else {command = message;message = "";}} else {command = "";}if (command.length() > 0) { //telnet输入回车时,写到通道的内容为空字符串,不会进入此处逻辑if (extensionLoader.hasExtension(command)) { //将命令名作为SPI的扩展名if (commandEnabled(channel.getUrl(), command)) {try {String result = extensionLoader.getExtension(command).telnet(channel, message); //获取指定命令的实例,并将结果写到channelif (result == null) {return null;}buf.append(result);} catch (Throwable t) {buf.append(t.getMessage());}} else {buf.append("Command: ");buf.append(command);buf.append(" disabled");}} else {buf.append("Unsupported command: ");buf.append(command);}}if (buf.length() > 0) {buf.append("\r\n"); //\r 回车符,\n 换行符,用回车符换行符结束指令}if (StringUtils.isNotEmpty(prompt) && !noprompt) { //提示键处理:若提示键内容不为空且没有禁用,则作对应展示buf.append(prompt);}return buf.toString(); //响应给telnet客户端的内容
}

问题点答疑

  • TelnetHandler处理接口有多个实现类,即telnet命令会有多个,如ListTelnetHandler列表指令,InvokeTelnetHandler调用指令,那么不同指令是怎么分发的?

    • 解答:通过实现类TelnetHandlerAdapter将输入的字符串message进行解析,获取到指令名以及执行内容,再把指令名称作为SPI的扩展名,根据SPI机制获取到对应的实例。
  • telnet中的提示键可以设置自定义提示符吗,怎么设置?

    • 解答:可以自定义提示符,通过dubbo:parameter/参数配置设置。

归纳总结

  • Telnet协议是TCP/IP协议族中的一员,是Internet远程登陆服务的标准协议。Telnet协议的目的是提供一个相对通用的,双向的,面向八位字节的通信方法,允许界面终端设备和面向终端的过程能通过一个标准过程进行互相交互。应用Telnet协议能够把本地用户所使用的计算机变成远程主机系统的一个终端

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/62093.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

OB Cloud上新,4.1版本现已全面开放

2022 年 8 月 10 日&#xff0c;OceanBase 宣布 OceanBase 公有云服务全球开服&#xff0c;帮助不同规模客户&#xff0c;在全球不同区域&#xff0c;享受同样优质的企业级数据库产品与服务。 经过近一年的发展&#xff0c;公有云业务取得了长足的发展&#xff0c;去年对客收入…

三步完成echers展示离线地图

1.首先要去阿里云提供的地图选择器网站选择你需要下载的地图矢量数据。链接 以湖北省为例&#xff1a; 2.复制上图中的JSON API&#xff0c;在浏览器输入json api链接&#xff0c;可以看到数据格式是很规整的json数据&#xff0c;在浏览器中右键保存为json格式数据&#xff0c…

go学习笔记 炒土豆丝

今天的主菜是土豆&#xff0c;就来个土豆丝吧。我的大致流程如下&#xff1a; 1.挑选白瓤土豆&#xff0c;洗它 2.土豆去皮 3.土豆切片&#xff0c;切丝&#xff0c;丝要粗细均匀 4.清洗几遍土豆丝&#xff0c;去除上面的淀粉&#xff0c;在清水中泡一小会 5.起锅&#xff0c;放…

Bytebase 2.7.0 - ​新增分支(Branching)功能

&#x1f680; 新功能 新增支持与 Git 类似的分支&#xff08;Branching&#xff09;功能来管理 schema 变更。支持搜索所有历史工单。支持导出审计日志。 &#x1f384; 改进 变更数据库工单详情页面全新改版。优化工单搜索体验。SQL 审核规则支持针对不同数据库进行独立配…

癌症预测新利器:弹性逻辑回归让健康更可控!

一、引言 癌症是全球范围内健康领域的重大挑战&#xff0c;早期预测和诊断对于提高治疗效果和生存率至关重要。在过去的几十年里&#xff0c;随着医学和数据科学的快速发展&#xff0c;基于机器学习和统计方法的癌症风险预测成为研究的热点。其中&#xff0c;弹性逻辑回归作为一…

C#委托(delegate)

概念&#xff1a; 委托&#xff08;delegate&#xff09;&#xff1a;是一种引用类型的变量&#xff0c;用于存储某个方法的引用地址 C#中的委托&#xff08;Delegate&#xff09;是一种类型安全的对象&#xff0c;它可以存储对一个或多个方法的引用。委托可以像其他类型一样作…

热烈祝贺蜀益表面处理成功入选航天系统采购平台

经过航天系统采购平台的严审&#xff0c;眉山市蜀益表面处理科技有限公司成功入选中国航天系统采购供应商库。航天系统采购平台是航天系统内企业采购专用平台&#xff0c;服务航天全球范围千亿采购需求&#xff0c;目前&#xff0c;已有华为、三一重工、格力电器、科大讯飞等企…

使用php实现微信登录其实并不难,可以简单地分为三步进行

使用php实现微信登录其实并不难&#xff0c;可以简单地分为三步进行。 第一步&#xff1a;用户同意授权&#xff0c;获取code //微信登录public function wxlogin(){$appid "";$secret "";$str"http://***.***.com/getToken";$redirect_uriu…

Linux文件管理知识:查找文件(第二篇)

上篇文章详细介绍了linux系统中查找文件的工具或者命令程序locate和find命令的基本操作。那么&#xff0c;今天这篇文章紧接着查找文件相关操作内容介绍。 Find命令所属操作列表中的条目&#xff0c;有助于我们想要的结果输出。上篇文章已讲到find 命令是基于搜索结果来执行操作…

Redis 介绍

一.Redis 介绍 Redis 和 Memcached 都是非关系型数据库也称为 NoSQL 数据库&#xff0c;MySQL、 Mariadb、SQL Server、PostgreSQL、Oracle 数据库属于关系型数据 关系型数据库(RDBMS, Relational Database Management System)。 1.1 Redis 介绍 Redis(Remote Dictionary Se…

Hadoop HA模式切换

Hadoop HA模式下 主从的切换&#xff08;操作命令&#xff09; YARN HA 获取所有RM节点的状态 yarn rmadmin -getAllServiceState获取 rm1 节点的状态 yarn rmadmin -getServiceState rm1手动将 rm1 的状态切换到STANDBY yarn rmadmin -transitionToStandby rm1 ##或者 y…

【LeetCode-中等题】236. 二叉树的最近公共祖先

文章目录 题目方法一&#xff1a;后序遍历 回溯 题目 方法一&#xff1a;后序遍历 回溯 解题的核心就是&#xff1a;采用后序遍历 讨论p&#xff0c;q是否在当前的root的两边&#xff0c;如在两边则返回当前节点root 如何不在两边&#xff0c;只要出现一个节点等于p或者q就…

OpenCV

文章目录 OpenCV学习报告读取图片和网络摄像头1.1 图片读取1.2 视频读取1.1.1 读取视频文件1.1.2读取网络摄像头 OpenCV基础功能调整、裁剪图像3.1 调整图像大小3.2 裁剪图像 图像上绘制形状和文本4.1 图像上绘制形状4.2图像上写文字 透视变换图像拼接颜色检测轮廓检测人脸检测…

前端无需install快速调试npm包,Console-Import使用

Console-Import是一个Chrome扩展插件&#xff0c;可以方便地从Chrome控制台导入JavaScript和CSS资源。它可以帮助我们在开发过程中快速调试和测试第三方库或代码。 下载地址 安装 要安装Console-Import&#xff0c;请在Chrome网上应用店搜索“Console-Import”&#xff0c;然…

微信小程序发布一个npm包

参考:https://developers.weixin.qq.com/miniprogram/dev/devtools/npm.html 同npm一样流程 npm install weixin_heath_apis

使用Redis统计网站的UV/DAU

HyperLogLog/BitMap 统计UV、DAU需要用到Redis的高级数据类型 M public class RedisKeyUtil {private static final String PREFIX_UV "uv";private static final String PREFIX_DAU "dau";// a single days UVpublic static String getUVKey(String …

MySQL 8.1.0 推出 InnoDB Cluster 只读副本

全面了解 8.1.0 版本新功能&#xff1a;InnoDB Cluster 只读副本的相关操作。 作者&#xff1a;Miguel Arajo 高级软件工程师 / Kenny Gryp MySQL 产品总监 本文来源&#xff1a;Oracle MySQL 官网博客 * 爱可生开源社区出品。 前言 MySQL 的第一个 Innovation 版本 8.1.0 已…

基于JAVA SpringBoot和HTML婴幼儿商品商城设计

摘要 随着网络技术的发展与普遍,人们的生活发生了日新月异的变化,特别是计算机的应用已经普及到经济和社会的各个领域.为了让消费者网上购物过程变得简单,方便,安全,快捷,网上商城购物成了一种新型而热门的购物方式。网上商城在商品销售的发展中占据了重要的地位,已成为商家展示…

【Hello Algorithm】链表相关算法题

本篇博客介绍&#xff1a; 介绍下链表相关的算法题 链表相关算法题 快慢指针回文结构链表将单向链表按某值划分为左边小&#xff0c;中间相等&#xff0c;右边大的形式复制带随机指针的链表 链表相关的算法题其实都算不上难 我们真正要考虑的是一些边界问题 事实上链表题就是在…

【大数据】数据湖:下一代大数据的发展趋势

数据湖&#xff1a;下一代大数据的发展趋势 1.数据湖技术产生的背景1.1 离线大数据平台&#xff08;第一代&#xff09;1.2 Lambda 架构1.3 Lambda 架构的痛点1.4 Kappa 架构1.5 Kappa 架构的痛点1.6 大数据架构痛点总结1.7 实时数仓建设需求 2.数据湖助力于解决数据仓库痛点问…