duubo报错:一个NoClassDefFoundError:factories/SerializerFactory问题解决

duubo编译错误 NoClassDefFoundError,factories/SerializerFactory说一个挺有意思的解释dubbo关系,dubbo分为服务者和消费者,服务者比作司机,消费者比作乘客,zookeeper比作滴滴APP,双方之间的建立关系都在这个APP体现)说正题了,昨天写了一个dubbo 提供的接口报错,百思不得其解。第一眼感觉是序列化的问题,实际已经加上了:1.序列化的问题,缺少Serializable(bean实现Serializable接口即可)Serialized class com.yykj.mall.dto.ProductListItemDTO must implement java.io.Serializable报错信息如下:Caused by: java.lang.NoClassDefFoundError: com/esotericsoftware/kryo/factories/SerializerFactoryat com.alibaba.dubbo.common.serialize.support.kryo.KryoFactory.createKryo(KryoFactory.java:74)at com.alibaba.dubbo.common.serialize.support.kryo.PooledKryoFactory.getKryo(PooledKryoFactory.java:43)at com.alibaba.dubbo.common.serialize.support.kryo.KryoObjectOutput.<init>(KryoObjectOutput.java:31)at com.alibaba.dubbo.common.serialize.support.kryo.KryoSerialization.serialize(KryoSerialization.java:43)at com.alibaba.dubbo.remoting.exchange.codec.ExchangeCodec.encodeRequest(ExchangeCodec.java:240)at com.alibaba.dubbo.remoting.exchange.codec.ExchangeCodec.encode(ExchangeCodec.java:76)at com.alibaba.dubbo.rpc.protocol.dubbo.DubboCountCodec.encode(DubboCountCodec.java:39)at com.alibaba.dubbo.remoting.transport.netty.NettyCodecAdapter$InternalEncoder.encode(NettyCodecAdapter.java:81)at org.jboss.netty.handler.codec.oneone.OneToOneEncoder.doEncode(OneToOneEncoder.java:66)at org.jboss.netty.handler.codec.oneone.OneToOneEncoder.handleDownstream(OneToOneEncoder.java:59)at org.jboss.netty.channel.DefaultChannelPipeline.sendDownstream(DefaultChannelPipeline.java:591)at org.jboss.netty.channel.DefaultChannelPipeline$DefaultChannelHandlerContext.sendDownstream(DefaultChannelPipeline.java:784)at org.jboss.netty.channel.SimpleChannelHandler.writeRequested(SimpleChannelHandler.java:292)at com.alibaba.dubbo.remoting.transport.netty.NettyHandler.writeRequested(NettyHandler.java:99)at org.jboss.netty.channel.SimpleChannelHandler.handleDownstream(SimpleChannelHandler.java:254)at org.jboss.netty.channel.DefaultChannelPipeline.sendDownstream(DefaultChannelPipeline.java:591)at org.jboss.netty.channel.DefaultChannelPipeline.sendDownstream(DefaultChannelPipeline.java:582)at org.jboss.netty.channel.Channels.write(Channels.java:704)at org.jboss.netty.channel.Channels.write(Channels.java:671)at org.jboss.netty.channel.AbstractChannel.write(AbstractChannel.java:348)at com.alibaba.dubbo.remoting.transport.netty.NettyChannel.send(NettyChannel.java:98)… 52 moreCaused by: java.lang.ClassNotFoundException: com.esotericsoftware.kryo.factories.SerializerFactoryat java.net.URLClassLoader.findClass(URLClassLoader.java:382)at java.lang.ClassLoader.loadClass(ClassLoader.java:424)at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)at java.lang.ClassLoader.loadClass(ClassLoader.java:357)… 73 more出现问题,总是感觉都配对了,很完美,嗯嗯嗯,但是就是一直这个错,感觉是一个心病一样缠绕。。。。所以,想一想列出几项错误:1.配置问题2.接口调用(使用命令)3.使用工具验证 (dubbo-admin)1.首先保证配置正确(好像是废话,但是我不删)2.使用dubbo-admin注册服务中心,等于zookeeper的可视化界面,服务端和消费端接口正常情况3.使用CMD或win10 PowerShell操作,telnet本地的dubbo端口,telnet 127.0.0.1 20880 回车(找之前的问题截屏,太费劲就手写了)dubbo> lsdubbo> com.dubbo.IDubboService// 你的接口dubbo> ls IDubboService // 查看你的方法是否存在select // 三个方法insertupdate dubbo> invoke select(“哈哈哈”)[{“哈哈哈”,“中国”,“xxx@qq.com”}] // 说明接口能调通,没有问题说了这么多,总之前面的问题还没有解决,从另一方面解决,保证其他项是没有问题的(如配置,接口实现等)想了好久,SerializerFactory感觉还是序列化出问题1.dubbo请求接口正常,说明配置是没有问题的,问题出现在我消费者调用服务者的时候或,我在启动消费者过程中发现这个错,表示转换有问题,不兼容,不匹配——-> 检查版本–查看zookper版本,dubbo引包的版本服务者消费者哦,果然是,总结:版本不兼容,确实是,替换一样的版本就好了,原因在于,项目过多,依赖好多jar不一定是这个版本,所以,保证双方之间版本一致性是很重要的,解决很多调用的时候,或者异常错误不明朗,高版本和低版本差异等,检查版本往往是有效果的,夜深了就说这么多了。

April 16, 2019 · 1 min · jiezi

如何通过Telnet和SSH远程监控主机

来源 | 愿码(ChainDesk.CN)内容编辑愿码Slogan | 连接每个程序员的故事网站 | http://chaindesk.cn愿码愿景 | 打造全学科IT系统免费课程,助力小白用户、初级工程师0成本免费系统学习、低成本进阶,帮助BAT一线资深工程师成长并利用自身优势创造睡后收入。官方公众号 | 愿码 | 愿码服务号 | 区块链部落免费加入愿码全思维工程师社群 | 任一公众号回复“愿码”两个字获取入群二维码本文阅读时长:13min在本文中,你将学习如何在配置了Telnet和SSH的服务器上执行基本配置。我们将首先使用Telnet模块,之后我们将使用首选方法实现相同的配置:使用Python中的不同模块进行SSH,了解如何telnetlib,subprocess,fabric,Netmiko,和paramiko模块的工作。telnetlib()模块在本节中,我们将了解Telnet协议,然后我们将通过远程服务器上的telnetlib模块执行Telnet操作。Telnet是一种允许用户与远程服务器通信的网络协议。它主要由网络管理员用于远程访问和管理设备。要访问设备,请使用终端中远程服务器的IP地址或主机名运行Telnet命令。Telnet在默认端口号上使用TCP 23。要使用Telnet,请确保它已安装在你的系统上。如果没有,请运行以下命令进行安装:$ sudo apt-get install telnetd要使用简单的终端运行Telnet,您只需输入以下命令:$ telnet ip_address_of_your_remote_serverPython具有telnetlib通过Python脚本执行Telnet功能的模块。在telnet远程设备或路由器之前,请确保它们已正确配置,如果没有,则可以使用路由器终端中的以下命令进行基本配置:configure terminalenable password ‘set_Your_password_to_access_router’username ‘set_username’ password ‘set_password_for_remote_access’line vty 0 4 login local transport input all interface f0/0 ip add ‘set_ip_address_to_the_router’ ‘put_subnet_mask’no shut end show ip interface brief现在,让我们看一下Telnet远程设备的示例。为此,创建一个telnet_example.py脚本并在其中编写以下内容:import telnetlibimport getpassimport sysHOST_IP = “your host ip address"host_user = input(“Enter your telnet username: “)password = getpass.getpass()t = telnetlib.Telnet(HOST_IP)t.read_until(b"Username:")t.write(host_user.encode(“ascii”) + b”\n”)if password:t.read_until(b"Password:")t.write(password.encode(“ascii”) + b”\n")t.write(b"enable\n")t.write(b"enter_remote_device_password\n") #password of your remote devicet.write(b"conf t\n")t.write(b"int loop 1\n")t.write(b"ip add 10.1.1.1 255.255.255.255\n")t.write(b"int loop 2\n")t.write(b"ip add 20.2.2.2 255.255.255.255\n")t.write(b"end\n")t.write(b"exit\n")print(t.read_all().decode(“ascii”) )运行脚本,获得如下输出:student@ubuntu:$ python3 telnet_example.pyOutput:Enter your telnet username: studentPassword:server>enablePassword:server#conf tEnter configuration commands, one per line. End with CNTL/Z.server(config)#int loop 1server(config-if)#ip add 10.1.1.1 255.255.255.255server(config-if)#int loop 23server(config-if)#ip add 20.2.2.2 255.255.255.255server(config-if)#endserver#exit在前面的示例中,我们使用该telnetlib模块访问和配置了Cisco路由器。在此脚本中,首先,我们从用户那里获取用户名和密码,以初始化与远程设备的Telnet连接。建立连接后,我们在远程设备上进行了进一步配置。远程登录后,我们将能够访问远程服务器或设备。但是这个Telnet协议有一个非常重要的缺点,即所有数据,包括用户名和密码,都是以文本方式通过网络发送的,这可能会带来安全风险。因此,如今Telnet很少被使用,并且被称为Secure Shell的非常安全的协议所取代,称为SSH。通过在终端中运行以下命令来安装SSH:$ sudo apt install ssh此外,在用户想要通信的远程服务器上,必须安装并运行SSH服务器。SSH使用TCP协议,22默认使用端口号。您可以ssh通过终端运行 命令,如下所示:$ ssh host_name@host_ip_address现在来学习使用Python中的不同模块来执行SSH,例如subprocess,fabric,Netmiko和Paramiko。现在,我们将逐一看到这些模块。subprocess.Popen()模块此模块的底层的进程创建与管理由 Popen 类处理。它提供了很大的灵活性,因此开发者能够处理未被便利函数覆盖的不常见用例。子程序执行将在新进程中完成。要在Unix / Linux上执行子程序,该类将使用该 os.execvp()函数。要在Windows中执行子程序,该类将使用CreateProcess()函数。现在,让我们看一些有用的参数subprocess.Popen():class subprocess.Popen(args, bufsize=0, executable=None, stdin=None, stdout=None, stderr=None, preexec_fn=None, close_fds=False, shell=False, cwd=None, env=None, universal_newlines=False, startupinfo=None, creationflags=0)让我们看看每个论点:· args: 它可以是一系列程序参数或单个字符串。如果args是序列,则执行args中的第一项。如果args是一个字符串,它建议将args作为序列传递。· shell:shell参数默认设置为False,它指定是否使用shell执行程序。如果是shell True,则建议将args作为字符串传递。在 Linux中,如果shell=True,shell默认为/bin/sh。如果args是字符串,则字符串指定要通过shell执行的命令。· bufsize:如果bufsize是0(默认情况下是0),则表示无缓冲,如果bufsize是1,则表示行缓冲。如果bufsize是任何其他正值,请使用给定大小的缓冲区。如果bufsize是任何其他负值,则表示完全缓冲。· executable:它指定要执行的替换程序。· stdin,, stdout和stderr:这些参数分别定义标准输入,标准输出和标准错误。· preexec_fn: 这被设置为可调用对象,将在子进程中执行子进程之前调用。· close_fds: 在Linux中,如果close_fds是真的,所有的文件描述符,除了0,1和2执行子进程之前,将被关闭。在Windows中,如果close_fds是,true那么子进程将继承没有句柄。· env: 如果值不是None,则映射将为新进程定义环境变量。· universal_newlines: 如果该值True则stdout和stderr将被打开,在新行模式下的文本文件。现在,我们将看到一个例子subprocess.Popen()。为此,创建一个 ssh_using_sub.py 脚本并在其中写入以下内容:import subprocessimport sysHOST=“your host username@host ip"COMMAND= “ls"ssh_obj = subprocess.Popen([“ssh”, “%s” % HOST, COMMAND],shell=False,stdout=subprocess.PIPE,stderr=subprocess.PIPE)result = ssh_obj.stdout.readlines()if result == []:err = ssh_obj.stderr.readlines()print(sys.stderr, “ERROR: %s” % err)else:print(result)运行脚本,您将获得如下输出:student@ubuntu:$ python3 ssh_using_sub.pyOutput :student@192.168.0.106’s password:[b’Desktop\n’, b’Documents\n’, b’Downloads\n’, b’examples.desktop\n’, b’Music\n’, b’Pictures\n’, b’Public\n’, b’sample.py\n’, b’spark\n’, b’spark-2.3.1-bin-hadoop2.7\n’, b’spark-2.3.1-bin-hadoop2.7.tgz\n’, b’ssh\n’, b’Templates\n’, b’test_folder\n’, b’test.txt\n’, b’Untitled1.ipynb\n’, b’Untitled.ipynb\n’, b’Videos\n’, b’work\n’]在前面的示例中,首先,我们导入了子进程模块,然后我们定义了要建立SSH连接的主机地址。之后,我们给出了一个通过远程设备执行的简单命令。完成所有这些后,我们将此信息放在 subprocess.Popen()函数中。此函数执行该函数内定义的参数以创建与远程设备的连接。建立SSH连接后,执行我们定义的命令并提供结果。然后我们在终端上打印SSH的结果,如输出中所示。SSH使用Fabric模块Fabric是一个Python库,也是一个使用SSH的命令行工具。它用于通过网络进行系统管理和应用程序部署。我们也可以通过SSH执行shell命令。要使用结构模块,首先必须使用以下命令安装它:$ pip3 install fabric3现在,我们将看到一个例子。创建一个 fabfile.py脚本并在其中写入以下内容:from fabric.api import *env.hosts=[“host_name@host_ip”]env.password=‘your password’def dir(): run(‘mkdir fabric’) print(‘Directory named fabric has been created on your host network’)def diskspace(): run(‘df’)运行脚本,您将获得如下输出:student@ubuntu:$ fab dirOutput:[student@192.168.0.106] Executing task ‘dir’[student@192.168.0.106] run: mkdir fabric Done.Disconnecting from 192.168.0.106… done.在前面的示例中,首先,我们导入了fabric.api模块,然后设置主机名和密码以与主机网络连接。之后,我们设置了一个不同的任务来执行SSH。因此,为了执行我们的程序而不是Python3 fabfile.py,我们使用了fabutility(fab dir),之后我们声明所需的任务应该从我们的执行fabfile.py。在我们的例子中,我们执行了dir任务,该任务’fabric’在远程网络上创建了一个名称目录。您可以在Python文件中添加特定任务。它可以使用fab结构模块的实用程序执行。SSH使用Paramiko库Paramiko是一个实现SSHv2协议的库,用于与远程设备的安全连接。Paramiko是一个围绕SSH的纯Python界面。在使用Paramiko之前,请确保已在系统上正确安装。如果未安装,可以通过在终端中运行以下命令来安装它:$ sudo pip3 install paramiko现在,我们将看到一个使用示例paramiko。对于此paramiko连接,我们使用的是Cisco设备。Paramiko支持基于密码和基于密钥对的身份验证,以实现与服务器的安全连接。在我们的脚本中,我们使用基于密码的身份验证,这意味着我们检查密码,如果可用,则使用普通用户名/密码身份验证尝试进行身份验证。在我们要对你的远程设备或多层路由器进行SSH连接之前,请确保它们已正确配置,如果没有,可以在多层路由器终端中使用以下命令进行基本配置:configure tip domain-name cciepython.comcrypto key generate rsaHow many bits in the modulus [512]: 1024interface range f0/0 - 1switchport mode accessswitchport access vlan 1no shutint vlan 1ip add ‘set_ip_address_to_the_router’ ‘put_subnet_mask’no shutexitenable password ‘set_Your_password_to_access_router’username ‘set_username’ password ‘set_password_for_remote_access’username ‘username’ privilege 15line vty 0 4login localtransport input allend现在,创建一个pmiko.py脚本并在其中编写以下内容:import paramikoimport timeip_address = “host_ip_address"usr = “host_username"pwd = “host_password"c = paramiko.SSHClient()c.set_missing_host_key_policy(paramiko.AutoAddPolicy())c.connect(hostname=ip_address,username=usr,password=pwd)print(“SSH connection is successfully established with “, ip_address)rc = c.invoke_shell()for n in range (2,6):print(“Creating VLAN " + str(n))rc.send(“vlan database\n”)rc.send(“vlan " + str(n) + “\n”)rc.send(“exit\n”)time.sleep(0.5)time.sleep(1)output = rc.recv(65535)print(output)c.close运行脚本,您将获得如下输出:student@ubuntu:$ python3 pmiko.pyOutput:SSH connection is successfuly established with 192.168.0.70Creating VLAN 2Creating VLAN 3Creating VLAN 4Creating VLAN 5在前面的示例中,首先,我们导入了paramiko模块,然后我们定义了连接远程设备所需的SSH凭据。提供凭证后,我们创建一个实例’c’的paramiko.SSHclient(),它是用于与远程设备建立连接和执行命令或操作主客户端。创建SSHClient对象允许我们使用该.connect()函数建立远程连接。然后,我们设置策略paramiko连接,因为默认情况下, paramiko.SSHclient将SSH策略设置为拒绝策略状态。这会导致策略在没有任何验证的情况下拒绝任何SSH连接。在我们的脚本中,我们忽略了SSH连接丢失的可能性 AutoAddPolicy()在不提示的情况下自动添加服务器主机密钥的功能。我们可以将此策略用于测试目的,但出于安全目的,这在生产环境中不是一个好的选择。建立SSH连接后,你可以在设备上执行所需的任何配置或操作。在这里,我们在远程设备上创建了一些虚拟LAN。创建VLAN后,我们只关闭了连接。SSH使用Netmiko库在本节中,我们将了解Netmiko。Netmiko库是Paramiko的高级版本。这是一个multi_vendor基于Paramiko 的图书馆。Netmiko简化了与网络设备的SSH连接,并对设备进行了特殊操作。在对远程设备或多层路由器进行SSH连接之前,请确保它们已正确配置,如果没有,则可以通过Paramiko部分中提到的命令进行基本配置。现在,让我们看一个例子。创建一个 nmiko.py脚本并在其中编写以下代码:from netmiko import ConnectHandlerremote_device={‘device_type’: ‘cisco_ios’,‘ip’: ‘your remote_device ip address’,‘username’: ‘username’,‘password’: ‘password’,}remote_connection = ConnectHandler(**remote_device)#net_connect.find_prompt()for n in range (2,6):print(“Creating VLAN " + str(n))commands = [’exit’,‘vlan database’,‘vlan ’ + str(n), ’exit’]output = remote_connection.send_config_set(commands)print(output)command = remote_connection.send_command(‘show vlan-switch brief’)print(command)运行脚本,您将获得如下输出:student@ubuntu:~$ python3 nmiko.pyOutput:Creating VLAN 2config termEnter configuration commands, one per line. End with CNTL/Z.server(config)#exitserver #vlan databaseserver (vlan)#vlan 2VLAN 2 modified:server (vlan)#exitAPPLY completed.Exiting….server #……..switch#Creating VLAN 5config termEnter configuration commands, one per line. End with CNTL/Z.server (config)#exitserver #vlan databaseserver (vlan)#vlan 5VLAN 5 modified:server (vlan)#exitAPPLY completed.Exiting….VLAN Name Status Ports—- ——————————– ——— ——————————-1 default active Fa0/0, Fa0/1, Fa0/2, Fa0/3, Fa0/4, Fa0/5, Fa0/6, Fa0/7, Fa0/8, Fa0/9, Fa0/10, Fa0/11, Fa0/12, Fa0/13, Fa0/14, Fa0/152 VLAN0002 active 3 VLAN0003 active 4 VLAN0004 active 5 VLAN0005 active 1002 fddi-default active 1003 token-ring-default active 1004 fddinet-default active 1005 trnet-default active在前面的示例中,我们使用Netmiko库来执行SSH,而不是Paramiko。在这个脚本中,首先,我们ConnectHandler从Netmiko库导入,我们通过传入设备字典来建立与远程网络设备的SSH连接。在我们的例子中,那个词典是remote_device。建立连接后,我们执行配置命令以使用该send_config_set()功能创建多个虚拟LAN 。当我们使用这种类型.send_config_set()的函数来传递远程设备上的命令时,它会自动将我们的设备设置为配置模式。发送配置命令后,我们还传递了一个简单的命令来获取有关已配置设备的信息。 ...

April 11, 2019 · 3 min · jiezi

dubbo源码解析(十二)远程通信——Telnet

远程通讯——Telnet目标:介绍telnet的相关实现逻辑、介绍dubbo-remoting-api中的telnet包内的源码解析。前言从dubbo 2.0.5开始,dubbo开始支持通过 telnet 命令来进行服务治理。本文就是讲解一些公用的telnet命令的实现。下面来看一下telnet实现的类图:可以看到,实现了TelnetHandler接口的有六个类,除了TelnetHandlerAdapter是以外,其他五个分别对应了clear、exit、help、log、status命令的实现,具体用来干嘛,请看官方文档的介绍。源码分析(一)TelnetHandler@SPIpublic interface TelnetHandler { /** * telnet. * 处理对应的telnet命令 * @param channel * @param message telnet命令 / String telnet(Channel channel, String message) throws RemotingException;}该接口上telnet命令处理器接口,是一个可扩展接口。它定义了一个方法,就是处理相关的telnet命令。(二)TelnetHandlerAdapter该类继承了ChannelHandlerAdapter,实现了TelnetHandler接口,是TelnetHandler的适配器类,负责在接收到HeaderExchangeHandler发来的telnet命令后分发给对应的TelnetHandler实现类去实现,并且返回命令结果。public class TelnetHandlerAdapter extends ChannelHandlerAdapter implements TelnetHandler { /* * 扩展加载器 / private final ExtensionLoader<TelnetHandler> extensionLoader = ExtensionLoader.getExtensionLoader(TelnetHandler.class); @Override public String telnet(Channel channel, String message) throws RemotingException { // 获得提示键配置,用于nc获取信息时不显示提示符 String prompt = channel.getUrl().getParameterAndDecoded(Constants.PROMPT_KEY, Constants.DEFAULT_PROMPT); boolean noprompt = message.contains("–no-prompt"); message = message.replace("–no-prompt", “”); StringBuilder buf = new StringBuilder(); // 删除头尾空白符的字符串 message = message.trim(); String command; // 获得命令 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) { // 如果有该命令的扩展实现类 if (extensionLoader.hasExtension(command)) { try { // 执行相应命令的实现类的telnet String result = extensionLoader.getExtension(command).telnet(channel, message); if (result == null) { return null; } // 返回结果 buf.append(result); } catch (Throwable t) { buf.append(t.getMessage()); } } else { buf.append(“Unsupported command: “); buf.append(command); } } if (buf.length() > 0) { buf.append("\r\n”); } // 添加 telnet 提示语 if (prompt != null && prompt.length() > 0 && !noprompt) { buf.append(prompt); } return buf.toString(); }}该类只实现了telnet方法,其中的逻辑还是比较清晰,就是根据对应的命令去让对应的实现类产生命令结果。(三)ClearTelnetHandler该类实现了TelnetHandler接口,封装了clear命令的实现。@Activate@Help(parameter = “[lines]”, summary = “Clear screen.”, detail = “Clear screen.")public class ClearTelnetHandler implements TelnetHandler { @Override public String telnet(Channel channel, String message) { // 清除屏幕上的内容行数 int lines = 100; if (message.length() > 0) { // 如果不是一个数字 if (!StringUtils.isInteger(message)) { return “Illegal lines " + message + “, must be integer.”; } lines = Integer.parseInt(message); } StringBuilder buf = new StringBuilder(); // 一行一行清除 for (int i = 0; i < lines; i++) { buf.append("\r\n”); } return buf.toString(); }}(四)ExitTelnetHandler该类实现了TelnetHandler接口,封装了exit命令的实现。@Activate@Help(parameter = “”, summary = “Exit the telnet.”, detail = “Exit the telnet.")public class ExitTelnetHandler implements TelnetHandler { @Override public String telnet(Channel channel, String message) { // 关闭通道 channel.close(); return null; }}(五)HelpTelnetHandler该类实现了TelnetHandler接口,封装了help命令的实现。@Activate@Help(parameter = “[command]”, summary = “Show help.”, detail = “Show help.")public class HelpTelnetHandler implements TelnetHandler { /* * 扩展加载器 / private final ExtensionLoader<TelnetHandler> extensionLoader = ExtensionLoader.getExtensionLoader(TelnetHandler.class); @Override public String telnet(Channel channel, String message) { // 如果需要查看某一个命令的帮助 if (message.length() > 0) { if (!extensionLoader.hasExtension(message)) { return “No such command " + message; } // 获得对应的扩展实现类 TelnetHandler handler = extensionLoader.getExtension(message); Help help = handler.getClass().getAnnotation(Help.class); StringBuilder buf = new StringBuilder(); // 生成命令和帮助信息 buf.append(“Command:\r\n “); buf.append(message + " " + help.parameter().replace("\r\n”, " “).replace("\n”, " “)); buf.append("\r\nSummary:\r\n “); buf.append(help.summary().replace("\r\n”, " “).replace("\n”, " “)); buf.append("\r\nDetail:\r\n “); buf.append(help.detail().replace("\r\n”, " \r\n”).replace("\n”, " \n”)); return buf.toString(); // 如果查看所有命令的帮助 } else { List<List<String>> table = new ArrayList<List<String>>(); // 获得所有命令的提示信息 List<TelnetHandler> handlers = extensionLoader.getActivateExtension(channel.getUrl(), “telnet”); if (handlers != null && !handlers.isEmpty()) { for (TelnetHandler handler : handlers) { Help help = handler.getClass().getAnnotation(Help.class); List<String> row = new ArrayList<String>(); String parameter = " " + extensionLoader.getExtensionName(handler) + " " + (help != null ? help.parameter().replace("\r\n”, " “).replace("\n”, " “) : “”); row.add(parameter.length() > 50 ? parameter.substring(0, 50) + “…” : parameter); String summary = help != null ? help.summary().replace("\r\n”, " “).replace("\n”, " “) : “”; row.add(summary.length() > 50 ? summary.substring(0, 50) + “…” : summary); table.add(row); } } return “Please input "help [command]" show detail.\r\n” + TelnetUtils.toList(table); } }}help分为了需要查看某一个命令的帮助还是查看全部命令的帮助。(六)LogTelnetHandler该类实现了TelnetHandler接口,封装了log命令的实现。@Activate@Help(parameter = “level”, summary = “Change log level or show log “, detail = “Change log level or show log”)public class LogTelnetHandler implements TelnetHandler { public static final String SERVICE_KEY = “telnet.log”; @Override public String telnet(Channel channel, String message) { long size = 0; File file = LoggerFactory.getFile(); StringBuffer buf = new StringBuffer(); if (message == null || message.trim().length() == 0) { buf.append(“EXAMPLE: log error / log 100”); } else { String str[] = message.split(” “); if (!StringUtils.isInteger(str[0])) { // 设置日志级别 LoggerFactory.setLevel(Level.valueOf(message.toUpperCase())); } else { // 获得日志长度 int SHOW_LOG_LENGTH = Integer.parseInt(str[0]); if (file != null && file.exists()) { try { FileInputStream fis = new FileInputStream(file); try { FileChannel filechannel = fis.getChannel(); try { size = filechannel.size(); ByteBuffer bb; if (size <= SHOW_LOG_LENGTH) { // 分配缓冲区 bb = ByteBuffer.allocate((int) size); // 读日志数据 filechannel.read(bb, 0); } else { int pos = (int) (size - SHOW_LOG_LENGTH); // 分配缓冲区 bb = ByteBuffer.allocate(SHOW_LOG_LENGTH); // 读取日志数据 filechannel.read(bb, pos); } bb.flip(); String content = new String(bb.array()).replace("<”, “&lt;”) .replace(">”, “&gt;”).replace("\n”, “<br/><br/>”); buf.append("\r\ncontent:” + content); buf.append("\r\nmodified:” + (new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”) .format(new Date(file.lastModified())))); buf.append("\r\nsize:” + size + “\r\n”); } finally { filechannel.close(); } } finally { fis.close(); } } catch (Exception e) { buf.append(e.getMessage()); } } else { size = 0; buf.append("\r\nMESSAGE: log file not exists or log appender is console .”); } } } buf.append("\r\nCURRENT LOG LEVEL:" + LoggerFactory.getLevel()) .append("\r\nCURRENT LOG APPENDER:" + (file == null ? “console” : file.getAbsolutePath())); return buf.toString(); }}log命令实现原理就是从日志文件中把日志信息读取出来。(七)StatusTelnetHandler该类实现了TelnetHandler接口,封装了status命令的实现。@Activate@Help(parameter = “[-l]”, summary = “Show status.”, detail = “Show status.")public class StatusTelnetHandler implements TelnetHandler { private final ExtensionLoader<StatusChecker> extensionLoader = ExtensionLoader.getExtensionLoader(StatusChecker.class); @Override public String telnet(Channel channel, String message) { // 显示状态列表 if (message.equals("-l”)) { List<StatusChecker> checkers = extensionLoader.getActivateExtension(channel.getUrl(), “status”); String[] header = new String[]{“resource”, “status”, “message”}; List<List<String>> table = new ArrayList<List<String>>(); Map<String, Status> statuses = new HashMap<String, Status>(); if (checkers != null && !checkers.isEmpty()) { // 遍历各个资源的状态,如果一个当全部 OK 时则显示 OK,只要有一个 ERROR 则显示 ERROR,只要有一个 WARN 则显示 WARN for (StatusChecker checker : checkers) { String name = extensionLoader.getExtensionName(checker); Status stat; try { stat = checker.check(); } catch (Throwable t) { stat = new Status(Status.Level.ERROR, t.getMessage()); } statuses.put(name, stat); if (stat.getLevel() != null && stat.getLevel() != Status.Level.UNKNOWN) { List<String> row = new ArrayList<String>(); row.add(name); row.add(String.valueOf(stat.getLevel())); row.add(stat.getMessage() == null ? "" : stat.getMessage()); table.add(row); } } } Status stat = StatusUtils.getSummaryStatus(statuses); List<String> row = new ArrayList<String>(); row.add(“summary”); row.add(String.valueOf(stat.getLevel())); row.add(stat.getMessage()); table.add(row); return TelnetUtils.toTable(header, table); } else if (message.length() > 0) { return “Unsupported parameter " + message + " for status.”; } String status = channel.getUrl().getParameter(“status”); Map<String, Status> statuses = new HashMap<String, Status>(); if (status != null && status.length() > 0) { String[] ss = Constants.COMMA_SPLIT_PATTERN.split(status); for (String s : ss) { StatusChecker handler = extensionLoader.getExtension(s); Status stat; try { stat = handler.check(); } catch (Throwable t) { stat = new Status(Status.Level.ERROR, t.getMessage()); } statuses.put(s, stat); } } Status stat = StatusUtils.getSummaryStatus(statuses); return String.valueOf(stat.getLevel()); }}(八)Help该接口是帮助文档接口@Documented@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.TYPE})public @interface Help { String parameter() default “”; String summary(); String detail() default “”;}可以看上在每个命令的实现类上都加上了@Help注解,为了添加一些帮助文案。(九)TelnetUtils该类是Telnet命令的工具类,其中逻辑我就不介绍了。(十)TelnetCodec该类继承了TransportCodec,是telnet的编解码类。1.属性private static final Logger logger = LoggerFactory.getLogger(TelnetCodec.class);/* * 历史命令列表 /private static final String HISTORY_LIST_KEY = “telnet.history.list”;/* * 历史命令位置,就是用上下键来找历史命令 /private static final String HISTORY_INDEX_KEY = “telnet.history.index”;/* * 向上键 /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(new Object[]{new byte[]{’\r’, ‘\n’} / Windows Enter /, new byte[]{’\n’} / Linux Enter /});/* * 退出 /private static final List<?> EXIT = Arrays.asList(new Object[]{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 */});2.getCharsetprivate static Charset getCharset(Channel channel) { if (channel != null) { // 获得属性设置 Object attribute = channel.getAttribute(Constants.CHARSET_KEY); // 返回指定字符集的charset对象。 if (attribute instanceof String) { 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(); if (url != null) { String parameter = url.getParameter(Constants.CHARSET_KEY); if (parameter != null && parameter.length() > 0) { try { return Charset.forName(parameter); } catch (Throwable t) { logger.warn(t.getMessage(), t); } } } } // 默认的编码是utf-8 try { return Charset.forName(Constants.DEFAULT_CHARSET); } catch (Throwable t) { logger.warn(t.getMessage(), t); } return Charset.defaultCharset();}该方法是获得通道的字符集,根据url中编码来获得字符集,默认是utf-8。3.encode@Overridepublic void encode(Channel channel, ChannelBuffer buffer, Object message) throws IOException { // 如果需要编码的是 telnet 命令结果 if (message instanceof String) { //如果为客户端侧的通道message直接返回 if (isClientSide(channel)) { message = message + “\r\n”; } // 获得字节数组 byte[] msgData = ((String) message).getBytes(getCharset(channel).name()); // 写入缓冲区 buffer.writeBytes(msgData); } else { super.encode(channel, buffer, message); }}该方法是编码方法。4.decode@Overridepublic Object decode(Channel channel, ChannelBuffer buffer) throws IOException { // 获得缓冲区可读的字节 int readable = buffer.readableBytes(); byte[] message = new byte[readable]; // 从缓冲区读数据 buffer.readBytes(message); return decode(channel, buffer, readable, message);}@SuppressWarnings(“unchecked”)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 try { 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); if (history == null || history.isEmpty()) { 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; } } else { // 向下 index = index + 1; if (index > history.size() - 1) { index = 0; } } } // 获得历史命令,并发送给客户端 if (old == null || !old.equals(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); // 将历史命令拼接 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;}该方法是编码。后记该部分相关的源码解析地址:https://github.com/CrazyHZM/i…该文章讲解了telnet的相关实现逻辑,本文有兴趣的朋友可以看看。下一篇我会讲解基于grizzly实现远程通信部分。 ...

December 23, 2018 · 9 min · jiezi