netty与protobuf与node.js

接触netty的源代码应该也有好几个月的时间了,一直觉得只是了解了一个框架的设计和原理,而不能熟练的运用这个框架也算是非常遗憾的事情了,尤其是以前读了nginx的部分代码,而我现在都还不会配置nginx服务器,好囧。。。可能也没有机会用nginx吧。。。不能强求。。。

netty不能够继续遗憾,其实它的运用无非就是用它定义的一些基础handler或者再定义自己的handler用于处理传输和接收数据,那么这篇就讲讲昨天搞的,用node.js将protobuf的数据发送给netty服务器。。。也就是着重要用一下netty自带的protobuf的handler。。。在这中间还遇到了一些坑。。。所也也觉得有必要写一下记录下来,以备以后有机会用到的时候不要再掉进坑里面。。。

这里先上netty主程序部分的代码吧:

package proto;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;
import io.netty.handler.codec.protobuf.ProtobufDecoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.handler.timeout.ReadTimeoutHandler;
import io.netty.handler.timeout.WriteTimeoutHandler;


public class Fjs {
	public void run() throws Exception {
		EventLoopGroup bossGroup = new NioEventLoopGroup();   //这个是用于serversocketchannel的eventloop
		EventLoopGroup workerGroup = new NioEventLoopGroup();    //这个是用于处理accept到的channel
		try {
			ServerBootstrap b = new ServerBootstrap();    //构建serverbootstrap对象
			b.group(bossGroup, workerGroup);   //设置时间循环对象,前者用来处理accept事件,后者用于处理已经建立的连接的io
			b.channel(NioServerSocketChannel.class);   //用它来建立新accept的连接,用于构造serversocketchannel的工厂类
			
			
			b.childHandler(new ChannelInitializer<SocketChannel>(){      //为accept channel的pipeline预添加的inboundhandler
				@Override     //当新连接accept的时候,这个方法会调用
				protected void initChannel(SocketChannel ch) throws Exception {
					
					ch.pipeline().addLast(new ReadTimeoutHandler(10));  //如果10秒钟都没有新的数据读取,那么自动关闭
					ch.pipeline().addLast(new WriteTimeoutHandler(1));  //写的1秒钟超时
					
					ch.pipeline().addLast(new ProtobufVarint32FrameDecoder());  //这里需要将protobuf的数据封装成netty定义的帧,将帧完整的解析出来
					ch.pipeline().addLast(new ProtobufDecoder(MyMessage.Message.getDefaultInstance()));  //用于将二进制的protobuf数据转化为对象
					ch.pipeline().addLast(new MyHandler());  //自己定义的handler
					
		
				}
				
			});
			//bind方法会创建一个serverchannel,并且会将当前的channel注册到eventloop上面,
			//会为其绑定本地端口,并对其进行初始化,为其的pipeline加一些默认的handler
			ChannelFuture f = b.bind(8000).sync();    
			f.channel().closeFuture().sync();  //相当于在这里阻塞,直到serverchannel关闭
		} finally {
			bossGroup.shutdownGracefully();
			workerGroup.shutdownGracefully();
		}
	}
	
	public static void main(String args[]) throws Exception {
		new Fjs().run();
	}
}
代码很简单,而且注释我觉得已经算是很详细了。。。

这里无非就是在pipeline上面添加了3个handler,而且前面的两个handler都是netty框架自带的。。分别用于将按照帧格式传送的数据按照一帧一帧的读取出来,然后接着另一个handler将二进制的的数据转化为对象。。这里详细说明一下吧:

(1)ProtobufVarint32FrameDecoder这个handler的作用就是将数据按照帧的格式读取出来,然后再将里面protobuf的数据取出来。。。

这里的帧是netty自己定义的一种格式,具体如下:


其实定义的帧格式本身还是很简单的,因为数据要在网络上面传输,而网络上传输的数据都是二进制的流数据,如果没有一定的格式包装我们是无法知道我们是否已经接收到,那么netty的做法就是在protobuf的数据前面加上一个头部,用于代表后面protobuf的数据的长度。。。也就是说这个头部是一个整形,但是这个整形又是采用的另外一种编码方式来存储的----google在protobuf里面提出来的Base 128 Varints

这种编码方式有很多的好处,而且可以方便按照字节的方式来解析数据,具体这种编码方式用另外一篇文章来说明吧。。

(2)ProtobufDecoder,这个也是netty自己的handler,它需要传进一个解码器,用于将前面的handler传递过来的protobuf二进制数据转化为对象。。。这个就不细讲了。。

(3)最后就是自己的handler了,很简单,因为直接接收到的就是protobuf的对象,那么就按照对象的方式操作一下就好了。。还是来看一下这个handler的代码吧:

public class MyHandler extends SimpleChannelInboundHandler<MyMessage.Message>{

	@Override
	protected void channelRead0(ChannelHandlerContext ctx, Message message)
			throws Exception {
		// TODO Auto-generated method stub
		System.out.println(message.getText().length());
		System.out.println(message.getText());
		ctx.close();
	}

}

至于说前面两个netty的handler的源码,就不讲了,其实很简单,只要稍微看看就能看明白。

这个真的没啥意思,不过这里又要表扬一下新版本的netty,取消了byte与message之分,开发起来更清晰了。。。那么接下来就是node.js的代码了,它是用于将二进制的protobuf数据直接发给netty服务器。。

其实刚开始觉得这部分会非常的简单的,不就是将数据通过socket发过去么。。。刚开始就是这么直接弄的,后来发现出错。。。然后找了很久才知道netty需要将数据包装成定义好的帧格式。。

也就是说node.js在发送数据之前需要将二进制的数据转化一下,在前面添加一个采用Base 128 Varints编码的头部。。

那么接下来就直接贴上自己的代码了,里面就包含了如何在数据前面添加上头部。。。

var http = require("http");  
var fs = require("fs");
var ProtoBuf = require("protobufjs");  
  


//var Message = ProtoBuf.protoFromFile("MyMessage.proto").build("Message");  

  
  

var data = fs.readFileSync("fjs.dat"); 






function computeRawVarint32Size (value) {
	  if ((value & (0xffffffff <<  7)) == 0) return 1;
    if ((value & (0xffffffff << 14)) == 0) return 2;
    if ((value & (0xffffffff << 21)) == 0) return 3;
    if ((value & (0xffffffff << 28)) == 0) return 4;
    return 5;
}

function writeRawVarint32(value) {   
    var size = computeRawVarint32Size(value);
    var buffer = new Buffer(size);

    var i = 0;
    while (true) {
        if ((value & ~0x7F) == 0) {
            buffer.writeUInt8(value, i);
            break;
        } else {
            var temp = ((value & 0x7F) | 0x80);
            buffer.writeUInt8(temp, i);
            value >>>= 7;
        }
        i++;
    }
    return buffer;
}

function merge(a, b) {
    return Buffer.concat([a, b], a.length + b.length);
}




function formFrame(data) {
    var header = writeRawVarint32(data.length);
    return merge(header, data);
}





var net = require('net');

var socket = new net.Socket();

socket.connect(8000, "192.168.2.181");

socket.on('connect', function(){
    console.log("bbbb");
    socket.write(formFrame(data));
});

代码还是很简单的,基本上很容易能明白。。。其实这里对Base 64 Varints的编码也都是从netty里面copy下来的。。这里就算是保存一下吧。。。说不定以后能用上。。。

毕竟通过protobuf来传输数据的话,可以实现跨平台跨语言的rpc调用。。。而且以后要是有机会的话还真想写一个这样子的rpc框架。。呵呵。。。希望有机会吧。。。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值