(这里做的解析不是很详细,等到走完整个流程再来解析)Dubbo中编解码的工作由Codec2接口的实现来处理,回想一下第一次接触到Codec2相关的内容是在服务端暴露服务的时候,根据具体的协议去暴露服务的步骤中,在DubboProtocol的createServer方法中:
1 2 3 4 5 6 7 8 9 10 11
|
private ExchangeServer createServer(URL url) { 。。。 //这里url会添加codec=dubbo url = url.addParameter(Constants.CODEC_KEY, Version.isCompatibleVersion() ? COMPATIBLE_CODEC_NAME : DubboCodec.NAME); ExchangeServer server; try { server = Exchangers.bind(url, requestHandler); } 。。。 return server; }
|
紧接着进入Exchangers.bind(url, requestHandler);
:
1 2 3 4 5
|
public static ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException { //如果url中没有codec属性,就会添加codec=exchange url = url.addParameterIfAbsent(Constants.CODEC_KEY, "exchange"); return getExchanger(url).bind(url, handler); }
|
然后会继续进入HeaderExchanger的bind方法:
1 2 3
|
public ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException { return new HeaderExchangeServer(Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler)))); }
|
在这里会创建一个DecodeHandler实例。继续跟踪Transporters的bind方法,会发现直接返回一个NettyServer实例,在NettyServer的父类AbstractEndpoint构造方法初始的时候,会根据url获取一个ChannelCodec,并将其赋值给codec存放到NettyServer的实例中。
我们先看下getChannelCodec(url);
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
protected static Codec2 getChannelCodec(URL url) { //获取codecName,不存在的话,默认为telnet String codecName = url.getParameter(Constants.CODEC_KEY, "telnet"); //先看下是不是Codec2的实现,是的话就根据SPI扩展机制获得Codec2扩展的实现 //我们这里默认使用的是DubboCountCodec if (ExtensionLoader.getExtensionLoader(Codec2.class).hasExtension(codecName)) { return ExtensionLoader.getExtensionLoader(Codec2.class).getExtension(codecName); } else { //如果不是Codec2的实现,就去查找Codec的实现 //然后使用CodecAdapter适配器类来转换成Codec2 return new CodecAdapter(ExtensionLoader.getExtensionLoader(Codec.class) .getExtension(codecName)); } }
|
这里返回的是Codec2,而Codec这个接口已经被标记为过时。到这里的话,在NettyServer中就会存在一个Codec2的实例了。
在继续往下看到NettyServer中的doOpen()方法,这里是使用Netty的逻辑打开服务并绑定监听服务的地方:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
protected void doOpen() throws Throwable { NettyHelper.setNettyLoggerFactory(); ExecutorService boss = Executors.newCachedThreadPool(new NamedThreadFactory("NettyServerBoss", true)); ExecutorService worker = Executors.newCachedThreadPool(new NamedThreadFactory("NettyServerWorker", true)); ChannelFactory channelFactory = new NioServerSocketChannelFactory(boss, worker, getUrl().getPositiveParameter(Constants.IO_THREADS_KEY, Constants.DEFAULT_IO_THREADS)); bootstrap = new ServerBootstrap(channelFactory);
final NettyHandler nettyHandler = new NettyHandler(getUrl(), this); channels = nettyHandler.getChannels(); bootstrap.setPipelineFactory(new ChannelPipelineFactory() { public ChannelPipeline getPipeline() { //这里的getCodec方法获取到的codec就是在AbstractEndpoint中我们获取到的codec //NettyCodecAdapter,适配器类 NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec() ,getUrl(), NettyServer.this); ChannelPipeline pipeline = Channels.pipeline(); pipeline.addLast("decoder", adapter.getDecoder());//SimpleChannelUpstreamHandler pipeline.addLast("encoder", adapter.getEncoder());//OneToOneEncoder pipeline.addLast("handler", nettyHandler); return pipeline; } }); // bind channel = bootstrap.bind(getBindAddress()); }
|
这里就在Netty的pipeline中添加了编解码器。这里涉及到Netty的相关流程,可以先了解下Netty3服务端流程简介。
decoder为解码器,是一个SimpleChannelUpstreamHandler,从Socket到Netty中的时候,需要解码,也就是服务提供端接收到消费者的请求的时候,需要解码。
encoder是编码器,是OneToOneEncoder,这个类实现了ChannelDownstreamHandler,从服务提供端发送给服务消费者的时候,需要编码。
nettyHandler实现了ChannelUpstreamHandler, ChannelDownstreamHandler两个,上下的时候都需要处理。
接收到服务消费者的请求的时候,会先执行decoder,然后执行nettyHandler。
发送给消费者的时候,会先执行nettyHandler,然后执行encoder。
dubbo协议头
协议头是16字节的定长数据:
- 2字节short类型的Magic
1字节的消息标志位
- 5位序列化id
- 1位心跳还是正常请求
- 1位双向还是单向
- 1位请求还是响应
1字节的状态位
- 8字节的消息id
- 4字节数据长度
编码的过程
首先会判断是请求还是响应,代码在ExchangeCodec的encode方法:
1 2 3 4 5 6 7 8 9
|
public void encode(Channel channel, ChannelBuffer buffer, Object msg) throws IOException { if (msg instanceof Request) {//Request类型 encodeRequest(channel, buffer, (Request) msg); } else if (msg instanceof Response) {//Response类型 encodeResponse(channel, buffer, (Response) msg); } else {//telenet类型的 super.encode(channel, buffer, msg); } }
|
服务提供者对响应信息编码
在服务提供者端一般是对响应来做编码,所以这里重点看下encodeResponse。
encodeResponse:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
|
protected void encodeResponse(Channel channel, ChannelBuffer buffer, Response res) throws IOException { try { //序列化方式 //也是根据SPI扩展来获取,url中没指定的话默认使用hessian2 Serialization serialization = getSerialization(channel); //长度为16字节的数组,协议头 byte[] header = new byte[HEADER_LENGTH]; //魔数0xdabb Bytes.short2bytes(MAGIC, header); //序列化方式 header[2] = serialization.getContentTypeId(); //心跳消息还是正常消息 if (res.isHeartbeat()) header[2] |= FLAG_EVENT; //响应状态 byte status = res.getStatus(); header[3] = status; //设置请求id Bytes.long2bytes(res.getId(), header, 4); //buffer为1024字节的ChannelBuffer //获取buffer的写入位置 int savedWriteIndex = buffer.writerIndex(); //需要再加上协议头的长度之后,才是正确的写入位置 buffer.writerIndex(savedWriteIndex + HEADER_LENGTH); ChannelBufferOutputStream bos = new ChannelBufferOutputStream(buffer); ObjectOutput out = serialization.serialize(channel.getUrl(), bos); // 对响应信息或者错误消息进行编码 if (status == Response.OK) { if (res.isHeartbeat()) { //心跳 encodeHeartbeatData(channel, out, res.getResult()); } else { //正常响应 encodeResponseData(channel, out, res.getResult()); } } //错误消息 else out.writeUTF(res.getErrorMessage()); out.flushBuffer(); bos.flush(); bos.close(); //写出去的消息的长度 int len = bos.writtenBytes(); //查看消息长度是否过长 checkPayload(channel, len); Bytes.int2bytes(len, header, 12); //重置写入的位置 buffer.writerIndex(savedWriteIndex); //向buffer中写入消息头 buffer.writeBytes(header); // write header. //buffer写出去的位置从writerIndex开始,加上header长度,加上数据长度 buffer.writerIndex(savedWriteIndex + HEADER_LENGTH + len); } catch (Throwable t) { // 发送失败信息给Consumer,否则Consumer只能等超时了 if (! res.isEvent() && res.getStatus() != Response.BAD_RESPONSE) { try { // FIXME 在Codec中打印出错日志?在IoHanndler的caught中统一处理? logger.warn("Fail to encode response: " + res + ", send bad_response info instead, cause: " + t.getMessage(), t);
Response r = new Response(res.getId(), res.getVersion()); r.setStatus(Response.BAD_RESPONSE); r.setErrorMessage("Failed to send response: " + res + ", cause: " + StringUtils.toString(t)); channel.send(r);
return; } catch (RemotingException e) { logger.warn("Failed to send bad_response info back: " + res + ", cause: " + e.getMessage(), e); } }
// 重新抛出收到的异常 if (t instanceof IOException) { throw (IOException) t; } else if (t instanceof RuntimeException) { throw (RuntimeException) t; } else if (t instanceof Error) { throw (Error) t; } else { throw new RuntimeException(t.getMessage(), t); } } }
|
服务消费者对请求信息编码
消费者端暂先不做解析
解码的过程
服务提供者对请求消息的解码
decode方法一次只会解析一个完整的dubbo协议包,但是每次收到的协议包不一定是完整的,或者有可能是多个协议包。看下代码解析,首先看NettyCodecAdapter的内部类InternalDecoder的messageReceived方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
|
public void messageReceived(ChannelHandlerContext ctx, MessageEvent event) throws Exception { Object o = event.getMessage(); if (! (o instanceof ChannelBuffer)) { ctx.sendUpstream(event); return; }
ChannelBuffer input = (ChannelBuffer) o; int readable = input.readableBytes(); if (readable <= 0) { return; }
com.alibaba.dubbo.remoting.buffer.ChannelBuffer message; if (buffer.readable()) { if (buffer instanceof DynamicChannelBuffer) { buffer.writeBytes(input.toByteBuffer()); message = buffer; } else { int size = buffer.readableBytes() + input.readableBytes(); message = com.alibaba.dubbo.remoting.buffer.ChannelBuffers.dynamicBuffer( size > bufferSize ? size : bufferSize); message.writeBytes(buffer, buffer.readableBytes()); message.writeBytes(input.toByteBuffer()); } } else { message = com.alibaba.dubbo.remoting.buffer.ChannelBuffers.wrappedBuffer( input.toByteBuffer()); }
NettyChannel channel = NettyChannel.getOrAddChannel(ctx.getChannel(), url, handler); Object msg; //读索引 int saveReaderIndex; try { do { saveReaderIndex = message.readerIndex(); try { //解码 msg = codec.decode(channel, message); } catch (IOException e) { buffer = com.alibaba.dubbo.remoting.buffer.ChannelBuffers.EMPTY_BUFFER; throw e; } //不完整的协议包 if (msg == Codec2.DecodeResult.NEED_MORE_INPUT) { //重置读索引 message.readerIndex(saveReaderIndex); //跳出循环,之后在finally中把message赋值给buffer保存起来,等到下次接收到数据包的时候会追加到buffer的后面 break; } else {//有多个协议包,触发messageReceived事件 if (saveReaderIndex == message.readerIndex()) { buffer = com.alibaba.dubbo.remoting.buffer.ChannelBuffers.EMPTY_BUFFER; throw new IOException("Decode without read data."); } if (msg != null) { Channels.fireMessageReceived(ctx, msg, event.getRemoteAddress()); } } } while (message.readable()); } finally { if (message.readable()) { message.discardReadBytes(); buffer = message; } else { buffer = com.alibaba.dubbo.remoting.buffer.ChannelBuffers.EMPTY_BUFFER; } NettyChannel.removeChannelIfDisconnected(ctx.getChannel()); } }
|
继续看codec.decode(channel, message);
这里是DubboCountCodec的decode方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
|
public Object decode(Channel channel, ChannelBuffer buffer) throws IOException { //当前的读索引记录下来 int save = buffer.readerIndex(); //多消息 MultiMessage result = MultiMessage.create(); do { //解码消息 Object obj = codec.decode(channel, buffer); //不是完整的协议包 if (Codec2.DecodeResult.NEED_MORE_INPUT == obj) { buffer.readerIndex(save); break; } else {//多个协议包 result.addMessage(obj); logMessageLength(obj, buffer.readerIndex() - save); save = buffer.readerIndex(); } } while (true); if (result.isEmpty()) { return Codec2.DecodeResult.NEED_MORE_INPUT; } if (result.size() == 1) { return result.get(0); } return result; }
|
继续看ExchangeCodec的decode方法:
1 2 3 4 5 6 7 8 9
|
public Object decode(Channel channel, ChannelBuffer buffer) throws IOException { //可读字节数 int readable = buffer.readableBytes(); byte[] header = new byte[Math.min(readable, HEADER_LENGTH)]; //协议头 buffer.readBytes(header); //解码 return decode(channel, buffer, readable, header); }
|
解码decode:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
|
protected Object decode(Channel channel, ChannelBuffer buffer, int readable, byte[] header) throws IOException { //检查魔数. if (readable > 0 && header[0] != MAGIC_HIGH || readable > 1 && header[1] != MAGIC_LOW) { int length = header.length; if (header.length < readable) { header = Bytes.copyOf(header, readable); buffer.readBytes(header, length, readable - length); } for (int i = 1; i < header.length - 1; i ++) { if (header[i] == MAGIC_HIGH && header[i + 1] == MAGIC_LOW) { buffer.readerIndex(buffer.readerIndex() - header.length + i); header = Bytes.copyOf(header, i); break; } } //telenet return super.decode(channel, buffer, readable, header); } //不完整的包 if (readable < HEADER_LENGTH) { return DecodeResult.NEED_MORE_INPUT; }
//数据长度 int len = Bytes.bytes2int(header, 12); checkPayload(channel, len);
int tt = len + HEADER_LENGTH; if( readable < tt ) { return DecodeResult.NEED_MORE_INPUT; }
// limit input stream. ChannelBufferInputStream is = new ChannelBufferInputStream(buffer, len);
try { //解码数据 return decodeBody(channel, is, header); } finally { if (is.available() > 0) { try { StreamUtils.skipUnusedStream(is); } catch (IOException e) { } } } }
|
decodeBody解析数据部分:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
|
protected Object decodeBody(Channel channel, InputStream is, byte[] header) throws IOException { byte flag = header[2], proto = (byte) (flag & SERIALIZATION_MASK); //获取序列化方式 Serialization s = CodecSupport.getSerialization(channel.getUrl(), proto); //反序列化 ObjectInput in = s.deserialize(channel.getUrl(), is); //获取请求id long id = Bytes.bytes2long(header, 4); //这里是解码响应数据 if ((flag & FLAG_REQUEST) == 0) { //response的id设为来时候的Request的id,这样才能对上暗号 Response res = new Response(id); //判断是什么类型请求 if ((flag & FLAG_EVENT) != 0) { res.setEvent(Response.HEARTBEAT_EVENT); } //获取状态 byte status = header[3]; res.setStatus(status); if (status == Response.OK) { try { Object data; if (res.isHeartbeat()) { //解码心跳数据 data = decodeHeartbeatData(channel, in); } else if (res.isEvent()) { //事件 data = decodeEventData(channel, in); } else { //响应 data = decodeResponseData(channel, in, getRequestData(id)); } res.setResult(data); } catch (Throwable t) { res.setStatus(Response.CLIENT_ERROR); res.setErrorMessage(StringUtils.toString(t)); } } else { res.setErrorMessage(in.readUTF()); } return res; } else {//这是解码请求数据 // request的id Request req = new Request(id); req.setVersion("2.0.0"); req.setTwoWay((flag & FLAG_TWOWAY) != 0); if ((flag & FLAG_EVENT) != 0) { req.setEvent(Request.HEARTBEAT_EVENT); } try { Object data; if (req.isHeartbeat()) { //心跳 data = decodeHeartbeatData(channel, in); } else if (req.isEvent()) { //事件 data = decodeEventData(channel, in); } else { //请求 data = decodeRequestData(channel, in); } req.setData(data); } catch (Throwable t) { // bad request req.setBroken(true); req.setData(t); } return req; } }
|
具体的解码细节交给底层解码器,这里是使用的hessian2。
服务消费者对响应消息的解码
暂先不做解释。
- python3中编码与解码的问题
python3中编码与解码的问题 ASCII .Unicode.UTF-8 ASCII 我们知道,在计算机内部,所有的信息最终都表示为一个二进制的字符串.每一个二进制位(bit)有0和1两种状态,因此 ...
- JS高级面试题思路(装箱和拆箱、栈和堆、js中sort()方法、.js中Date对象中的getMounth() 需要注意的、开发中编码和解码使用场景有哪些)
1.装箱和拆箱: 装箱:把基本数据类型转化为对应的引用数据类型的操作: var num = 123 // num var objNum = new Num(123) // object console ...
- Dubbo中暴露服务的过程解析
dubbo暴露服务有两种情况,一种是设置了延迟暴露(比如delay="5000"),另外一种是没有设置延迟暴露或者延迟设置为-1(delay="-1"): 设置 ...
- Dubbo中消费者初始化的过程解析
首先还是Spring碰到dubbo的标签之后,会使用parseCustomElement解析dubbo标签,使用的解析器是dubbo的DubboBeanDefinitionParser,解析完成之后返 ...
- IO 流中编码和解码问题
编码表 ASCII : American Standard Code for Information Interchange 使用一个字节的 7 位可以表示 ISO8859-1 : 拉丁码表. 欧洲码 ...
- 转 python3中SQLLIT编码与解码之Unicode与bytes
#########sample########## sqlite3.OperationalError: Could not decode to UTF-8 column 'logtype' with ...
- ASP.Net中的编码与解码
当javascript传递的参数中有中文时,服务端获得的将是乱码,此时需要用到编码和解码 javascript中编码与解码的三种方法 escape方法返回一个可在所有计算机上读取的编码 String ...
- java、js的编码、解码
如果在地址栏挂载参数,特别是包含中文,往往要进行编码,取值时再解码,以下是java和js中编码.解码的各自方法. java: @Test public void test3() throws Unsu ...
- NET MVC全局异常处理(一) 【转载】网站遭遇DDoS攻击怎么办 使用 HttpRequester 更方便的发起 HTTP 请求 C#文件流。 Url的Base64编码以及解码 C#计算字符串长度,汉字算两个字符 2019周笔记(2.18-2.23) Mysql语句中当前时间不能直接使用C#中的Date.Now传输 Mysql中Count函数的正确使用
NET MVC全局异常处理(一) 目录 .NET MVC全局异常处理 IIS配置 静态错误页配置 .NET错误页配置 程序设置 全局异常配置 .NET MVC全局异常处理 一直知道有.NET有相关 ...
- java并发包分析之———volitale
首要结论:volatile 变量提供了线程的可见性,并不能保证线程安全性和原子性. 什么是线程的可见性: 锁提供了两种主要特性:互斥(mutual exclusion) 和可见性(visibility ...
- SpringMVC:数据绑定入门(-)
1.数据类型,可以绑定基本数据类型,如int age,或者包装类型如:Integer age; 两者的区别:int 类型时,必填该参数,Integer 可以为空. 2.绑定数组 , 3.绑定对象. 3 ...
- Factory Method (工厂模式)
什么是工厂设计模式 根据名字即可了解,工厂肯定是用来生产产品的,在我们的程序开发中,需要用到不同的类,对于熟悉SSH.SSM开发的可以知道,在初期学习的时候,总是有一个框架提供好的的factory供我 ...
- Awesome Big Data List
https://github.com/onurakpolat/awesome-bigdata A curated list of awesome big data frameworks, resour ...
- There is No Alternative~最小生成树变形
Description ICPC (Isles of Coral Park City) consist of several beautiful islands. The citizens reque ...
- DDGScreenShot — 复杂屏幕截屏(如view ScrollView webView wkwebView)
写在前面 最近有这么一个需求,分享页面,分享的是web订单截图,既然是web 就会有超出屏幕的部分, 生成的图片还要加上我们的二维码,这就涉及到图片的合成了. 有了这样的需求,就是各种google.也 ...
- 面试(三)---volatile
一.前言 最近去成都玩了一圈,整体感觉还不错,辞职以后工作找的也很顺利,随着年龄增加自己也考虑定居和个人长期发展的问题,反正乱七八糟的事,总之需要好好屡屡思路,不能那么着急下定论,当然我对 ...
- ZeroMQ 教程 002 : 高级技巧
本文主要译自 zguide - chapter two. 但并不是照本翻译. 上一章我们简单的介绍了一个ZMQ, 并给出了三个套路的例子: 请求-回应, 订阅-发布, 流水线(分治). 这一章, 我们 ...
- Java程序基础编程基础
1.在屏幕上输出"你好" //Programmer name Helloword.javapublic class Helloword { public static void m ...
- [CVPR 2017] Semantic Autoencoder for Zero-Shot Learning论文笔记
http://openaccess.thecvf.com/content_cvpr_2017/papers/Kodirov_Semantic_Autoencoder_for_CVPR_2017_pap ...